summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Mooney <pmooney@pfmooney.com>2016-11-09 22:09:53 +0000
committerPatrick Mooney <pmooney@pfmooney.com>2016-11-16 15:36:53 +0000
commitb103d868ed7f2b4de2da80722eeda3aaf6b95ae6 (patch)
tree1fb0f38751c8685c4ea4ff83d200547904dd3e7b
parent289e41bc2a2b4d5d99886bea0534b96ace98d833 (diff)
downloadillumos-joyent-b103d868ed7f2b4de2da80722eeda3aaf6b95ae6.tar.gz
OS-5738 increase timers allowed per-process
Reviewed by: Ryan Zezeski <rpz@joyent.com> Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com> Approved by: Jerry Jelinek <jerry.jelinek@joyent.com>
-rw-r--r--usr/src/test/os-tests/runfiles/default.run4
-rw-r--r--usr/src/test/os-tests/tests/Makefile2
-rw-r--r--usr/src/test/os-tests/tests/timer/Makefile50
-rw-r--r--usr/src/test/os-tests/tests/timer/timer_limit.c83
-rw-r--r--usr/src/uts/common/os/timer.c181
-rw-r--r--usr/src/uts/common/sys/proc.h3
-rw-r--r--usr/src/uts/common/sys/sysconfig.h5
-rw-r--r--usr/src/uts/common/sys/timer.h11
-rw-r--r--usr/src/uts/common/syscall/sysconfig.c5
9 files changed, 292 insertions, 52 deletions
diff --git a/usr/src/test/os-tests/runfiles/default.run b/usr/src/test/os-tests/runfiles/default.run
index 1b95421f84..216f189519 100644
--- a/usr/src/test/os-tests/runfiles/default.run
+++ b/usr/src/test/os-tests/runfiles/default.run
@@ -48,6 +48,10 @@ tests = ['sigqueue_queue_size']
user = root
tests = ['sdevfs_eisdir']
+[/opt/os-tests/tests/timer]
+user = root
+tests = ['timer_limit']
+
[/opt/os-tests/tests/tmpfs]
user = root
tests = ['tmpfs_badmount', 'tmpfs_enospc']
diff --git a/usr/src/test/os-tests/tests/Makefile b/usr/src/test/os-tests/tests/Makefile
index fcbab56508..b4330ae706 100644
--- a/usr/src/test/os-tests/tests/Makefile
+++ b/usr/src/test/os-tests/tests/Makefile
@@ -14,6 +14,6 @@
# Copyright 2016 Joyent, Inc.
#
-SUBDIRS = poll secflags sigqueue spoof-ras sdevfs tmpfs file-locking
+SUBDIRS = poll secflags sigqueue spoof-ras sdevfs timer tmpfs file-locking
include $(SRC)/test/Makefile.com
diff --git a/usr/src/test/os-tests/tests/timer/Makefile b/usr/src/test/os-tests/tests/timer/Makefile
new file mode 100644
index 0000000000..dd85b0caf8
--- /dev/null
+++ b/usr/src/test/os-tests/tests/timer/Makefile
@@ -0,0 +1,50 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2016 Joyent, Inc.
+#
+
+include $(SRC)/Makefile.master
+
+ROOTOPTPKG = $(ROOT)/opt/os-tests
+TESTDIR = $(ROOTOPTPKG)/tests/timer
+
+PROGS = timer_limit
+
+include $(SRC)/cmd/Makefile.cmd
+include $(SRC)/test/Makefile.com
+
+C99MODE = -xc99=%all
+
+
+CMDS = $(PROGS:%=$(TESTDIR)/%)
+$(CMDS) := FILEMODE = 0555
+
+all: $(PROGS)
+
+install: all $(CMDS)
+
+lint:
+
+clobber: clean
+ -$(RM) $(PROGS)
+
+clean:
+ -$(RM) *.o
+
+$(CMDS): $(TESTDIR) $(PROGS)
+
+$(TESTDIR):
+ $(INS.dir)
+
+$(TESTDIR)/%: %
+ $(INS.file)
diff --git a/usr/src/test/os-tests/tests/timer/timer_limit.c b/usr/src/test/os-tests/tests/timer/timer_limit.c
new file mode 100644
index 0000000000..77473c806f
--- /dev/null
+++ b/usr/src/test/os-tests/tests/timer/timer_limit.c
@@ -0,0 +1,83 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2016 Joyent, Inc.
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+#include <limits.h>
+#include <assert.h>
+#include <sys/sysconfig.h>
+#include <sys/sysmacros.h>
+
+/* Need direct access to _sysconfig to query NCPU */
+extern long _sysconfig(int);
+
+
+static int
+mktimer(timer_t *timer)
+{
+ struct sigevent sev;
+ sev.sigev_notify = SIGEV_SIGNAL;
+ sev.sigev_signo = SIGRTMIN;
+ sev.sigev_value.sival_ptr = timer;
+
+ return (timer_create(CLOCK_MONOTONIC, &sev, timer));
+}
+
+int
+main()
+{
+ long ncpu;
+ size_t limit;
+ timer_t *timers, timer_overage;
+
+ /* Query NCPU with private sysconfig param */
+ ncpu = _sysconfig(_CONFIG_NPROC_NCPU);
+ assert(ncpu > 0 && ncpu < INT32_MAX);
+
+ /* Current specified limit is 4 * NCPU */
+ limit = 4 * ncpu;
+ timers = calloc(limit + 1, sizeof (timer_t));
+ assert(timers != NULL);
+
+ /* Slowly walk up to the limit doing creations/deletions */
+ for (int i = 1; i <= limit; i = MIN(limit, i*2)) {
+ for (int j = 0; j < i; j++) {
+ assert(mktimer(&timers[j]) == 0);
+ }
+
+ /*
+ * Attempt to allocate one additional timer if we've reached
+ * the assumed limit.
+ */
+ if (i == limit) {
+ assert(mktimer(&timer_overage) == -1);
+ }
+
+ for (int j = 0; j < i; j++) {
+ assert(timer_delete(timers[j]) == 0);
+ }
+
+ /* Bail out if we've finished at the limit */
+ if (i == limit)
+ break;
+ }
+
+
+ return (0);
+}
diff --git a/usr/src/uts/common/os/timer.c b/usr/src/uts/common/os/timer.c
index b25a6cbcf1..9e4bfe190e 100644
--- a/usr/src/uts/common/os/timer.c
+++ b/usr/src/uts/common/os/timer.c
@@ -25,11 +25,12 @@
*/
/*
- * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ * Copyright 2016 Joyent, Inc.
*/
#include <sys/timer.h>
#include <sys/systm.h>
+#include <sys/sysmacros.h>
#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/debug.h>
@@ -81,6 +82,7 @@ timer_lock(proc_t *p, itimer_t *it)
* waiters. p_lock must be held on entry; it will not be dropped by
* timer_unlock().
*/
+/* ARGSUSED */
static void
timer_unlock(proc_t *p, itimer_t *it)
{
@@ -123,6 +125,7 @@ timer_delete_locked(proc_t *p, timer_t tid, itimer_t *it)
timer_lock(p, it);
}
+ ASSERT(p->p_itimer_sz > tid);
ASSERT(p->p_itimer[tid] == it);
p->p_itimer[tid] = NULL;
@@ -201,12 +204,14 @@ timer_grab(proc_t *p, timer_t tid)
{
itimer_t **itp, *it;
- if (tid >= timer_max || tid < 0)
+ if (tid < 0) {
return (NULL);
+ }
mutex_enter(&p->p_lock);
- if ((itp = p->p_itimer) == NULL || (it = itp[tid]) == NULL) {
+ if ((itp = p->p_itimer) == NULL || tid >= p->p_itimer_sz ||
+ (it = itp[tid]) == NULL) {
mutex_exit(&p->p_lock);
return (NULL);
}
@@ -258,6 +263,13 @@ clock_timer_init()
{
clock_timer_cache = kmem_cache_create("timer_cache",
sizeof (itimer_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
+
+ /*
+ * Push the timer_max limit up to at least 4 * NCPU. Due to the way
+ * NCPU is defined, proper initialization of the timer limit is
+ * performed at runtime.
+ */
+ timer_max = MAX(NCPU * 4, timer_max);
}
void
@@ -466,13 +478,115 @@ timer_fire(itimer_t *it)
mutex_exit(&p->p_lock);
}
+/*
+ * Allocate an itimer_t and find and appropriate slot for it in p_itimer.
+ * Acquires p_lock and holds it on return, regardless of success.
+ */
+static itimer_t *
+timer_alloc(proc_t *p, timer_t *id)
+{
+ itimer_t *it, **itp = NULL;
+ uint_t i;
+
+ ASSERT(MUTEX_NOT_HELD(&p->p_lock));
+
+ it = kmem_cache_alloc(clock_timer_cache, KM_SLEEP);
+ bzero(it, sizeof (itimer_t));
+ mutex_init(&it->it_mutex, NULL, MUTEX_DEFAULT, NULL);
+
+ mutex_enter(&p->p_lock);
+retry:
+ if (p->p_itimer != NULL) {
+ for (i = 0; i < p->p_itimer_sz; i++) {
+ if (p->p_itimer[i] == NULL) {
+ itp = &(p->p_itimer[i]);
+ break;
+ }
+ }
+ }
+
+ /*
+ * A suitable slot was not found. If possible, allocate (or resize)
+ * the p_itimer array and try again.
+ */
+ if (itp == NULL) {
+ uint_t target_sz = _TIMER_ALLOC_INIT;
+ itimer_t **itp_new;
+
+ if (p->p_itimer != NULL) {
+ ASSERT(p->p_itimer_sz != 0);
+
+ target_sz = p->p_itimer_sz * 2;
+ }
+ /*
+ * Protect against exceeding the max or overflow
+ */
+ if (target_sz > timer_max || target_sz > INT_MAX ||
+ target_sz < p->p_itimer_sz) {
+ kmem_cache_free(clock_timer_cache, it);
+ return (NULL);
+ }
+ mutex_exit(&p->p_lock);
+ itp_new = kmem_zalloc(target_sz * sizeof (itimer_t *),
+ KM_SLEEP);
+ mutex_enter(&p->p_lock);
+ if (target_sz <= p->p_itimer_sz) {
+ /*
+ * A racing thread performed the resize while we were
+ * waiting outside p_lock. Discard our now-useless
+ * allocation and retry.
+ */
+ kmem_free(itp_new, target_sz * sizeof (itimer_t *));
+ goto retry;
+ } else {
+ /*
+ * Instantiate the larger allocation and select the
+ * first fresh entry for use.
+ */
+ if (p->p_itimer != NULL) {
+ uint_t old_sz;
+
+ old_sz = p->p_itimer_sz;
+ bcopy(p->p_itimer, itp_new,
+ old_sz * sizeof (itimer_t *));
+ kmem_free(p->p_itimer,
+ old_sz * sizeof (itimer_t *));
+
+ /*
+ * Short circuit to use the first free entry in
+ * the new allocation. It's possible that
+ * other lower-indexed timers were freed while
+ * p_lock was dropped, but skipping over them
+ * is not harmful at all. In the common case,
+ * we skip the need to walk over an array
+ * filled with timers before arriving at the
+ * slot we know is fresh from the allocation.
+ */
+ i = old_sz;
+ } else {
+ /*
+ * For processes lacking any existing timers,
+ * we can simply select the first entry.
+ */
+ i = 0;
+ }
+ p->p_itimer = itp_new;
+ p->p_itimer_sz = target_sz;
+ }
+ }
+
+ ASSERT(i <= INT_MAX);
+ *id = (timer_t)i;
+ return (it);
+}
+
int
timer_create(clockid_t clock, struct sigevent *evp, timer_t *tid)
{
struct sigevent ev;
proc_t *p = curproc;
clock_backend_t *backend;
- itimer_t *it, **itp;
+ itimer_t *it;
sigqueue_t *sigq;
cred_t *cr = CRED();
int error = 0;
@@ -555,43 +669,19 @@ timer_create(clockid_t clock, struct sigevent *evp, timer_t *tid)
mutex_init(&it->it_mutex, NULL, MUTEX_DEFAULT, NULL);
sigq = kmem_zalloc(sizeof (sigqueue_t), KM_SLEEP);
- mutex_enter(&p->p_lock);
-
/*
- * If this is this process' first timer, we need to attempt to allocate
- * an array of timerstr_t pointers. We drop p_lock to perform the
- * allocation; if we return to discover that p_itimer is non-NULL,
- * we will free our allocation and drive on.
+ * Allocate a timer and choose a slot for it. This acquires p_lock.
*/
- if ((itp = p->p_itimer) == NULL) {
- mutex_exit(&p->p_lock);
- itp = kmem_zalloc(timer_max * sizeof (itimer_t *), KM_SLEEP);
- mutex_enter(&p->p_lock);
-
- if (p->p_itimer == NULL)
- p->p_itimer = itp;
- else {
- kmem_free(itp, timer_max * sizeof (itimer_t *));
- itp = p->p_itimer;
- }
- }
-
- for (i = 0; i < timer_max && itp[i] != NULL; i++)
- continue;
+ it = timer_alloc(p, &i);
+ ASSERT(MUTEX_HELD(&p->p_lock));
- if (i == timer_max) {
- /*
- * We couldn't find a slot. Drop p_lock, free the preallocated
- * timer and sigqueue, and return an error.
- */
+ if (it == NULL) {
mutex_exit(&p->p_lock);
- kmem_cache_free(clock_timer_cache, it);
kmem_free(sigq, sizeof (sigqueue_t));
-
return (set_errno(EAGAIN));
}
- ASSERT(i < timer_max && itp[i] == NULL);
+ ASSERT(i < p->p_itimer_sz && p->p_itimer[i] == NULL);
/*
* If we develop other notification mechanisms, this will need
@@ -613,8 +703,6 @@ timer_create(clockid_t clock, struct sigevent *evp, timer_t *tid)
it->it_sigq = sigq;
it->it_backend = backend;
it->it_lock = ITLK_LOCKED;
- itp[i] = it;
-
if (ev.sigev_notify == SIGEV_THREAD ||
ev.sigev_notify == SIGEV_PORT) {
@@ -645,7 +733,6 @@ timer_create(clockid_t clock, struct sigevent *evp, timer_t *tid)
(port_source_t **)&it->it_portsrc, timer_close_port,
(void *)it, NULL);
if (error) {
- itp[i] = NULL; /* clear slot */
mutex_exit(&p->p_lock);
kmem_cache_free(clock_timer_cache, it);
kmem_free(sigq, sizeof (sigqueue_t));
@@ -658,7 +745,6 @@ timer_create(clockid_t clock, struct sigevent *evp, timer_t *tid)
if (error) {
(void) port_dissociate_ksource(port, PORT_SOURCE_TIMER,
(port_source_t *)it->it_portsrc);
- itp[i] = NULL; /* clear slot */
mutex_exit(&p->p_lock);
kmem_cache_free(clock_timer_cache, it);
kmem_free(sigq, sizeof (sigqueue_t));
@@ -675,6 +761,8 @@ timer_create(clockid_t clock, struct sigevent *evp, timer_t *tid)
it->it_flags |= IT_SIGNAL;
}
+ /* Populate the slot now that the timer is prepped. */
+ p->p_itimer[i] = it;
mutex_exit(&p->p_lock);
/*
@@ -832,7 +920,7 @@ timer_getoverrun(timer_t tid)
void
timer_lwpexit(void)
{
- timer_t i;
+ uint_t i;
proc_t *p = curproc;
klwp_t *lwp = ttolwp(curthread);
itimer_t *it, **itp;
@@ -842,7 +930,7 @@ timer_lwpexit(void)
if ((itp = p->p_itimer) == NULL)
return;
- for (i = 0; i < timer_max; i++) {
+ for (i = 0; i < p->p_itimer_sz; i++) {
if ((it = itp[i]) == NULL)
continue;
@@ -876,7 +964,7 @@ timer_lwpexit(void)
void
timer_lwpbind()
{
- timer_t i;
+ uint_t i;
proc_t *p = curproc;
klwp_t *lwp = ttolwp(curthread);
itimer_t *it, **itp;
@@ -886,7 +974,7 @@ timer_lwpbind()
if ((itp = p->p_itimer) == NULL)
return;
- for (i = 0; i < timer_max; i++) {
+ for (i = 0; i < p->p_itimer_sz; i++) {
if ((it = itp[i]) == NULL)
continue;
@@ -911,16 +999,19 @@ timer_lwpbind()
void
timer_exit(void)
{
- timer_t i;
+ uint_t i;
proc_t *p = curproc;
ASSERT(p->p_itimer != NULL);
+ ASSERT(p->p_itimer_sz != 0);
- for (i = 0; i < timer_max; i++)
- (void) timer_delete(i);
+ for (i = 0; i < p->p_itimer_sz; i++) {
+ (void) timer_delete((timer_t)i);
+ }
- kmem_free(p->p_itimer, timer_max * sizeof (itimer_t *));
+ kmem_free(p->p_itimer, p->p_itimer_sz * sizeof (itimer_t *));
p->p_itimer = NULL;
+ p->p_itimer_sz = 0;
}
/*
diff --git a/usr/src/uts/common/sys/proc.h b/usr/src/uts/common/sys/proc.h
index 20d4cfdaa5..9f36a34ebd 100644
--- a/usr/src/uts/common/sys/proc.h
+++ b/usr/src/uts/common/sys/proc.h
@@ -21,7 +21,7 @@
/*
* Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2015 Joyent, Inc. All rights reserved.
+ * Copyright 2016 Joyent, Inc.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
@@ -307,6 +307,7 @@ typedef struct proc {
size_t p_swrss; /* resident set size before last swap */
struct aio *p_aio; /* pointer to async I/O struct */
struct itimer **p_itimer; /* interval timers */
+ uint_t p_itimer_sz; /* max allocated interval timers */
timeout_id_t p_alarmid; /* alarm's timeout id */
caddr_t p_usrstack; /* top of the process stack */
uint_t p_stkprot; /* stack memory protection */
diff --git a/usr/src/uts/common/sys/sysconfig.h b/usr/src/uts/common/sys/sysconfig.h
index 1bbcdcfe98..12ab07e2db 100644
--- a/usr/src/uts/common/sys/sysconfig.h
+++ b/usr/src/uts/common/sys/sysconfig.h
@@ -25,13 +25,12 @@
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
+ * Copyright 2016 Joyent, Inc.
*/
#ifndef _SYS_SYSCONFIG_H
#define _SYS_SYSCONFIG_H
-#pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.5 */
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -103,6 +102,8 @@ extern int mach_sysconfig(int);
#define _CONFIG_EPHID_MAX 47 /* maximum ephemeral uid */
+#define _CONFIG_NPROC_NCPU 48 /* NCPU (sometimes > NPROC_MAX) */
+
#ifdef __cplusplus
}
#endif
diff --git a/usr/src/uts/common/sys/timer.h b/usr/src/uts/common/sys/timer.h
index ec349c962f..688a381ecc 100644
--- a/usr/src/uts/common/sys/timer.h
+++ b/usr/src/uts/common/sys/timer.h
@@ -25,7 +25,7 @@
*/
/*
- * Copyright (c) 2015, Joyent, Inc. All rights reserved.
+ * Copyright 2016 Joyent, Inc.
*/
#ifndef _SYS_TIMER_H
@@ -34,6 +34,7 @@
#include <sys/types.h>
#include <sys/proc.h>
#include <sys/thread.h>
+#include <sys/param.h>
#ifdef __cplusplus
extern "C" {
@@ -42,7 +43,13 @@ extern "C" {
#ifdef _KERNEL
#define _TIMER_MAX 32
-extern int timer_max; /* patchable via /etc/system */
+/*
+ * Max timers per process. This is patchable via /etc/system and can be
+ * updated via kmdb. Sticking to positive powers of 2 is recommended.
+ */
+extern int timer_max;
+
+#define _TIMER_ALLOC_INIT 8 /* initial size for p_itimer array */
/*
* Bit values for the it_lock field.
diff --git a/usr/src/uts/common/syscall/sysconfig.c b/usr/src/uts/common/syscall/sysconfig.c
index 26ea859224..92daeed703 100644
--- a/usr/src/uts/common/syscall/sysconfig.c
+++ b/usr/src/uts/common/syscall/sysconfig.c
@@ -22,7 +22,7 @@
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
- * Copyright 2012 Joyent, Inc. All rights reserved.
+ * Copyright 2016 Joyent, Inc.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
@@ -112,6 +112,9 @@ sysconfig(int which)
case _CONFIG_NPROC_MAX:
return (max_ncpus);
+ case _CONFIG_NPROC_NCPU:
+ return (NCPU); /* Private sysconfig for direct NCPU access */
+
case _CONFIG_STACK_PROT:
return (curproc->p_stkprot & ~PROT_USER);