Source-Changes-HG archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
[src/trunk]: src Add support for lazily generating a "global thread ID" for a...
details: https://anonhg.NetBSD.org/src/rev/c9558c7aa8d5
branches: trunk
changeset: 1008866:c9558c7aa8d5
user: thorpej <thorpej%NetBSD.org@localhost>
date: Sat Apr 04 20:20:12 2020 +0000
description:
Add support for lazily generating a "global thread ID" for a LWP. This
identifier uniquely identifies an LWP across the entire system, and will
be used in future improvements in user-space synchronization primitives.
(Test disabled and libc stub not included intentionally so as to avoid
multiple libc version bumps.)
diffstat:
sys/compat/netbsd32/syscalls.master | 4 +-
sys/kern/kern_exit.c | 19 +-
sys/kern/kern_lwp.c | 286 ++++++++++++++++++++++++++++++++++-
sys/kern/sys_lwp.c | 12 +-
sys/kern/syscalls.master | 6 +-
sys/sys/lwp.h | 27 +++-
tests/lib/libc/sys/t_lwp_tid.c | 144 ++++++++++++++++++
7 files changed, 469 insertions(+), 29 deletions(-)
diffs (truncated from 712 to 300 lines):
diff -r 859cb371075f -r c9558c7aa8d5 sys/compat/netbsd32/syscalls.master
--- a/sys/compat/netbsd32/syscalls.master Sat Apr 04 20:17:58 2020 +0000
+++ b/sys/compat/netbsd32/syscalls.master Sat Apr 04 20:20:12 2020 +0000
@@ -1,4 +1,4 @@
- $NetBSD: syscalls.master,v 1.133 2020/03/12 15:02:29 pgoyette Exp $
+ $NetBSD: syscalls.master,v 1.134 2020/04/04 20:20:12 thorpej Exp $
; from: NetBSD: syscalls.master,v 1.81 1998/07/05 08:49:50 jonathan Exp
; @(#)syscalls.master 8.2 (Berkeley) 1/13/94
@@ -747,7 +747,7 @@
netbsd32_charp name, netbsd32_size_t len); }
325 STD { int|netbsd32||_lwp_ctl(int features, \
netbsd32_pointer_t address); }
-326 UNIMPL
+326 NOARGS { lwptid_t|sys||_lwp_gettid(void); }
327 UNIMPL
328 UNIMPL
329 UNIMPL
diff -r 859cb371075f -r c9558c7aa8d5 sys/kern/kern_exit.c
--- a/sys/kern/kern_exit.c Sat Apr 04 20:17:58 2020 +0000
+++ b/sys/kern/kern_exit.c Sat Apr 04 20:20:12 2020 +0000
@@ -1,4 +1,4 @@
-/* $NetBSD: kern_exit.c,v 1.286 2020/03/26 21:31:55 ad Exp $ */
+/* $NetBSD: kern_exit.c,v 1.287 2020/04/04 20:20:12 thorpej Exp $ */
/*-
* Copyright (c) 1998, 1999, 2006, 2007, 2008, 2020 The NetBSD Foundation, Inc.
@@ -67,7 +67,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_exit.c,v 1.286 2020/03/26 21:31:55 ad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_exit.c,v 1.287 2020/04/04 20:20:12 thorpej Exp $");
#include "opt_ktrace.h"
#include "opt_dtrace.h"
@@ -265,7 +265,16 @@
sigfillset(&p->p_sigctx.ps_sigignore);
sigclearall(p, NULL, &kq);
p->p_stat = SDYING;
- mutex_exit(p->p_lock);
+
+ /*
+ * Perform any required thread cleanup. Do this early so
+ * anyone wanting to look us up by our global thread ID
+ * will fail to find us.
+ *
+ * N.B. this will unlock p->p_lock on our behalf.
+ */
+ lwp_thread_cleanup(l);
+
ksiginfo_queue_drain(&kq);
/* Destroy any lwpctl info. */
@@ -559,9 +568,7 @@
/* Free the linux lwp id */
if ((l->l_pflag & LP_PIDLID) != 0 && l->l_lid != p->p_pid)
proc_free_pid(l->l_lid);
- if (l->l_refcnt > 0) {
- lwp_drainrefs(l);
- }
+ lwp_drainrefs(l);
lwp_lock(l);
l->l_prflag &= ~LPR_DETACHED;
l->l_stat = LSZOMB;
diff -r 859cb371075f -r c9558c7aa8d5 sys/kern/kern_lwp.c
--- a/sys/kern/kern_lwp.c Sat Apr 04 20:17:58 2020 +0000
+++ b/sys/kern/kern_lwp.c Sat Apr 04 20:20:12 2020 +0000
@@ -1,4 +1,4 @@
-/* $NetBSD: kern_lwp.c,v 1.232 2020/04/04 06:51:46 maxv Exp $ */
+/* $NetBSD: kern_lwp.c,v 1.233 2020/04/04 20:20:12 thorpej Exp $ */
/*-
* Copyright (c) 2001, 2006, 2007, 2008, 2009, 2019, 2020
@@ -211,7 +211,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_lwp.c,v 1.232 2020/04/04 06:51:46 maxv Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_lwp.c,v 1.233 2020/04/04 20:20:12 thorpej Exp $");
#include "opt_ddb.h"
#include "opt_lockdebug.h"
@@ -245,6 +245,8 @@
#include <sys/psref.h>
#include <sys/msan.h>
#include <sys/kcov.h>
+#include <sys/thmap.h>
+#include <sys/cprng.h>
#include <uvm/uvm_extern.h>
#include <uvm/uvm_object.h>
@@ -252,6 +254,64 @@
static pool_cache_t lwp_cache __read_mostly;
struct lwplist alllwp __cacheline_aligned;
+/*
+ * Lookups by global thread ID operate outside of the normal LWP
+ * locking protocol.
+ *
+ * We are using a thmap, which internally can perform lookups lock-free.
+ * However, we still need to serialize lookups against LWP exit. We
+ * achieve this as follows:
+ *
+ * => Assignment of TID is performed lazily by the LWP itself, when it
+ * is first requested. Insertion into the thmap is done completely
+ * lock-free (other than the internal locking performed by thmap itself).
+ * Once the TID is published in the map, the l___tid field in the LWP
+ * is protected by p_lock.
+ *
+ * => When we look up an LWP in the thmap, we take lwp_threadid_lock as
+ * a READER. While still holding the lock, we add a reference to
+ * the LWP (using atomics). After adding the reference, we drop the
+ * lwp_threadid_lock. We now take p_lock and check the state of the
+ * LWP. If the LWP is draining its references or if the l___tid field
+ * has been invalidated, we drop the reference we took and return NULL.
+ * Otherwise, the lookup has succeeded and the LWP is returned with a
+ * reference count that the caller is responsible for dropping.
+ *
+ * => When a LWP is exiting it releases its TID. While holding the
+ * p_lock, the entry is deleted from the thmap and the l___tid field
+ * invalidated. Once the field is invalidated, p_lock is released.
+ * It is done in this sequence because the l___tid field is used as
+ * the lookup key storage in the thmap in order to conserve memory.
+ * Even if a lookup races with this process and succeeds only to have
+ * the TID invalidated, it's OK because it also results in a reference
+ * that will be drained later.
+ *
+ * => Deleting a node also requires GC of now-unused thmap nodes. The
+ * serialization point between stage_gc and gc is performed by simply
+ * taking the lwp_threadid_lock as a WRITER and immediately releasing
+ * it. By doing this, we know that any busy readers will have drained.
+ *
+ * => When a LWP is exiting, it also drains off any references being
+ * held by others. However, the reference in the lookup path is taken
+ * outside the normal locking protocol. There needs to be additional
+ * serialization so that EITHER lwp_drainrefs() sees the incremented
+ * reference count so that it knows to wait, OR lwp_getref_tid() sees
+ * that the LWP is waiting to drain and thus drops the reference
+ * immediately. This is achieved by taking lwp_threadid_lock as a
+ * WRITER when setting LPR_DRAINING. Note the locking order:
+ *
+ * p_lock -> lwp_threadid_lock
+ *
+ * Note that this scheme could easily use pserialize(9) in place of the
+ * lwp_threadid_lock rwlock lock. However, this would require placing a
+ * pserialize_perform() call in the LWP exit path, which is arguably more
+ * expensive than briefly taking a global lock that should be relatively
+ * uncontended. This issue can be revisited if the rwlock proves to be
+ * a performance problem.
+ */
+static krwlock_t lwp_threadid_lock __cacheline_aligned;
+static thmap_t * lwp_threadid_map __read_mostly;
+
static void lwp_dtor(void *, void *);
/* DTrace proc provider probes */
@@ -285,6 +345,7 @@
.l_fd = &filedesc0,
};
+static void lwp_threadid_init(void);
static int sysctl_kern_maxlwp(SYSCTLFN_PROTO);
/*
@@ -337,6 +398,7 @@
maxlwp = cpu_maxlwp();
sysctl_kern_lwp_setup();
+ lwp_threadid_init();
}
void
@@ -1126,7 +1188,15 @@
/* NOTREACHED */
}
p->p_nzlwps++;
- mutex_exit(p->p_lock);
+
+ /*
+ * Perform any required thread cleanup. Do this early so
+ * anyone wanting to look us up by our global thread ID
+ * will fail to find us.
+ *
+ * N.B. this will unlock p->p_lock on our behalf.
+ */
+ lwp_thread_cleanup(l);
if (p->p_emul->e_lwp_exit)
(*p->p_emul->e_lwp_exit)(l);
@@ -1187,10 +1257,8 @@
*/
mutex_enter(p->p_lock);
for (;;) {
- if (l->l_refcnt > 0) {
- lwp_drainrefs(l);
+ if (lwp_drainrefs(l))
continue;
- }
if ((l->l_prflag & LPR_DETACHED) != 0) {
if ((l2 = p->p_zomblwp) != NULL) {
p->p_zomblwp = NULL;
@@ -1693,6 +1761,19 @@
}
/*
+ * Add one reference to an LWP. Interlocked against lwp_drainrefs()
+ * either by holding the proc's lock or by holding lwp_threadid_lock.
+ */
+static void
+lwp_addref2(struct lwp *l)
+{
+
+ KASSERT(l->l_stat != LSZOMB);
+
+ atomic_inc_uint(&l->l_refcnt);
+}
+
+/*
* Add one reference to an LWP. This will prevent the LWP from
* exiting, thus keep the lwp structure and PCB around to inspect.
*/
@@ -1701,9 +1782,7 @@
{
KASSERT(mutex_owned(l->l_proc->p_lock));
- KASSERT(l->l_stat != LSZOMB);
-
- l->l_refcnt++;
+ lwp_addref2(l);
}
/*
@@ -1732,24 +1811,40 @@
KASSERT(mutex_owned(p->p_lock));
KASSERT(l->l_stat != LSZOMB);
- KASSERT(l->l_refcnt > 0);
+ KASSERT(atomic_load_relaxed(&l->l_refcnt) > 0);
- if (--l->l_refcnt == 0)
+ if (atomic_dec_uint_nv(&l->l_refcnt) == 0)
cv_broadcast(&p->p_lwpcv);
}
/*
- * Drain all references to the current LWP.
+ * Drain all references to the current LWP. Returns true if
+ * we blocked.
*/
-void
+bool
lwp_drainrefs(struct lwp *l)
{
struct proc *p = l->l_proc;
+ bool rv = false;
KASSERT(mutex_owned(p->p_lock));
- while (l->l_refcnt > 0)
+ /*
+ * Lookups in the lwp_threadid_map hold lwp_threadid_lock
+ * as a reader, increase l_refcnt, release it, and then
+ * acquire p_lock to check for LPR_DRAINING. By taking
+ * lwp_threadid_lock as a writer here we ensure that either
+ * we see the increase in l_refcnt or that they see LPR_DRAINING.
+ */
+ rw_enter(&lwp_threadid_lock, RW_WRITER);
+ l->l_prflag |= LPR_DRAINING;
+ rw_exit(&lwp_threadid_lock);
+
+ while (atomic_load_relaxed(&l->l_refcnt) > 0) {
+ rv = true;
cv_wait(&p->p_lwpcv, p->p_lock);
+ }
+ return rv;
}
/*
@@ -2066,6 +2161,169 @@
}
}
+#define LWP_TID_MASK 0x3fffffff /* placeholder */
+
+static void
+lwp_threadid_init(void)
+{
+ rw_init(&lwp_threadid_lock);
+ lwp_threadid_map = thmap_create(0, NULL, THMAP_NOCOPY);
+}
+
+static void
+lwp_threadid_alloc(struct lwp * const l)
+{
+
+ KASSERT(l == curlwp);
+ KASSERT(l->l___tid == 0);
+
+ for (;;) {
Home |
Main Index |
Thread Index |
Old Index