Module Name: src
Committed By: thorpej
Date: Sat Apr 4 20:20:12 UTC 2020
Modified Files:
src/sys/compat/netbsd32: syscalls.master
src/sys/kern: kern_exit.c kern_lwp.c sys_lwp.c syscalls.master
src/sys/sys: lwp.h
Added Files:
src/tests/lib/libc/sys: t_lwp_tid.c
Log Message:
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.)
To generate a diff of this commit:
cvs rdiff -u -r1.133 -r1.134 src/sys/compat/netbsd32/syscalls.master
cvs rdiff -u -r1.286 -r1.287 src/sys/kern/kern_exit.c
cvs rdiff -u -r1.232 -r1.233 src/sys/kern/kern_lwp.c
cvs rdiff -u -r1.75 -r1.76 src/sys/kern/sys_lwp.c
cvs rdiff -u -r1.297 -r1.298 src/sys/kern/syscalls.master
cvs rdiff -u -r1.204 -r1.205 src/sys/sys/lwp.h
cvs rdiff -u -r0 -r1.1 src/tests/lib/libc/sys/t_lwp_tid.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/compat/netbsd32/syscalls.master
diff -u src/sys/compat/netbsd32/syscalls.master:1.133 src/sys/compat/netbsd32/syscalls.master:1.134
--- src/sys/compat/netbsd32/syscalls.master:1.133 Thu Mar 12 15:02:29 2020
+++ src/sys/compat/netbsd32/syscalls.master Sat Apr 4 20:20:12 2020
@@ -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
Index: src/sys/kern/kern_exit.c
diff -u src/sys/kern/kern_exit.c:1.286 src/sys/kern/kern_exit.c:1.287
--- src/sys/kern/kern_exit.c:1.286 Thu Mar 26 21:31:55 2020
+++ src/sys/kern/kern_exit.c Sat Apr 4 20:20:12 2020
@@ -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 @@ exit1(struct lwp *l, int exitcode, int s
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 @@ exit1(struct lwp *l, int exitcode, int s
/* 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;
Index: src/sys/kern/kern_lwp.c
diff -u src/sys/kern/kern_lwp.c:1.232 src/sys/kern/kern_lwp.c:1.233
--- src/sys/kern/kern_lwp.c:1.232 Sat Apr 4 06:51:46 2020
+++ src/sys/kern/kern_lwp.c Sat Apr 4 20:20:12 2020
@@ -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 @@ __KERNEL_RCSID(0, "$NetBSD: kern_lwp.c,v
#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 @@ __KERNEL_RCSID(0, "$NetBSD: kern_lwp.c,v
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 @@ struct lwp lwp0 __aligned(MIN_LWP_ALIGNM
.l_fd = &filedesc0,
};
+static void lwp_threadid_init(void);
static int sysctl_kern_maxlwp(SYSCTLFN_PROTO);
/*
@@ -337,6 +398,7 @@ lwpinit(void)
maxlwp = cpu_maxlwp();
sysctl_kern_lwp_setup();
+ lwp_threadid_init();
}
void
@@ -1126,7 +1188,15 @@ lwp_exit(struct lwp *l)
/* 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 @@ lwp_exit(struct lwp *l)
*/
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 @@ lwp_need_userret(struct lwp *l)
}
/*
+ * 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 @@ lwp_addref(struct lwp *l)
{
KASSERT(mutex_owned(l->l_proc->p_lock));
- KASSERT(l->l_stat != LSZOMB);
-
- l->l_refcnt++;
+ lwp_addref2(l);
}
/*
@@ -1732,24 +1811,40 @@ lwp_delref2(struct lwp *l)
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 @@ lwp_renumber(lwp_t *l, lwpid_t lid)
}
}
+#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 (;;) {
+ l->l___tid = cprng_fast32() & LWP_TID_MASK;
+ if (l->l___tid != 0 &&
+ /*
+ * There is no need to take the lwp_threadid_lock
+ * while inserting into the map: internally, the
+ * map is already concurrency-safe, and the lock
+ * is only needed to serialize removal with respect
+ * to lookup.
+ */
+ thmap_put(lwp_threadid_map,
+ &l->l___tid, sizeof(l->l___tid), l) == l) {
+ /* claimed! */
+ return;
+ }
+ preempt_point();
+ }
+}
+
+static inline void
+lwp_threadid_gc_serialize(void)
+{
+
+ /*
+ * By acquiring the lock as a writer, we will know that
+ * all of the existing readers have drained away and thus
+ * the GC is safe.
+ */
+ rw_enter(&lwp_threadid_lock, RW_WRITER);
+ rw_exit(&lwp_threadid_lock);
+}
+
+static void
+lwp_threadid_free(struct lwp * const l)
+{
+
+ KASSERT(l == curlwp);
+ KASSERT(l->l___tid != 0);
+
+ /*
+ * Ensure that anyone who finds this entry in the lock-free lookup
+ * path sees that the key has been deleted by serialzing with the
+ * examination of l___tid.
+ *
+ * N.B. l___tid field must be zapped *after* deleting from the map
+ * because that field is being used as the key storage by thmap.
+ */
+ KASSERT(mutex_owned(l->l_proc->p_lock));
+ struct lwp * const ldiag __diagused = thmap_del(lwp_threadid_map,
+ &l->l___tid, sizeof(l->l___tid));
+ l->l___tid = 0;
+ mutex_exit(l->l_proc->p_lock);
+
+ KASSERT(l == ldiag);
+
+ void * const gc_ref = thmap_stage_gc(lwp_threadid_map);
+ lwp_threadid_gc_serialize();
+ thmap_gc(lwp_threadid_map, gc_ref);
+}
+
+/*
+ * Return the current LWP's global thread ID. Only the current LWP
+ * should ever use this value, unless it is guaranteed that the LWP
+ * is paused (and then it should be accessed directly, rather than
+ * by this accessor).
+ */
+lwpid_t
+lwp_gettid(void)
+{
+ struct lwp * const l = curlwp;
+
+ if (l->l___tid == 0)
+ lwp_threadid_alloc(l);
+
+ return l->l___tid;
+}
+
+/*
+ * Lookup an LWP by global thread ID. Care must be taken because
+ * callers of this are operating outside the normal locking protocol.
+ * We return the LWP with an additional reference that must be dropped
+ * with lwp_delref().
+ */
+struct lwp *
+lwp_getref_tid(lwpid_t tid)
+{
+ struct lwp *l, *rv;
+
+ rw_enter(&lwp_threadid_lock, RW_READER);
+ l = thmap_get(lwp_threadid_map, &tid, sizeof(&tid));
+ if (__predict_false(l == NULL)) {
+ rw_exit(&lwp_threadid_lock);
+ return NULL;
+ }
+
+ /*
+ * Acquire a reference on the lwp. It is safe to do this unlocked
+ * here because lwp_drainrefs() serializes with us by taking the
+ * lwp_threadid_lock as a writer.
+ */
+ lwp_addref2(l);
+ rw_exit(&lwp_threadid_lock);
+
+ /*
+ * Now verify that our reference is valid.
+ */
+ mutex_enter(l->l_proc->p_lock);
+ if (__predict_false((l->l_prflag & LPR_DRAINING) != 0 ||
+ l->l___tid == 0)) {
+ lwp_delref2(l);
+ rv = NULL;
+ } else {
+ rv = l;
+ }
+ mutex_exit(l->l_proc->p_lock);
+
+ return rv;
+}
+
+/*
+ * Perform any thread-related cleanup on LWP exit.
+ * N.B. l->l_proc->p_lock must be HELD on entry but will
+ * be released before returning!
+ */
+void
+lwp_thread_cleanup(struct lwp *l)
+{
+ KASSERT(l == curlwp);
+ const lwpid_t tid = l->l___tid;
+
+ KASSERT(mutex_owned(l->l_proc->p_lock));
+
+ if (__predict_false(tid != 0)) {
+ /*
+ * Drop our thread ID. This will also unlock
+ * our proc.
+ */
+ lwp_threadid_free(l);
+ } else {
+ /*
+ * No thread cleanup was required; just unlock
+ * the proc.
+ */
+ mutex_exit(l->l_proc->p_lock);
+ }
+}
+
#if defined(DDB)
#include <machine/pcb.h>
Index: src/sys/kern/sys_lwp.c
diff -u src/sys/kern/sys_lwp.c:1.75 src/sys/kern/sys_lwp.c:1.76
--- src/sys/kern/sys_lwp.c:1.75 Thu Jan 30 12:36:38 2020
+++ src/sys/kern/sys_lwp.c Sat Apr 4 20:20:12 2020
@@ -1,4 +1,4 @@
-/* $NetBSD: sys_lwp.c,v 1.75 2020/01/30 12:36:38 ad Exp $ */
+/* $NetBSD: sys_lwp.c,v 1.76 2020/04/04 20:20:12 thorpej Exp $ */
/*-
* Copyright (c) 2001, 2006, 2007, 2008, 2019, 2020 The NetBSD Foundation, Inc.
@@ -35,7 +35,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sys_lwp.c,v 1.75 2020/01/30 12:36:38 ad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sys_lwp.c,v 1.76 2020/04/04 20:20:12 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@@ -175,6 +175,14 @@ sys__lwp_self(struct lwp *l, const void
}
int
+sys__lwp_gettid(struct lwp *l, const void *v, register_t *retval)
+{
+
+ *retval = lwp_gettid();
+ return 0;
+}
+
+int
sys__lwp_getprivate(struct lwp *l, const void *v, register_t *retval)
{
Index: src/sys/kern/syscalls.master
diff -u src/sys/kern/syscalls.master:1.297 src/sys/kern/syscalls.master:1.298
--- src/sys/kern/syscalls.master:1.297 Tue Jan 21 02:37:16 2020
+++ src/sys/kern/syscalls.master Sat Apr 4 20:20:12 2020
@@ -1,4 +1,4 @@
- $NetBSD: syscalls.master,v 1.297 2020/01/21 02:37:16 pgoyette Exp $
+ $NetBSD: syscalls.master,v 1.298 2020/04/04 20:20:12 thorpej Exp $
; @(#)syscalls.master 8.2 (Berkeley) 1/13/94
@@ -661,8 +661,8 @@
char *name, size_t len); }
325 STD { int|sys||_lwp_ctl(int features, \
struct lwpctl **address); }
-; Syscalls 326-339 reserved for LWP syscalls.
-326 UNIMPL
+326 STD { lwptid_t|sys||_lwp_gettid(void); }
+; Syscalls 327-339 reserved for LWP syscalls.
327 UNIMPL
328 UNIMPL
329 UNIMPL
Index: src/sys/sys/lwp.h
diff -u src/sys/sys/lwp.h:1.204 src/sys/sys/lwp.h:1.205
--- src/sys/sys/lwp.h:1.204 Sat Apr 4 06:51:46 2020
+++ src/sys/sys/lwp.h Sat Apr 4 20:20:12 2020
@@ -1,4 +1,4 @@
-/* $NetBSD: lwp.h,v 1.204 2020/04/04 06:51:46 maxv Exp $ */
+/* $NetBSD: lwp.h,v 1.205 2020/04/04 20:20:12 thorpej Exp $ */
/*
* Copyright (c) 2001, 2006, 2007, 2008, 2009, 2010, 2019, 2020
@@ -135,6 +135,24 @@ struct lwp {
u_int l_slptime; /* l: time since last blocked */
bool l_vforkwaiting; /* a: vfork() waiting */
+ /* User-space synchronization. */
+ uintptr_t l___reserved; /* reserved for future use */
+ /*
+ * The global thread ID has special locking and access
+ * considerations. Because many LWPs may never need one,
+ * global thread IDs are allocated lazily in lwp_gettid().
+ * l___tid is not bean to be accessed directly unless
+ * the accessor has specific knowledge that doing so
+ * is safe. l___tid is only assigned by the LWP itself.
+ * Once assigned, it is stable until the LWP exits.
+ * An LWP assigns its own thread ID unlocked before it
+ * reaches visibility to the rest of the system, and
+ * can access its own thread ID unlocked. But once
+ * published, it must hold the proc's lock to change
+ * the value.
+ */
+ lwpid_t l___tid; /* p: global thread id */
+
#if PCU_UNIT_COUNT > 0
struct cpu_info * volatile l_pcu_cpu[PCU_UNIT_COUNT];
uint32_t l_pcu_valid;
@@ -288,6 +306,7 @@ extern int maxlwp __read_mostly; /* max
*/
#define LPR_DETACHED 0x00800000 /* Won't be waited for. */
#define LPR_CRMOD 0x00000100 /* Credentials modified */
+#define LPR_DRAINING 0x80000000 /* Draining references before exiting */
/*
* Mask indicating that there is "exceptional" work to be done on return to
@@ -345,7 +364,7 @@ int lwp_trylock(lwp_t *);
void lwp_addref(lwp_t *);
void lwp_delref(lwp_t *);
void lwp_delref2(lwp_t *);
-void lwp_drainrefs(lwp_t *);
+bool lwp_drainrefs(lwp_t *);
bool lwp_alive(lwp_t *);
lwp_t *lwp_find_first(proc_t *);
@@ -370,6 +389,10 @@ int lwp_setprivate(lwp_t *, void *);
int do_lwp_create(lwp_t *, void *, u_long, lwp_t **, const sigset_t *,
const stack_t *);
+lwpid_t lwp_gettid(void);
+lwp_t * lwp_getref_tid(lwpid_t);
+void lwp_thread_cleanup(lwp_t *);
+
void lwpinit_specificdata(void);
int lwp_specific_key_create(specificdata_key_t *, specificdata_dtor_t);
void lwp_specific_key_delete(specificdata_key_t);
Added files:
Index: src/tests/lib/libc/sys/t_lwp_tid.c
diff -u /dev/null src/tests/lib/libc/sys/t_lwp_tid.c:1.1
--- /dev/null Sat Apr 4 20:20:13 2020
+++ src/tests/lib/libc/sys/t_lwp_tid.c Sat Apr 4 20:20:12 2020
@@ -0,0 +1,144 @@
+/* $NetBSD: t_lwp_tid.c,v 1.1 2020/04/04 20:20:12 thorpej Exp $ */
+
+/*-
+ * Copyright (c) 2019, 2020 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__COPYRIGHT("@(#) Copyright (c) 2019\
+ The NetBSD Foundation, inc. All rights reserved.");
+__RCSID("$NetBSD: t_lwp_tid.c,v 1.1 2020/04/04 20:20:12 thorpej Exp $");
+
+#include <sys/mman.h>
+#include <errno.h>
+#include <lwp.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <atf-c.h>
+
+#define MAX_LWPS 8
+#define STACK_SIZE 65536
+#define LWP_TID_MASK 0x3fffffff /* placeholder */
+
+struct lwp_data {
+ ucontext_t context;
+ void *stack_base;
+ lwpid_t lwpid;
+ lwpid_t threadid;
+};
+
+struct lwp_data lwp_data[MAX_LWPS];
+lwpid_t master_tid;
+
+static bool
+tid_is_unique(int index, lwpid_t tid)
+{
+ if (tid == master_tid)
+ return false;
+
+ for (int i = 0; i < MAX_LWPS; i++) {
+ if (i == index)
+ continue;
+ if (lwp_data[i].threadid == tid)
+ return false;
+ }
+
+ return true;
+}
+
+static void
+gettid_test(void *arg)
+{
+ struct lwp_data *d = arg;
+
+ d->threadid = _lwp_gettid();
+ _lwp_exit();
+}
+
+ATF_TC(lwp_gettid);
+ATF_TC_HEAD(lwp_gettid, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "checks _lwp_gettid()");
+}
+
+ATF_TC_BODY(lwp_gettid, tc)
+{
+ int i;
+
+ master_tid = _lwp_gettid();
+ ATF_REQUIRE(master_tid != 0);
+ ATF_REQUIRE((master_tid & ~LWP_TID_MASK) == 0);
+
+ /* Initialize our LWP data. */
+ for (i = 0; i < MAX_LWPS; i++) {
+ lwp_data[i].stack_base = mmap(NULL, STACK_SIZE,
+ PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_STACK | MAP_PRIVATE, -1, 0);
+ ATF_REQUIRE(lwp_data[i].stack_base != MAP_FAILED);
+ _lwp_makecontext(&lwp_data[i].context, gettid_test,
+ &lwp_data[i], NULL, lwp_data[i].stack_base,
+ STACK_SIZE);
+ lwp_data[i].threadid = 0;
+ }
+
+ /* Now kick off all the LWPs... */
+ for (i = 0; i < MAX_LWPS; i++) {
+ ATF_REQUIRE(_lwp_create(&lwp_data[i].context, 0,
+ &lwp_data[i].lwpid) == 0);
+ printf("Started LWP %d\n", lwp_data[i].lwpid);
+ }
+
+ /* ...and wait for them to exit. */
+ for (i = 0; i < MAX_LWPS;) {
+ lwpid_t reaped_lwpid;
+
+ int rv = _lwp_wait(0, &reaped_lwpid);
+ if (rv == 0) {
+ printf("Reaped LWP %d\n", reaped_lwpid);
+ i++;
+ continue;
+ } else {
+ ATF_REQUIRE(errno != EDEADLK);
+ continue;
+ }
+ }
+
+ /* Check the TIDs. */
+ for (i = 0; i < MAX_LWPS; i++) {
+ printf("Checking tid[%d] %d (0x%08x)\n", i,
+ lwp_data[i].threadid, lwp_data[i].threadid);
+ ATF_REQUIRE(lwp_data[i].threadid != 0);
+ ATF_REQUIRE((lwp_data[i].threadid & ~LWP_TID_MASK) == 0);
+ ATF_REQUIRE(tid_is_unique(i, lwp_data[i].threadid));
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, lwp_gettid);
+
+ return atf_no_error();
+}