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/05d7046af0d2
branches:  trunk
changeset: 850452:05d7046af0d2
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 34a5a4a30024 -r 05d7046af0d2 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 34a5a4a30024 -r 05d7046af0d2 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 34a5a4a30024 -r 05d7046af0d2 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