#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_lwp.c,v 1.271 2026/01/04 01:35:44 riastradh Exp $");
#include "opt_ddb.h"
#include "opt_lockdebug.h"
#include "opt_dtrace.h"
#define _LWP_API_PRIVATE
#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/cprng.h>
#include <sys/cpu.h>
#include <sys/dtrace_bsd.h>
#include <sys/filedesc.h>
#include <sys/fstrans.h>
#include <sys/futex.h>
#include <sys/intr.h>
#include <sys/kauth.h>
#include <sys/kcov.h>
#include <sys/kmem.h>
#include <sys/lockdebug.h>
#include <sys/lwpctl.h>
#include <sys/msan.h>
#include <sys/pool.h>
#include <sys/proc.h>
#include <sys/pset.h>
#include <sys/psref.h>
#include <sys/ptrace.h>
#include <sys/sdt.h>
#include <sys/sleepq.h>
#include <sys/syncobj.h>
#include <sys/syscall_stats.h>
#include <sys/syscallargs.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/uidinfo.h>
#include <sys/xcall.h>
#include <uvm/uvm_extern.h>
#include <uvm/uvm_object.h>
static pool_cache_t lwp_cache __read_mostly;
struct lwplist alllwp __cacheline_aligned;
static int lwp_ctor(void *, void *, int);
static void lwp_dtor(void *, void *);
SDT_PROVIDER_DEFINE(proc);
SDT_PROBE_DEFINE1(proc, kernel, , lwp__create, "struct lwp *");
SDT_PROBE_DEFINE1(proc, kernel, , lwp__start, "struct lwp *");
SDT_PROBE_DEFINE1(proc, kernel, , lwp__exit, "struct lwp *");
struct turnstile turnstile0 __cacheline_aligned;
struct lwp lwp0 __aligned(MIN_LWP_ALIGNMENT) = {
#ifdef LWP0_CPU_INFO
.l_cpu = LWP0_CPU_INFO,
#endif
#ifdef LWP0_MD_INITIALIZER
.l_md = LWP0_MD_INITIALIZER,
#endif
.l_proc = &proc0,
.l_lid = 0,
.l_flag = LW_SYSTEM,
.l_stat = LSONPROC,
.l_ts = &turnstile0,
.l_syncobj = &sched_syncobj,
.l_refcnt = 0,
.l_priority = PRI_USER + NPRI_USER - 1,
.l_inheritedprio = -1,
.l_class = SCHED_OTHER,
.l_psid = PS_NONE,
.l_pi_lenders = SLIST_HEAD_INITIALIZER(&lwp0.l_pi_lenders),
.l_name = __UNCONST("swapper"),
.l_fd = &filedesc0,
};
static int
lwp_maxlwp(void)
{
uint64_t lwps_per = ctob(physmem) / (1024 * 1024);
return MAX(MIN(MAXMAXLWP, lwps_per), MAXLWP);
}
static int sysctl_kern_maxlwp(SYSCTLFN_PROTO);
static int
sysctl_kern_maxlwp(SYSCTLFN_ARGS)
{
int error, nmaxlwp;
struct sysctlnode node;
nmaxlwp = maxlwp;
node = *rnode;
node.sysctl_data = &nmaxlwp;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (nmaxlwp < 0 || nmaxlwp >= MAXMAXLWP)
return SET_ERROR(EINVAL);
if (nmaxlwp > lwp_maxlwp())
return SET_ERROR(EINVAL);
maxlwp = nmaxlwp;
return 0;
}
static void
sysctl_kern_lwp_setup(void)
{
sysctl_createv(NULL, 0, NULL, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
CTLTYPE_INT, "maxlwp",
SYSCTL_DESCR("Maximum number of simultaneous threads"),
sysctl_kern_maxlwp, 0, NULL, 0,
CTL_KERN, CTL_CREATE, CTL_EOL);
}
void
lwpinit(void)
{
LIST_INIT(&alllwp);
lwpinit_specificdata();
lwp_cache = pool_cache_init(sizeof(lwp_t), MIN_LWP_ALIGNMENT, 0,
PR_PSERIALIZE, "lwppl", NULL, IPL_NONE, lwp_ctor, lwp_dtor, NULL);
maxlwp = lwp_maxlwp();
sysctl_kern_lwp_setup();
}
void
lwp0_init(void)
{
struct lwp *l = &lwp0;
KASSERT((void *)uvm_lwp_getuarea(l) != NULL);
LIST_INSERT_HEAD(&alllwp, l, l_list);
callout_init(&l->l_timeout_ch, CALLOUT_MPSAFE);
callout_setfunc(&l->l_timeout_ch, sleepq_timeout, l);
cv_init(&l->l_sigcv, "sigwait");
cv_init(&l->l_waitcv, "vfork");
l->l_cred = kauth_cred_hold(proc0.p_cred);
kdtrace_thread_ctor(NULL, l);
lwp_initspecific(l);
SYSCALL_TIME_LWP_INIT(l);
}
static int
lwp_ctor(void *arg, void *obj, int flags)
{
lwp_t *l = obj;
l->l_stat = LSIDL;
l->l_cpu = curcpu();
l->l_mutex = l->l_cpu->ci_schedstate.spc_lwplock;
l->l_ts = kmem_alloc(sizeof(*l->l_ts), flags == PR_WAITOK ?
KM_SLEEP : KM_NOSLEEP);
if (l->l_ts == NULL) {
return SET_ERROR(ENOMEM);
} else {
turnstile_ctor(l->l_ts);
return 0;
}
}
static void
lwp_dtor(void *arg, void *obj)
{
lwp_t *l = obj;
KASSERT(l->l_cpu != NULL);
if (l->l_ts != &turnstile0)
kmem_free(l->l_ts, sizeof(*l->l_ts));
}
int
lwp_suspend(struct lwp *curl, struct lwp *t)
{
int error;
KASSERT(mutex_owned(t->l_proc->p_lock));
KASSERT(lwp_locked(t, NULL));
KASSERT(curl != t || curl->l_stat == LSONPROC);
if ((curl->l_flag & (LW_WEXIT | LW_WCORE)) != 0) {
lwp_unlock(t);
return SET_ERROR(EDEADLK);
}
if ((t->l_flag & LW_DBGSUSPEND) != 0) {
lwp_unlock(t);
return 0;
}
error = 0;
switch (t->l_stat) {
case LSRUN:
case LSONPROC:
t->l_flag |= LW_WSUSPEND;
lwp_need_userret(t);
lwp_unlock(t);
break;
case LSSLEEP:
t->l_flag |= LW_WSUSPEND;
lwp_need_userret(t);
if ((t->l_flag & LW_SINTR) != 0)
setrunnable(t);
else
lwp_unlock(t);
break;
case LSSUSPENDED:
lwp_unlock(t);
break;
case LSSTOP:
t->l_flag |= LW_WSUSPEND;
lwp_need_userret(t);
setrunnable(t);
break;
case LSIDL:
case LSZOMB:
error = SET_ERROR(EINTR);
lwp_unlock(t);
break;
}
return (error);
}
void
lwp_continue(struct lwp *l)
{
KASSERT(mutex_owned(l->l_proc->p_lock));
KASSERT(lwp_locked(l, NULL));
if ((l->l_flag & LW_WREBOOT) != 0) {
lwp_unlock(l);
return;
}
l->l_flag &= ~LW_WSUSPEND;
if (l->l_stat != LSSUSPENDED || (l->l_flag & LW_DBGSUSPEND) != 0) {
lwp_unlock(l);
return;
}
setrunnable(l);
}
void
lwp_unstop(struct lwp *l)
{
struct proc *p = l->l_proc;
KASSERT(mutex_owned(&proc_lock));
KASSERT(mutex_owned(p->p_lock));
lwp_lock(l);
KASSERT((l->l_flag & LW_DBGSUSPEND) == 0);
if (l->l_stat != LSSTOP) {
lwp_unlock(l);
return;
}
p->p_stat = SACTIVE;
p->p_sflag &= ~PS_STOPPING;
if (!p->p_waited)
p->p_pptr->p_nstopchild--;
if (l->l_wchan == NULL) {
setrunnable(l);
} else if (p->p_xsig && (l->l_flag & LW_SINTR) != 0) {
setrunnable(l);
} else {
l->l_stat = LSSLEEP;
p->p_nrlwps++;
lwp_unlock(l);
}
}
int
lwp_wait(struct lwp *l, lwpid_t lid, lwpid_t *departed, bool exiting)
{
const lwpid_t curlid = l->l_lid;
proc_t *p = l->l_proc;
lwp_t *l2, *next;
int error;
KASSERT(mutex_owned(p->p_lock));
p->p_nlwpwait++;
l->l_waitingfor = lid;
for (;;) {
int nfound;
if ((p->p_sflag & PS_WCORE) != 0) {
mutex_exit(p->p_lock);
lwp_userret(l);
KASSERT(false);
}
if ((l2 = p->p_zomblwp) != NULL) {
p->p_zomblwp = NULL;
lwp_free(l2, false, false);
mutex_enter(p->p_lock);
continue;
}
nfound = 0;
error = 0;
if (lid != 0) {
l2 = proc_find_lwp(p, lid);
if (l2 == NULL) {
error = SET_ERROR(ESRCH);
break;
}
KASSERT(l2->l_lid == lid);
if ((l2->l_prflag & LPR_DETACHED) != 0) {
error = SET_ERROR(EINVAL);
break;
}
} else {
l2 = LIST_FIRST(&p->p_lwps);
}
for (; l2 != NULL; l2 = next) {
next = (lid != 0 ? NULL : LIST_NEXT(l2, l_sibling));
if (l2->l_lid == lid && l2->l_waitingfor == curlid) {
error = SET_ERROR(EDEADLK);
break;
}
if (l2 == l)
continue;
if ((l2->l_prflag & LPR_DETACHED) != 0) {
nfound += exiting;
continue;
}
if (lid != 0) {
if (l2->l_waiter == 0)
l2->l_waiter = curlid;
} else if (l2->l_waiter != 0) {
nfound++;
continue;
}
nfound++;
if (l2->l_stat != LSZOMB)
continue;
l->l_waitingfor = 0;
l2->l_waiter = 0;
p->p_nlwpwait--;
if (departed)
*departed = l2->l_lid;
sched_lwp_collect(l2);
lwp_free(l2, false, false);
mutex_enter(p->p_lock);
return 0;
}
if (error != 0)
break;
if (nfound == 0) {
error = SET_ERROR(ESRCH);
break;
}
if (exiting) {
KASSERT(p->p_nlwps > 1);
error = cv_timedwait(&p->p_lwpcv, p->p_lock, 1);
break;
}
if (p->p_nlwpwait == p->p_nlwps) {
error = SET_ERROR(EDEADLK);
break;
}
if ((error = cv_wait_sig(&p->p_lwpcv, p->p_lock)) != 0)
break;
}
if (lid != 0) {
l2 = proc_find_lwp(p, lid);
KASSERT(l2 == NULL || l2->l_lid == lid);
if (l2 != NULL && l2->l_waiter == curlid)
l2->l_waiter = 0;
}
p->p_nlwpwait--;
l->l_waitingfor = 0;
cv_broadcast(&p->p_lwpcv);
return error;
}
int
lwp_create(lwp_t *l1, proc_t *p2, vaddr_t uaddr, int flags,
void *stack, size_t stacksize, void (*func)(void *), void *arg,
lwp_t **rnewlwpp, int sclass, const sigset_t *sigmask,
const stack_t *sigstk)
{
struct lwp *l2;
KASSERT(l1 == curlwp || l1->l_proc == &proc0);
mutex_enter(p2->p_lock);
if (p2->p_nlwps != 0 && p2 != &proc0) {
uid_t uid = kauth_cred_getuid(p2->p_cred);
int count = chglwpcnt(uid, 1);
if (__predict_false(count >
p2->p_rlimit[RLIMIT_NTHR].rlim_cur)) {
if (kauth_authorize_process(l1->l_cred,
KAUTH_PROCESS_RLIMIT, p2,
KAUTH_ARG(KAUTH_REQ_PROCESS_RLIMIT_BYPASS),
&p2->p_rlimit[RLIMIT_NTHR], KAUTH_ARG(RLIMIT_NTHR))
!= 0) {
(void)chglwpcnt(uid, -1);
mutex_exit(p2->p_lock);
return SET_ERROR(EAGAIN);
}
}
}
if ((l2 = p2->p_zomblwp) != NULL) {
p2->p_zomblwp = NULL;
lwp_free(l2, true, false);
KASSERT(l2->l_ts != NULL);
KASSERT(l2->l_inheritedprio == -1);
KASSERT(SLIST_EMPTY(&l2->l_pi_lenders));
memset(&l2->l_startzero, 0, sizeof(*l2) -
offsetof(lwp_t, l_startzero));
} else {
mutex_exit(p2->p_lock);
l2 = pool_cache_get(lwp_cache, PR_WAITOK);
memset(&l2->l_startzero, 0, sizeof(*l2) -
offsetof(lwp_t, l_startzero));
SLIST_INIT(&l2->l_pi_lenders);
}
KASSERT(l2->l_stat == LSIDL);
KASSERT(l2->l_cpu != NULL);
KASSERT(l2->l_ts != NULL);
KASSERT(l2->l_mutex == l2->l_cpu->ci_schedstate.spc_lwplock);
l2->l_proc = p2;
l2->l_refcnt = 0;
l2->l_class = sclass;
if (__predict_false(proc_alloc_lwpid(p2, l2) == -1)) {
pool_cache_put(lwp_cache, l2);
return SET_ERROR(EAGAIN);
}
l2->l_boostpri = ((flags & LWP_VFORK) ? PRI_KERNEL : PRI_USER);
l2->l_priority = l1->l_priority;
l2->l_inheritedprio = -1;
l2->l_protectprio = -1;
l2->l_auxprio = -1;
l2->l_flag = 0;
l2->l_pflag = LP_MPSAFE;
TAILQ_INIT(&l2->l_ld_locks);
l2->l_psrefs = 0;
kmsan_lwp_alloc(l2);
if (flags & LWP_VFORK && l1->l_lwpctl) {
l2->l_lwpctl = l1->l_lwpctl;
l2->l_flag |= LW_LWPCTL;
}
l2->l_fd = p2->p_fd;
if (p2->p_nlwps != 0) {
KASSERT(l1->l_proc == p2);
fd_hold(l2);
} else {
KASSERT(l1->l_proc != p2);
}
if (p2->p_flag & PK_SYSTEM) {
l2->l_flag |= LW_SYSTEM;
}
kdtrace_thread_ctor(NULL, l2);
lwp_initspecific(l2);
sched_lwp_fork(l1, l2);
callout_init(&l2->l_timeout_ch, CALLOUT_MPSAFE);
callout_setfunc(&l2->l_timeout_ch, sleepq_timeout, l2);
cv_init(&l2->l_sigcv, "sigwait");
cv_init(&l2->l_waitcv, "vfork");
l2->l_syncobj = &sched_syncobj;
PSREF_DEBUG_INIT_LWP(l2);
if (rnewlwpp != NULL)
*rnewlwpp = l2;
pcu_save_all(l1);
#if PCU_UNIT_COUNT > 0
l2->l_pcu_valid = l1->l_pcu_valid;
#endif
uvm_lwp_setuarea(l2, uaddr);
uvm_lwp_fork(l1, l2, stack, stacksize, func, (arg != NULL) ? arg : l2);
mutex_enter(p2->p_lock);
l2->l_cred = kauth_cred_hold(p2->p_cred);
if ((flags & LWP_DETACHED) != 0) {
l2->l_prflag = LPR_DETACHED;
p2->p_ndlwps++;
} else
l2->l_prflag = 0;
if (l1->l_proc == p2) {
l2->l_flag |= (l1->l_flag & (LW_WEXIT | LW_WREBOOT | LW_WCORE));
} else {
l2->l_flag |= (l1->l_flag & LW_WREBOOT);
}
l2->l_sigstk = *sigstk;
l2->l_sigmask = *sigmask;
TAILQ_INIT(&l2->l_sigpend.sp_info);
sigemptyset(&l2->l_sigpend.sp_set);
LIST_INSERT_HEAD(&p2->p_lwps, l2, l_sibling);
p2->p_nlwps++;
p2->p_nrlwps++;
KASSERT(l2->l_affinity == NULL);
if (l1->l_affinity) {
lwp_lock(l1);
if (l1->l_affinity) {
kcpuset_use(l1->l_affinity);
l2->l_affinity = l1->l_affinity;
}
lwp_unlock(l1);
}
if ((l2->l_flag & LW_USERRET) != 0) {
lwp_need_userret(l2);
}
mutex_exit(p2->p_lock);
SDT_PROBE(proc, kernel, , lwp__create, l2, 0, 0, 0, 0);
mutex_enter(&proc_lock);
LIST_INSERT_HEAD(&alllwp, l2, l_list);
l2->l_psid = l1->l_psid;
mutex_exit(&proc_lock);
SYSCALL_TIME_LWP_INIT(l2);
if (p2->p_emul->e_lwp_fork)
(*p2->p_emul->e_lwp_fork)(l1, l2);
return (0);
}
void
lwp_start(lwp_t *l, int flags)
{
proc_t *p = l->l_proc;
mutex_enter(p->p_lock);
lwp_lock(l);
KASSERT(l->l_stat == LSIDL);
if ((flags & LWP_SUSPENDED) != 0) {
l->l_flag |= LW_WSUSPEND;
lwp_need_userret(l);
}
if (p->p_stat == SSTOP || (p->p_sflag & PS_STOPPING) != 0) {
KASSERT(l->l_wchan == NULL);
l->l_stat = LSSTOP;
p->p_nrlwps--;
lwp_unlock(l);
} else {
setrunnable(l);
}
mutex_exit(p->p_lock);
}
void
lwp_startup(struct lwp *prev, struct lwp *new_lwp)
{
kmutex_t *lock;
KASSERTMSG(new_lwp == curlwp, "l %p curlwp %p prevlwp %p", new_lwp, curlwp, prev);
KASSERT(kpreempt_disabled());
KASSERT(prev != NULL);
KASSERT((prev->l_pflag & LP_RUNNING) != 0);
KASSERT(curcpu()->ci_mtx_count == -2);
lock = prev->l_mutex;
if (__predict_false(prev->l_stat == LSZOMB)) {
atomic_store_release(&prev->l_pflag,
prev->l_pflag & ~LP_RUNNING);
} else {
prev->l_pflag &= ~LP_RUNNING;
}
mutex_spin_exit(lock);
curcpu()->ci_mtx_count = 0;
if (__predict_true(new_lwp->l_proc->p_vmspace)) {
pmap_activate(new_lwp);
}
spl0();
LOCKDEBUG_BARRIER(NULL, 0);
SDT_PROBE(proc, kernel, , lwp__start, new_lwp, 0, 0, 0, 0);
if (__predict_false((new_lwp->l_pflag & LP_MPSAFE) == 0)) {
KERNEL_LOCK(1, new_lwp);
}
}
void
lwp_exit(struct lwp *l)
{
struct proc *p = l->l_proc;
struct lwp *l2;
bool current;
current = (l == curlwp);
KASSERT(current || l->l_stat == LSIDL);
KASSERT(current || l->l_target_cpu == NULL);
KASSERT(p == curproc);
SDT_PROBE(proc, kernel, , lwp__exit, l, 0, 0, 0, 0);
LOCKDEBUG_BARRIER(NULL, 0);
KASSERTMSG(curcpu()->ci_biglock_count == 0, "kernel_lock leaked");
mutex_enter(p->p_lock);
if (p->p_nlwps - p->p_nzlwps == 1) {
KASSERT(current == true);
KASSERT(p != &proc0);
exit1(l, 0, 0);
}
p->p_nzlwps++;
lwp_thread_cleanup(l);
if (p->p_emul->e_lwp_exit)
(*p->p_emul->e_lwp_exit)(l);
fd_free();
fstrans_lwp_dtor(l);
lwp_finispecific(l);
kauth_cred_free(l->l_cred);
callout_destroy(&l->l_timeout_ch);
mutex_enter(&proc_lock);
if ((p->p_slflag & (PSL_TRACED|PSL_TRACELWP_EXIT)) ==
(PSL_TRACED|PSL_TRACELWP_EXIT)) {
mutex_enter(p->p_lock);
if (ISSET(p->p_sflag, PS_WEXIT)) {
mutex_exit(p->p_lock);
} else {
eventswitch(TRAP_LWP, PTRACE_LWP_EXIT, l->l_lid);
mutex_enter(&proc_lock);
}
}
LIST_REMOVE(l, l_list);
mutex_exit(&proc_lock);
mutex_enter(p->p_lock);
for (;;) {
if (lwp_drainrefs(l))
continue;
if ((l->l_prflag & LPR_DETACHED) != 0) {
if ((l2 = p->p_zomblwp) != NULL) {
p->p_zomblwp = NULL;
lwp_free(l2, false, false);
mutex_enter(p->p_lock);
continue;
}
p->p_zomblwp = l;
}
break;
}
if ((l->l_flag & LW_PENDSIG) != 0 &&
firstsig(&p->p_sigpend.sp_set) != 0) {
LIST_FOREACH(l2, &p->p_lwps, l_sibling) {
lwp_lock(l2);
signotify(l2);
lwp_unlock(l2);
}
}
pcu_discard_all(l);
lwp_lock(l);
l->l_stat = LSZOMB;
if (l->l_name != NULL) {
strcpy(l->l_name, "(zombie)");
}
lwp_unlock(l);
p->p_nrlwps--;
if (l->l_lwpctl != NULL)
l->l_lwpctl->lc_curcpu = LWPCTL_CPU_EXITED;
mutex_exit(p->p_lock);
cv_broadcast(&p->p_lwpcv);
cpu_lwp_free(l, 0);
if (current) {
lwp_lock(l);
spc_lock(l->l_cpu);
mi_switch(l);
panic("lwp_exit");
}
}
void
lwp_free(struct lwp *l, bool recycle, bool last)
{
struct proc *p = l->l_proc;
struct rusage *ru;
ksiginfoq_t kq;
KASSERT(l != curlwp);
KASSERT(last || mutex_owned(p->p_lock));
if (p != &proc0 && p->p_nlwps != 1)
(void)chglwpcnt(kauth_cred_getuid(p->p_cred), -1);
while (__predict_false((atomic_load_acquire(&l->l_pflag) & LP_RUNNING)
!= 0)) {
SPINLOCK_BACKOFF_HOOK;
}
lwp_lock(l);
l->l_stat = LSIDL;
l->l_cpu = curcpu();
lwp_unlock_to(l, l->l_cpu->ci_schedstate.spc_lwplock);
if (!last) {
bintime_add(&p->p_rtime, &l->l_rtime);
p->p_pctcpu += l->l_pctcpu;
ru = &p->p_stats->p_ru;
ruadd(ru, &l->l_ru);
LIST_REMOVE(l, l_sibling);
p->p_nlwps--;
p->p_nzlwps--;
if ((l->l_prflag & LPR_DETACHED) != 0)
p->p_ndlwps--;
mutex_exit(p->p_lock);
cv_broadcast(&p->p_lwpcv);
mutex_enter(&proc_lock);
proc_free_lwpid(p, l->l_lid);
mutex_exit(&proc_lock);
}
ksiginfo_queue_init(&kq);
sigclear(&l->l_sigpend, NULL, &kq);
ksiginfo_queue_drain(&kq);
cv_destroy(&l->l_sigcv);
cv_destroy(&l->l_waitcv);
if (l->l_lwpctl) {
lwp_ctl_free(l);
}
if (l->l_affinity) {
kcpuset_unuse(l->l_affinity, NULL);
l->l_affinity = NULL;
}
if (l->l_name != NULL)
kmem_free(l->l_name, MAXCOMLEN);
kmsan_lwp_free(l);
kcov_lwp_free(l);
cpu_lwp_free2(l);
uvm_lwp_exit(l);
KASSERT(SLIST_EMPTY(&l->l_pi_lenders));
KASSERT(l->l_inheritedprio == -1);
KASSERT(l->l_blcnt == 0);
kdtrace_thread_dtor(NULL, l);
if (!recycle)
pool_cache_put(lwp_cache, l);
}
void
lwp_migrate(lwp_t *l, struct cpu_info *tci)
{
struct schedstate_percpu *tspc;
int lstat = l->l_stat;
KASSERT(lwp_locked(l, NULL));
KASSERT(tci != NULL);
if ((l->l_pflag & LP_RUNNING) != 0) {
lstat = LSONPROC;
}
if (l->l_target_cpu != NULL) {
l->l_target_cpu = tci;
lwp_unlock(l);
return;
}
if (l->l_cpu == tci) {
lwp_unlock(l);
return;
}
KASSERT(l->l_target_cpu == NULL);
tspc = &tci->ci_schedstate;
switch (lstat) {
case LSRUN:
l->l_target_cpu = tci;
break;
case LSSLEEP:
l->l_cpu = tci;
break;
case LSIDL:
case LSSTOP:
case LSSUSPENDED:
l->l_cpu = tci;
if (l->l_wchan == NULL) {
lwp_unlock_to(l, tspc->spc_lwplock);
return;
}
break;
case LSONPROC:
l->l_target_cpu = tci;
spc_lock(l->l_cpu);
sched_resched_cpu(l->l_cpu, PRI_USER_RT, true);
break;
}
lwp_unlock(l);
}
#define lwp_find_exclude(l) \
((l)->l_stat == LSIDL || (l)->l_stat == LSZOMB)
struct lwp *
lwp_find2(pid_t pid, lwpid_t lid)
{
proc_t *p;
lwp_t *l;
if (lid == 0) {
switch (pid) {
case -1:
return NULL;
case 0:
p = curproc;
mutex_enter(p->p_lock);
break;
default:
mutex_enter(&proc_lock);
p = proc_find(pid);
if (__predict_false(p == NULL)) {
mutex_exit(&proc_lock);
return NULL;
}
mutex_enter(p->p_lock);
mutex_exit(&proc_lock);
break;
}
LIST_FOREACH(l, &p->p_lwps, l_sibling) {
if (__predict_true(!lwp_find_exclude(l)))
break;
}
goto out;
}
l = proc_find_lwp_acquire_proc(lid, &p);
if (l == NULL)
return NULL;
KASSERT(p != NULL);
KASSERT(mutex_owned(p->p_lock));
if (__predict_false(lwp_find_exclude(l))) {
l = NULL;
goto out;
}
switch (pid) {
case -1:
break;
case 0:
if (p != curproc)
l = NULL;
break;
default:
if (p->p_pid != pid)
l = NULL;
break;
}
out:
if (__predict_false(l == NULL)) {
mutex_exit(p->p_lock);
}
return l;
}
struct lwp *
lwp_find(struct proc *p, lwpid_t id)
{
struct lwp *l;
KASSERT(mutex_owned(p->p_lock));
l = proc_find_lwp(p, id);
KASSERT(l == NULL || l->l_lid == id);
if (__predict_false(l != NULL && lwp_find_exclude(l)))
l = NULL;
return l;
}
int
lwp_locked(struct lwp *l, kmutex_t *mtx)
{
kmutex_t *cur = l->l_mutex;
return mutex_owned(cur) && (mtx == cur || mtx == NULL);
}
kmutex_t *
lwp_setlock(struct lwp *l, kmutex_t *mtx)
{
kmutex_t *oldmtx = l->l_mutex;
KASSERT(mutex_owned(oldmtx));
atomic_store_release(&l->l_mutex, mtx);
return oldmtx;
}
void
lwp_unlock_to(struct lwp *l, kmutex_t *mtx)
{
kmutex_t *old;
KASSERT(lwp_locked(l, NULL));
old = l->l_mutex;
atomic_store_release(&l->l_mutex, mtx);
mutex_spin_exit(old);
}
int
lwp_trylock(struct lwp *l)
{
kmutex_t *old;
for (;;) {
if (!mutex_tryenter(old = atomic_load_consume(&l->l_mutex)))
return 0;
if (__predict_true(atomic_load_relaxed(&l->l_mutex) == old))
return 1;
mutex_spin_exit(old);
}
}
void
lwp_unsleep(lwp_t *l, bool unlock)
{
KASSERT(mutex_owned(l->l_mutex));
(*l->l_syncobj->sobj_unsleep)(l, unlock);
}
void
lwp_lock(lwp_t *l)
{
kmutex_t *old = atomic_load_consume(&l->l_mutex);
mutex_spin_enter(old);
while (__predict_false(atomic_load_relaxed(&l->l_mutex) != old)) {
mutex_spin_exit(old);
old = atomic_load_consume(&l->l_mutex);
mutex_spin_enter(old);
}
}
void
lwp_unlock(lwp_t *l)
{
mutex_spin_exit(l->l_mutex);
}
void
lwp_changepri(lwp_t *l, pri_t pri)
{
KASSERT(mutex_owned(l->l_mutex));
if (l->l_priority == pri)
return;
(*l->l_syncobj->sobj_changepri)(l, pri);
KASSERT(l->l_priority == pri);
}
void
lwp_lendpri(lwp_t *l, pri_t pri)
{
KASSERT(mutex_owned(l->l_mutex));
(*l->l_syncobj->sobj_lendpri)(l, pri);
KASSERT(l->l_inheritedprio == pri);
}
pri_t
lwp_eprio(lwp_t *l)
{
pri_t pri = l->l_priority;
KASSERT(mutex_owned(l->l_mutex));
if (pri <= MAXPRI_USER && l->l_boostpri > MAXPRI_USER)
pri = (pri >> 1) + l->l_boostpri;
return MAX(l->l_auxprio, pri);
}
void
lwp_userret(struct lwp *l)
{
struct proc *p;
int sig, f;
KASSERT(l == curlwp);
KASSERT(l->l_stat == LSONPROC);
p = l->l_proc;
for (;;) {
preempt_point();
if ((f = atomic_load_relaxed(&l->l_flag) & LW_USERRET) == 0) {
return;
}
if ((f & LW_CACHECRED) != 0) {
kauth_cred_t oc = l->l_cred;
mutex_enter(p->p_lock);
l->l_cred = kauth_cred_hold(p->p_cred);
lwp_lock(l);
l->l_flag &= ~LW_CACHECRED;
lwp_unlock(l);
mutex_exit(p->p_lock);
kauth_cred_free(oc);
}
if ((f & (LW_PENDSIG | LW_WCORE | LW_WEXIT)) == LW_PENDSIG) {
mutex_enter(p->p_lock);
while ((sig = issignal(l)) != 0)
postsig(sig);
mutex_exit(p->p_lock);
continue;
}
if ((f & LW_WSUSPEND) != 0) {
pcu_save_all(l);
mutex_enter(p->p_lock);
p->p_nrlwps--;
lwp_lock(l);
l->l_stat = LSSUSPENDED;
lwp_unlock(l);
mutex_exit(p->p_lock);
cv_broadcast(&p->p_lwpcv);
lwp_lock(l);
spc_lock(l->l_cpu);
mi_switch(l);
continue;
}
if ((f & LW_WEXIT) != 0) {
lwp_exit(l);
KASSERT(0);
}
if ((f & LW_LWPCTL) != 0) {
lwp_lock(l);
KASSERT(kpreempt_disabled());
l->l_lwpctl->lc_curcpu = (int)cpu_index(l->l_cpu);
l->l_lwpctl->lc_pctr++;
l->l_flag &= ~LW_LWPCTL;
lwp_unlock(l);
continue;
}
}
}
void
lwp_need_userret(struct lwp *l)
{
KASSERT(!cpu_intr_p());
KASSERT(lwp_locked(l, NULL) || l->l_stat == LSIDL);
if (l->l_stat == LSONPROC && l != curlwp) {
membar_producer();
cpu_signotify(l);
}
}
void
lwp_addref(struct lwp *l)
{
KASSERT(mutex_owned(l->l_proc->p_lock));
KASSERT(l->l_stat != LSZOMB);
l->l_refcnt++;
}
void
lwp_delref(struct lwp *l)
{
struct proc *p = l->l_proc;
mutex_enter(p->p_lock);
lwp_delref2(l);
mutex_exit(p->p_lock);
}
void
lwp_delref2(struct lwp *l)
{
struct proc *p = l->l_proc;
KASSERT(mutex_owned(p->p_lock));
KASSERT(l->l_stat != LSZOMB);
KASSERT(l->l_refcnt > 0);
if (--l->l_refcnt == 0)
cv_broadcast(&p->p_lwpcv);
}
bool
lwp_drainrefs(struct lwp *l)
{
struct proc *p = l->l_proc;
bool rv = false;
KASSERT(mutex_owned(p->p_lock));
l->l_prflag |= LPR_DRAINING;
while (l->l_refcnt > 0) {
rv = true;
cv_wait(&p->p_lwpcv, p->p_lock);
}
return rv;
}
bool
lwp_alive(lwp_t *l)
{
KASSERT(mutex_owned(l->l_proc->p_lock));
switch (l->l_stat) {
case LSSLEEP:
case LSRUN:
case LSONPROC:
case LSSTOP:
case LSSUSPENDED:
return true;
default:
return false;
}
}
lwp_t *
lwp_find_first(proc_t *p)
{
lwp_t *l;
KASSERT(mutex_owned(p->p_lock));
LIST_FOREACH(l, &p->p_lwps, l_sibling) {
if (lwp_alive(l)) {
return l;
}
}
return NULL;
}
int
lwp_ctl_alloc(vaddr_t *uaddr)
{
lcproc_t *lp;
u_int bit, i, offset;
struct uvm_object *uao;
int error;
lcpage_t *lcp;
proc_t *p;
lwp_t *l;
l = curlwp;
p = l->l_proc;
if (p->p_lflag & PL_PPWAIT)
return SET_ERROR(EBUSY);
if (l->l_lcpage != NULL) {
lcp = l->l_lcpage;
*uaddr = lcp->lcp_uaddr + (vaddr_t)l->l_lwpctl - lcp->lcp_kaddr;
return 0;
}
if ((lp = p->p_lwpctl) == NULL) {
lp = kmem_alloc(sizeof(*lp), KM_SLEEP);
mutex_init(&lp->lp_lock, MUTEX_DEFAULT, IPL_NONE);
lp->lp_uao = NULL;
TAILQ_INIT(&lp->lp_pages);
mutex_enter(p->p_lock);
if (p->p_lwpctl == NULL) {
p->p_lwpctl = lp;
mutex_exit(p->p_lock);
} else {
mutex_exit(p->p_lock);
mutex_destroy(&lp->lp_lock);
kmem_free(lp, sizeof(*lp));
lp = p->p_lwpctl;
}
}
mutex_enter(&lp->lp_lock);
if (lp->lp_uao == NULL) {
lp->lp_uao = uao_create(LWPCTL_UAREA_SZ, 0);
lp->lp_cur = 0;
lp->lp_max = LWPCTL_UAREA_SZ;
lp->lp_uva = p->p_emul->e_vm_default_addr(p,
(vaddr_t)p->p_vmspace->vm_daddr, LWPCTL_UAREA_SZ,
p->p_vmspace->vm_map.flags & VM_MAP_TOPDOWN);
error = uvm_map(&p->p_vmspace->vm_map, &lp->lp_uva,
LWPCTL_UAREA_SZ, lp->lp_uao, 0, 0, UVM_MAPFLAG(UVM_PROT_RW,
UVM_PROT_RW, UVM_INH_NONE, UVM_ADV_NORMAL, 0));
if (error != 0) {
uao_detach(lp->lp_uao);
lp->lp_uao = NULL;
mutex_exit(&lp->lp_lock);
return error;
}
}
TAILQ_FOREACH(lcp, &lp->lp_pages, lcp_chain) {
if (lcp->lcp_nfree != 0)
break;
}
if (lcp == NULL) {
if (lp->lp_cur == lp->lp_max) {
mutex_exit(&lp->lp_lock);
return SET_ERROR(ENOMEM);
}
lcp = kmem_alloc(LWPCTL_LCPAGE_SZ, KM_SLEEP);
uao = lp->lp_uao;
(*uao->pgops->pgo_reference)(uao);
lcp->lcp_kaddr = vm_map_min(kernel_map);
error = uvm_map(kernel_map, &lcp->lcp_kaddr, PAGE_SIZE,
uao, lp->lp_cur, PAGE_SIZE,
UVM_MAPFLAG(UVM_PROT_RW, UVM_PROT_RW,
UVM_INH_NONE, UVM_ADV_RANDOM, 0));
if (error != 0) {
mutex_exit(&lp->lp_lock);
kmem_free(lcp, LWPCTL_LCPAGE_SZ);
(*uao->pgops->pgo_detach)(uao);
return error;
}
error = uvm_map_pageable(kernel_map, lcp->lcp_kaddr,
lcp->lcp_kaddr + PAGE_SIZE, FALSE, 0);
if (error != 0) {
mutex_exit(&lp->lp_lock);
uvm_unmap(kernel_map, lcp->lcp_kaddr,
lcp->lcp_kaddr + PAGE_SIZE);
kmem_free(lcp, LWPCTL_LCPAGE_SZ);
return error;
}
lcp->lcp_uaddr = lp->lp_uva + lp->lp_cur;
lp->lp_cur += PAGE_SIZE;
lcp->lcp_nfree = LWPCTL_PER_PAGE;
lcp->lcp_rotor = 0;
memset(lcp->lcp_bitmap, 0xff, LWPCTL_BITMAP_SZ);
TAILQ_INSERT_HEAD(&lp->lp_pages, lcp, lcp_chain);
}
for (i = lcp->lcp_rotor; lcp->lcp_bitmap[i] == 0;) {
if (++i >= LWPCTL_BITMAP_ENTRIES)
i = 0;
}
bit = ffs(lcp->lcp_bitmap[i]) - 1;
lcp->lcp_bitmap[i] ^= (1U << bit);
lcp->lcp_rotor = i;
lcp->lcp_nfree--;
l->l_lcpage = lcp;
offset = (i << 5) + bit;
l->l_lwpctl = (lwpctl_t *)lcp->lcp_kaddr + offset;
*uaddr = lcp->lcp_uaddr + offset * sizeof(lwpctl_t);
mutex_exit(&lp->lp_lock);
KPREEMPT_DISABLE(l);
l->l_lwpctl->lc_curcpu = (int)cpu_index(curcpu());
KPREEMPT_ENABLE(l);
return 0;
}
void
lwp_ctl_free(lwp_t *l)
{
struct proc *p = l->l_proc;
lcproc_t *lp;
lcpage_t *lcp;
u_int map, offset;
if (p->p_lflag & PL_PPWAIT) {
l->l_lwpctl = NULL;
return;
}
lp = p->p_lwpctl;
KASSERT(lp != NULL);
lcp = l->l_lcpage;
offset = (u_int)((lwpctl_t *)l->l_lwpctl - (lwpctl_t *)lcp->lcp_kaddr);
KASSERT(offset < LWPCTL_PER_PAGE);
mutex_enter(&lp->lp_lock);
lcp->lcp_nfree++;
map = offset >> 5;
lcp->lcp_bitmap[map] |= (1U << (offset & 31));
if (lcp->lcp_bitmap[lcp->lcp_rotor] == 0)
lcp->lcp_rotor = map;
if (TAILQ_FIRST(&lp->lp_pages)->lcp_nfree == 0) {
TAILQ_REMOVE(&lp->lp_pages, lcp, lcp_chain);
TAILQ_INSERT_HEAD(&lp->lp_pages, lcp, lcp_chain);
}
mutex_exit(&lp->lp_lock);
}
void
lwp_ctl_exit(void)
{
lcpage_t *lcp, *next;
lcproc_t *lp;
proc_t *p;
lwp_t *l;
l = curlwp;
l->l_lwpctl = NULL;
l->l_lcpage = NULL;
p = l->l_proc;
lp = p->p_lwpctl;
KASSERT(lp != NULL);
KASSERT(p->p_nlwps == 1);
for (lcp = TAILQ_FIRST(&lp->lp_pages); lcp != NULL; lcp = next) {
next = TAILQ_NEXT(lcp, lcp_chain);
uvm_unmap(kernel_map, lcp->lcp_kaddr,
lcp->lcp_kaddr + PAGE_SIZE);
kmem_free(lcp, LWPCTL_LCPAGE_SZ);
}
if (lp->lp_uao != NULL) {
uvm_unmap(&p->p_vmspace->vm_map, lp->lp_uva,
lp->lp_uva + LWPCTL_UAREA_SZ);
}
mutex_destroy(&lp->lp_lock);
kmem_free(lp, sizeof(*lp));
p->p_lwpctl = NULL;
}
long
lwp_pctr(void)
{
unsigned long pctr;
__insn_barrier();
pctr = curlwp->l_ru.ru_nvcsw;
pctr += curlwp->l_ru.ru_nivcsw;
__insn_barrier();
return pctr;
}
int
lwp_setprivate(struct lwp *l, void *ptr)
{
int error = 0;
l->l_private = ptr;
#ifdef __HAVE_CPU_LWP_SETPRIVATE
error = cpu_lwp_setprivate(l, ptr);
#endif
return error;
}
void
lwp_thread_cleanup(struct lwp *l)
{
KASSERT(mutex_owned(l->l_proc->p_lock));
mutex_exit(l->l_proc->p_lock);
if (__predict_false(l->l_robust_head != 0)) {
futex_release_all_lwp(l);
}
}
#if defined(DDB)
#include <machine/pcb.h>
void
lwp_whatis(uintptr_t addr, void (*pr)(const char *, ...))
{
lwp_t *l;
LIST_FOREACH(l, &alllwp, l_list) {
uintptr_t stack = (uintptr_t)KSTACK_LOWEST_ADDR(l);
if (addr < stack || stack + KSTACK_SIZE <= addr) {
continue;
}
(*pr)("%p is %p+%zu, LWP %p's stack\n",
(void *)addr, (void *)stack,
(size_t)(addr - stack), l);
}
}
#endif