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

@ -45,8 +45,8 @@ static struct vma *
vma_free_slot(struct proc *p)
{
for (int i = 0; i < NVMA; i++)
if (!p->vma[i].used)
return &p->vma[i];
if (!p->mm->vma[i].used)
return &p->mm->vma[i];
return 0;
}
@ -62,7 +62,7 @@ static int
vma_note_anon(struct proc *p, uint64 va, uint64 len)
{
for (int i = 0; i < NVMA; i++) {
struct vma *v = &p->vma[i];
struct vma *v = &p->mm->vma[i];
if (vma_anon(v) && v->addr + v->length == va) {
v->length += len;
return 0;
@ -81,7 +81,7 @@ static int
range_free(struct proc *p, uint64 va, uint64 len)
{
for (int i = 0; i < NVMA; i++) {
struct vma *v = &p->vma[i];
struct vma *v = &p->mm->vma[i];
if (v->used && va < v->addr + v->length && v->addr < va + len)
return 0;
}
@ -104,21 +104,21 @@ range_free(struct proc *p, uint64 va, uint64 len)
static uint64
mmap_va(struct proc *p, uint64 len, int mergeable)
{
if (p->mmap_next == 0)
p->mmap_next = 0x40000000;
if (p->mm->mmap_next == 0)
p->mm->mmap_next = 0x40000000;
if (mergeable) {
for (int i = 0; i < NVMA; i++) {
struct vma *v = &p->vma[i];
struct vma *v = &p->mm->vma[i];
uint64 e = v->addr + v->length;
if (vma_anon(v) && e >= 0x40000000 && e + len <= p->mmap_next &&
if (vma_anon(v) && e >= 0x40000000 && e + len <= p->mm->mmap_next &&
range_free(p, e, len))
return e;
}
}
uint64 va = p->mmap_next;
uint64 va = p->mm->mmap_next;
if (bad_user_range(va, len))
return 0;
p->mmap_next += len;
p->mm->mmap_next += len;
return va;
}
@ -138,7 +138,7 @@ vma_cover_anon(struct proc *p, uint64 va, uint64 len)
uint64 next = end;
int inside = -1;
for (int i = 0; i < NVMA; i++) {
struct vma *v = &p->vma[i];
struct vma *v = &p->mm->vma[i];
if (!v->used)
continue;
if (v->addr <= cur && cur < v->addr + v->length)
@ -147,7 +147,7 @@ vma_cover_anon(struct proc *p, uint64 va, uint64 len)
next = v->addr;
}
if (inside >= 0) {
cur = p->vma[inside].addr + p->vma[inside].length;
cur = p->mm->vma[inside].addr + p->mm->vma[inside].length;
continue;
}
if (vma_note_anon(p, cur, next - cur) < 0)
@ -180,8 +180,8 @@ sys_brk(void)
return myproc()->sz;
}
addr_t
sys_mmap(void)
static addr_t
mmap_locked(void)
{
uint64 addr, length, offset;
int prot, flags, fd;
@ -193,7 +193,7 @@ sys_mmap(void)
return -1;
if (fd >= 0 && fd < NOFILE)
f = myproc()->ofile[fd];
f = myproc()->files->ofile[fd];
length = PGROUNDUP(length);
uint64 va = addr;
@ -425,8 +425,8 @@ sys_mmap(void)
return va;
}
addr_t
sys_munmap(void)
static addr_t
munmap_locked(void)
{
uint64 addr, length;
struct proc *p = myproc();
@ -441,7 +441,7 @@ sys_munmap(void)
// Part of one is refused: unmapping it the anonymous way below would kfree
// pages that are the framebuffer's or another process's.
for (int i = 0; i < NVMA; i++) {
struct vma *v = &p->vma[i];
struct vma *v = &p->mm->vma[i];
if (!v->used || vma_anon(v))
continue;
if (v->addr == addr) {
@ -466,7 +466,7 @@ sys_munmap(void)
// are not there, and the bump allocator never hands the addresses out
// again.
for (int i = 0; i < NVMA; i++) {
struct vma *v = &p->vma[i];
struct vma *v = &p->mm->vma[i];
if (!vma_anon(v))
continue;
uint64 vs = v->addr, ve = v->addr + v->length;
@ -835,8 +835,8 @@ sys_shmget(void)
return -ENOSPC;
}
addr_t
sys_shmat(void)
static addr_t
shmat_locked(void)
{
int id, flags;
addr_t addr;
@ -859,15 +859,15 @@ sys_shmat(void)
struct vma *v = 0;
for (int i = 0; i < NVMA; i++)
if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; }
if (!myproc()->mm->vma[i].used) { v = &myproc()->mm->vma[i]; break; }
if (!v) { shm_detach(id); return -ENOMEM; }
// Ignore the hint and take a fresh range, the same way mmap does.
if (myproc()->mmap_next == 0)
myproc()->mmap_next = 0x40000000;
uint64 va = myproc()->mmap_next;
if (myproc()->mm->mmap_next == 0)
myproc()->mm->mmap_next = 0x40000000;
uint64 va = myproc()->mm->mmap_next;
if (bad_user_range(va, len)) { shm_detach(id); return -ENOMEM; }
myproc()->mmap_next += len;
myproc()->mm->mmap_next += len;
for (int pg = 0; pg < npages; pg++) {
if (mappages(myproc()->pgdir, (void*)(va + (uint64)pg * PGSIZE), PGSIZE,
@ -895,35 +895,36 @@ sys_shmat(void)
//
// Called from exit() and from exec() before the old address space goes away.
void
proc_release_shared(struct proc *p)
mm_release_shared(struct mm *m)
{
for (int i = 0; i < NVMA; i++) {
if (!p->vma[i].used || !p->vma[i].no_free)
if (!m->vma[i].used || !m->vma[i].no_free)
continue;
vm_unmap_norelease(p->pgdir, p->vma[i].addr, p->vma[i].length);
if (p->vma[i].shmid >= 0)
shm_detach(p->vma[i].shmid);
p->vma[i].used = 0;
p->vma[i].no_free = 0;
p->vma[i].shmid = -1;
if (m->pgdir)
vm_unmap_norelease(m->pgdir, m->vma[i].addr, m->vma[i].length);
if (m->vma[i].shmid >= 0)
shm_detach(m->vma[i].shmid);
m->vma[i].used = 0;
m->vma[i].no_free = 0;
m->vma[i].shmid = -1;
}
}
addr_t
sys_shmdt(void)
static addr_t
shmdt_locked(void)
{
addr_t addr;
if (argaddr(0, &addr) < 0)
return -EINVAL;
for (int i = 0; i < NVMA; i++) {
if (myproc()->vma[i].used && myproc()->vma[i].addr == addr &&
myproc()->vma[i].shmid >= 0) {
vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length);
shm_detach(myproc()->vma[i].shmid);
myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0;
myproc()->vma[i].no_free = 0;
myproc()->vma[i].shmid = -1;
if (myproc()->mm->vma[i].used && myproc()->mm->vma[i].addr == addr &&
myproc()->mm->vma[i].shmid >= 0) {
vm_unmap_norelease(myproc()->pgdir, addr, myproc()->mm->vma[i].length);
shm_detach(myproc()->mm->vma[i].shmid);
myproc()->mm->vma[i].used = myproc()->mm->vma[i].addr = myproc()->mm->vma[i].length = 0;
myproc()->mm->vma[i].no_free = 0;
myproc()->mm->vma[i].shmid = -1;
return 0;
}
}
@ -981,3 +982,21 @@ sys_setpriority(void)
return -EINVAL;
return (addr_t)proc_setnice(who, nice);
}
// One mapping change at a time per address space: threads share the VMA table
// and the page tables, and two of them allocating the same missing page-table
// level at once would each install their own and lose the other's mappings.
#define MM_LOCKED(name) \
addr_t \
sys_##name(void) \
{ \
struct mm *m = myproc()->mm; \
mm_lock(m); \
addr_t r = name##_locked(); \
mm_unlock(m); \
return r; \
}
MM_LOCKED(mmap)
MM_LOCKED(munmap)
MM_LOCKED(shmat)
MM_LOCKED(shmdt)