A thread is a struct proc that shares three counted objects with others of
its group: the address space (struct mm: page tables, heap top, VMAs and a
lock for changing them), the descriptor table, and the signal dispositions.
clone() shares what its flags say and fork() copies.

- clone(): pthread_create's shape (VM, FILES, SIGHAND, THREAD, SETTLS,
  PARENT_SETTID, CHILD_CLEARTID) and posix_spawn's (VM, VFORK, a stack).
- futex: WAIT, WAIT_BITSET with timeouts, WAKE, REQUEUE, CMP_REQUEUE, keyed
  by the word's kernel address so shared pages meet. set_tid_address keeps
  the word that exit zeroes and wakes, for pthread_join.
- exit ends a thread; exit_group, a fatal signal, or the leader exiting ends
  them all. reapd frees exited threads; the leader is reaped by its parent
  once the rest are gone. getpid is the group, gettid the thread.
- Stops and resumes reach every thread; other process signals the leader.
- exec kills the other threads and unshares the dispositions.
- No TLB shootdown, so pages unmapped from a shared address space are freed
  only once every CPU has reloaded CR3 or is idle.
- procfs: /proc lists processes, task/ lists threads, comm, Threads:, Tgid.
- The console honours VMIN 0 / VTIME (btop's input drain hung on it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-25 03:03:50 +01:00
commit f4d7f9100d
19 changed files with 1300 additions and 248 deletions

View file

@ -24,13 +24,27 @@ addr_t sys_wait(void) { return wait(); }
// wait() reports back through WEXITSTATUS.
addr_t
sys_exit(void)
{
int code = 0;
argint(0, &code);
// Linux's exit is one thread's: pthread_exit, and a thread returning
// from its start function. The program ends with exit_group -- or here,
// when this is the last thread, or the native ABI, which has no threads.
struct proc *p = myproc();
if (p->syscall_map && p->tgid != p->pid && proc_thread_count(p->tgid) > 1)
thread_exit();
exit(W_EXITCODE(code, 0));
return 0;
}
addr_t
sys_exit_group(void)
{
int code = 0;
argint(0, &code);
exit(W_EXITCODE(code, 0));
return 0;
}
addr_t sys_exit_group(void) { return sys_exit(); }
addr_t
@ -62,9 +76,10 @@ sys_kill(void)
// Defined with getrusage() further down; wait4() needs it too.
static void rusage_fill(struct rusage *ru, uint64 ut, uint64 st);
addr_t sys_getpid(void) { return myproc()->pid; }
addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->pid : 1; }
addr_t sys_gettid(void) { return myproc()->pid; } // single-threaded: tid == pid
// A process is its thread group: getpid is the group's id, gettid the thread's.
addr_t sys_getpid(void) { return myproc()->tgid; }
addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->tgid : 1; }
addr_t sys_gettid(void) { return myproc()->pid; }
addr_t
sys_waitpid(void)
@ -122,11 +137,13 @@ sys_wait4(void)
addr_t
sys_clone(void)
{
int flags;
argint(0, &flags);
if (flags & (CLONE_VM | CLONE_THREAD | CLONE_SIGHAND))
return -ENOSYS;
return fork();
uint64 flags, stack, ptid, ctid, tls;
argaddr(0, &flags);
argaddr(1, &stack);
argaddr(2, &ptid);
argaddr(3, &ctid);
argaddr(4, &tls);
return clone_proc(flags, stack, ptid, ctid, tls);
}
addr_t
@ -424,10 +441,11 @@ addr_t sys_tgkill(void)
{
int tgid, tid, sig;
argint(0, &tgid); argint(1, &tid); argint(2, &sig);
// In our single-threaded model tgid == tid == pid.
if (tgid <= 0) return 0;
if (sig == 0) return proc_exists(tgid) ? 0 : -ESRCH;
return sigsend(tgid, sig) < 0 ? -ESRCH : 0;
// To one thread: musl's pthread_kill, and raise(), which is tgkill on
// the calling thread.
if (tid <= 0) return -EINVAL;
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
}
addr_t sys_tkill(void)