diff options
author | Patrick Mooney <pmooney@pfmooney.com> | 2016-11-09 22:09:53 +0000 |
---|---|---|
committer | Patrick Mooney <pmooney@pfmooney.com> | 2016-11-16 15:36:53 +0000 |
commit | b103d868ed7f2b4de2da80722eeda3aaf6b95ae6 (patch) | |
tree | 1fb0f38751c8685c4ea4ff83d200547904dd3e7b | |
parent | 289e41bc2a2b4d5d99886bea0534b96ace98d833 (diff) | |
download | illumos-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.run | 4 | ||||
-rw-r--r-- | usr/src/test/os-tests/tests/Makefile | 2 | ||||
-rw-r--r-- | usr/src/test/os-tests/tests/timer/Makefile | 50 | ||||
-rw-r--r-- | usr/src/test/os-tests/tests/timer/timer_limit.c | 83 | ||||
-rw-r--r-- | usr/src/uts/common/os/timer.c | 181 | ||||
-rw-r--r-- | usr/src/uts/common/sys/proc.h | 3 | ||||
-rw-r--r-- | usr/src/uts/common/sys/sysconfig.h | 5 | ||||
-rw-r--r-- | usr/src/uts/common/sys/timer.h | 11 | ||||
-rw-r--r-- | usr/src/uts/common/syscall/sysconfig.c | 5 |
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); |