From 1745e407ef64115755c24e068e14ed20c9211d67 Mon Sep 17 00:00:00 2001 From: Philip Guenthe Date: Sun, 11 Dec 2011 19:42:29 +0000 Subject: Suspend other rthreads before dumping core or execing; make them exit when exec succeeds. ok jsing@ --- sys/kern/kern_exec.c | 15 ++++- sys/kern/kern_exit.c | 61 ++++++++----------- sys/kern/kern_sig.c | 166 +++++++++++++++++++++++++++++++++++++++++++++++++- sys/kern/kern_synch.c | 7 ++- 4 files changed, 208 insertions(+), 41 deletions(-) (limited to 'sys/kern') diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c index 1c5380c40f9..ff53d3feb9a 100644 --- a/sys/kern/kern_exec.c +++ b/sys/kern/kern_exec.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kern_exec.c,v 1.120 2011/10/16 05:29:51 guenther Exp $ */ +/* $OpenBSD: kern_exec.c,v 1.121 2011/12/11 19:42:28 guenther Exp $ */ /* $NetBSD: kern_exec.c,v 1.75 1996/02/09 18:59:28 christos Exp $ */ /*- @@ -267,6 +267,10 @@ sys_execve(struct proc *p, void *v, register_t *retval) #endif char *pathbuf = NULL; + /* get other threads to stop */ + if ((error = single_thread_set(p, SINGLE_UNWIND, 1))) + goto bad; + /* * Cheap solution to complicated problems. * Mark this process as "leave me alone, I'm execing". @@ -409,6 +413,13 @@ sys_execve(struct proc *p, void *v, register_t *retval) /* adjust "active stack depth" for process VSZ */ pack.ep_ssize = len; /* maybe should go elsewhere, but... */ + /* + * we're committed: any further errors will kill the process, so + * kill the other threads now. + * XXX wait until threads are reaped to make uvmspace_exec() cheaper? + */ + single_thread_set(p, SINGLE_EXIT, 0); + /* * Prepare vmspace for remapping. Note that uvmspace_exec can replace * p_vmspace! @@ -667,6 +678,7 @@ sys_execve(struct proc *p, void *v, register_t *retval) #endif atomic_clearbits_int(&p->p_flag, P_INEXEC); + single_thread_clear(p); #if NSYSTRACE > 0 if (ISSET(p->p_flag, P_SYSTRACE) && @@ -702,6 +714,7 @@ bad: clrflag: #endif atomic_clearbits_int(&p->p_flag, P_INEXEC); + single_thread_clear(p); if (pathbuf != NULL) pool_put(&namei_pool, pathbuf); diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c index dc4e7a16281..3e029725a0c 100644 --- a/sys/kern/kern_exit.c +++ b/sys/kern/kern_exit.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kern_exit.c,v 1.103 2011/07/25 19:38:53 tedu Exp $ */ +/* $OpenBSD: kern_exit.c,v 1.104 2011/12/11 19:42:28 guenther Exp $ */ /* $NetBSD: kern_exit.c,v 1.39 1996/04/22 01:38:25 christos Exp $ */ /* @@ -119,7 +119,6 @@ sys_threxit(struct proc *p, void *v, register_t *retval) void exit1(struct proc *p, int rv, int flags) { - struct proc *q, *nq; struct process *pr, *qr, *nqr; if (p->p_pid == 1) @@ -128,39 +127,21 @@ exit1(struct proc *p, int rv, int flags) atomic_setbits_int(&p->p_flag, P_WEXIT); - /* unlink ourselves from the active threads */ pr = p->p_p; - TAILQ_REMOVE(&pr->ps_threads, p, p_thr_link); - if (TAILQ_EMPTY(&pr->ps_threads)) - wakeup(&pr->ps_threads); - /* - * if one thread calls exit, we take down everybody. - * we have to be careful not to get recursively caught. - * this is kinda sick. - */ - if (flags == EXIT_NORMAL && (p->p_flag & P_THREAD) && - (pr->ps_mainproc->p_flag & P_WEXIT) == 0) { - /* - * we are one of the threads. we SIGKILL the parent, - * it will wake us up again, then we proceed. - */ - atomic_setbits_int(&pr->ps_mainproc->p_flag, P_IGNEXITRV); + + /* single-threaded? */ + if (TAILQ_FIRST(&pr->ps_threads) == p && + TAILQ_NEXT(p, p_thr_link) == NULL) + flags = EXIT_NORMAL; + else { + /* nope, multi-threaded */ + if (flags == EXIT_NORMAL) + single_thread_set(p, SINGLE_EXIT, 0); + } + + if (flags == EXIT_NORMAL) { pr->ps_mainproc->p_xstat = rv; - ptsignal(pr->ps_mainproc, SIGKILL, SPROPAGATED); - tsleep(pr, PUSER, "thrdying", 0); - } else if ((p->p_flag & P_THREAD) == 0) { - if (flags == EXIT_NORMAL) { - q = TAILQ_FIRST(&pr->ps_threads); - for (; q != NULL; q = nq) { - nq = TAILQ_NEXT(q, p_thr_link); - atomic_setbits_int(&q->p_flag, P_IGNEXITRV); - q->p_xstat = rv; - ptsignal(q, SIGKILL, SPROPAGATED); - } - } - wakeup(pr); - while (!TAILQ_EMPTY(&pr->ps_threads)) - tsleep(&pr->ps_threads, PUSER, "thrdeath", 0); + /* * If parent is waiting for us to exit or exec, PS_PPWAIT * is set; we wake up the parent early to avoid deadlock. @@ -173,6 +154,15 @@ exit1(struct proc *p, int rv, int flags) } } + /* unlink ourselves from the active threads */ + TAILQ_REMOVE(&pr->ps_threads, p, p_thr_link); + if ((p->p_flag & P_THREAD) == 0) { + /* main thread gotta wait because it has the pid, et al */ + while (! TAILQ_EMPTY(&pr->ps_threads)) + tsleep(&pr->ps_threads, PUSER, "thrdeath", 0); + } else if (TAILQ_EMPTY(&pr->ps_threads)) + wakeup(&pr->ps_threads); + if (p->p_flag & P_PROFIL) stopprofclock(p); p->p_ru = pool_get(&rusage_pool, PR_WAITOK); @@ -287,8 +277,6 @@ exit1(struct proc *p, int rv, int flags) * Save exit status and final rusage info, adding in child rusage * info and self times. */ - if (!(p->p_flag & P_IGNEXITRV)) - p->p_xstat = rv; *p->p_ru = p->p_stats->p_ru; calcru(p, &p->p_ru->ru_utime, &p->p_ru->ru_stime, NULL); ruadd(p->p_ru, &p->p_stats->p_cru); @@ -488,7 +476,8 @@ loop: proc_finish_wait(q, p); return (0); } - if (p->p_stat == SSTOP && (p->p_flag & P_WAITED) == 0 && + if (p->p_stat == SSTOP && + (p->p_flag & (P_WAITED|P_SUSPSINGLE)) == 0 && (p->p_flag & P_TRACED || SCARG(uap, options) & WUNTRACED)) { atomic_setbits_int(&p->p_flag, P_WAITED); retval[0] = p->p_pid; diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c index 9d1c5d845d5..0775a1edf52 100644 --- a/sys/kern/kern_sig.c +++ b/sys/kern/kern_sig.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kern_sig.c,v 1.130 2011/11/22 23:20:19 joshe Exp $ */ +/* $OpenBSD: kern_sig.c,v 1.131 2011/12/11 19:42:28 guenther Exp $ */ /* $NetBSD: kern_sig.c,v 1.54 1996/04/22 01:38:32 christos Exp $ */ /* @@ -1158,7 +1158,7 @@ proc_stop(struct proc *p, int sw) p->p_stat = SSTOP; atomic_clearbits_int(&p->p_flag, P_WAITED); - atomic_setbits_int(&p->p_flag, P_STOPPED); + atomic_setbits_int(&p->p_flag, P_STOPPED|P_SUSPSIG); if (!timeout_pending(&proc_stop_to)) { timeout_add(&proc_stop_to, 0); /* @@ -1313,6 +1313,12 @@ sigexit(struct proc *p, int signum) p->p_acflag |= AXSIG; if (sigprop[signum] & SA_CORE) { p->p_sisig = signum; + + /* if there are other threads, pause them */ + if (TAILQ_FIRST(&p->p_p->ps_threads) != p || + TAILQ_NEXT(p, p_thr_link) != NULL) + single_thread_set(p, SINGLE_SUSPEND, 0); + if (coredump(p) == 0) signum |= WCOREFLAG; } @@ -1640,5 +1646,161 @@ userret(struct proc *p) while ((sig = CURSIG(p)) != 0) postsig(sig); + if (p->p_flag & P_SUSPSINGLE) { + KERNEL_LOCK(); + single_thread_check(p, 0); + KERNEL_UNLOCK(); + } + p->p_cpu->ci_schedstate.spc_curpriority = p->p_priority = p->p_usrpri; } + +int +single_thread_check(struct proc *p, int deep) +{ + struct process *pr = p->p_p; + + if (pr->ps_single != NULL && pr->ps_single != p) { + do { + int s; + + /* if we're in deep, we need to unwind to the edge */ + if (deep) { + if (pr->ps_flags & PS_SINGLEUNWIND) + return (ERESTART); + if (pr->ps_flags & PS_SINGLEEXIT) + return (EINTR); + } + + if (--pr->ps_singlecount == 0) + wakeup(&pr->ps_singlecount); + if (pr->ps_flags & PS_SINGLEEXIT) + exit1(p, 0, EXIT_THREAD); + + /* not exiting and don't need to unwind, so suspend */ + SCHED_LOCK(s); + p->p_stat = SSTOP; + mi_switch(); + SCHED_UNLOCK(s); + } while (pr->ps_single != NULL); + } + + return (0); +} + +/* + * Stop other threads in the process. The mode controls how and + * where the other threads should stop: + * - SINGLE_SUSPEND: stop wherever they are, will later either be told to exit + * (by setting to SINGLE_EXIT) or be released (via single_thread_clear()) + * - SINGLE_UNWIND: just unwind to kernel boundary, will be told to exit + * or released as with SINGLE_SUSPEND + * - SINGLE_EXIT: unwind to kernel boundary and exit + */ +int +single_thread_set(struct proc *p, enum single_thread_mode mode, int deep) +{ + struct process *pr = p->p_p; + struct proc *q; + int error; + + if ((error = single_thread_check(p, deep))) + return error; + + switch (mode) { + case SINGLE_SUSPEND: + break; + case SINGLE_UNWIND: + atomic_setbits_int(&pr->ps_flags, PS_SINGLEUNWIND); + break; + case SINGLE_EXIT: + atomic_setbits_int(&pr->ps_flags, PS_SINGLEEXIT); + atomic_clearbits_int(&pr->ps_flags, PS_SINGLEUNWIND); + break; +#ifdef DIAGNOSTIC + default: + panic("single_thread_mode = %d", mode); +#endif + } + pr->ps_single = p; + pr->ps_singlecount = 0; + TAILQ_FOREACH(q, &pr->ps_threads, p_thr_link) { + int s; + + if (q == p) + continue; + SCHED_LOCK(s); + atomic_setbits_int(&q->p_flag, P_SUSPSINGLE); + switch (q->p_stat) { + case SIDL: + case SRUN: + pr->ps_singlecount++; + break; + case SSLEEP: + /* if it's not interruptible, then just have to wait */ + if (q->p_flag & P_SINTR) { + /* merely need to suspend? just stop it */ + if (mode == SINGLE_SUSPEND) { + q->p_stat = SSTOP; + break; + } + /* need to unwind or exit, so wake it */ + setrunnable(q); + } + pr->ps_singlecount++; + break; + case SSTOP: + if (mode == SINGLE_EXIT) { + setrunnable(q); + pr->ps_singlecount++; + } + break; + case SZOMB: + case SDEAD: + break; + case SONPROC: + pr->ps_singlecount++; + signotify(q); + break; + } + SCHED_UNLOCK(s); + } + + /* wait until they're all suspended */ + while (pr->ps_singlecount > 0) + tsleep(&pr->ps_singlecount, PUSER, "suspend", 0); + return 0; +} + +void +single_thread_clear(struct proc *p) +{ + struct process *pr = p->p_p; + struct proc *q; + + KASSERT(pr->ps_single == p); + + pr->ps_single = NULL; + atomic_clearbits_int(&pr->ps_flags, PS_SINGLEUNWIND | PS_SINGLEEXIT); + TAILQ_FOREACH(q, &pr->ps_threads, p_thr_link) { + int s; + + if (q == p || (q->p_flag & P_SUSPSINGLE) == 0) + continue; + atomic_clearbits_int(&q->p_flag, P_SUSPSINGLE); + + /* + * if the thread was only stopped for single threading + * then clearing that either makes it runnable or puts + * it back into some sleep queue + */ + SCHED_LOCK(s); + if (q->p_stat == SSTOP && (q->p_flag & P_SUSPSIG) == 0) { + if (q->p_wchan == 0) + setrunnable(q); + else + q->p_stat = SSLEEP; + } + SCHED_UNLOCK(s); + } +} diff --git a/sys/kern/kern_synch.c b/sys/kern/kern_synch.c index aebdfcb3c14..4b711711c02 100644 --- a/sys/kern/kern_synch.c +++ b/sys/kern/kern_synch.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kern_synch.c,v 1.97 2011/07/07 18:00:33 guenther Exp $ */ +/* $OpenBSD: kern_synch.c,v 1.98 2011/12/11 19:42:28 guenther Exp $ */ /* $NetBSD: kern_synch.c,v 1.37 1996/04/22 01:38:37 christos Exp $ */ /* @@ -282,7 +282,7 @@ sleep_setup_signal(struct sleep_state *sls, int prio) * stopped, p->p_wchan will be 0 upon return from CURSIG. */ atomic_setbits_int(&p->p_flag, P_SINTR); - if ((sls->sls_sig = CURSIG(p)) != 0) { + if (p->p_p->ps_single != NULL || (sls->sls_sig = CURSIG(p)) != 0) { if (p->p_wchan) unsleep(p); p->p_stat = SONPROC; @@ -297,8 +297,11 @@ int sleep_finish_signal(struct sleep_state *sls) { struct proc *p = curproc; + int error; if (sls->sls_catch != 0) { + if ((error = single_thread_check(p, 1))) + return (error); if (sls->sls_sig != 0 || (sls->sls_sig = CURSIG(p)) != 0) { if (p->p_sigacts->ps_sigintr & sigmask(sls->sls_sig)) return (EINTR); -- cgit v1.2.3