Threads
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:
parent
2372806862
commit
f4d7f9100d
19 changed files with 1300 additions and 248 deletions
|
|
@ -636,6 +636,11 @@ oom:
|
|||
return 0;
|
||||
}
|
||||
|
||||
// CR3 loads per processor, and what each has loaded; see "Pages another
|
||||
// processor may still have in its TLB" below.
|
||||
static volatile uint64 cr3_loads[NCPU];
|
||||
static pml4e_t *volatile cpu_pgdir[NCPU];
|
||||
|
||||
void
|
||||
switchuvm(struct proc *p)
|
||||
{
|
||||
|
|
@ -650,6 +655,8 @@ switchuvm(struct proc *p)
|
|||
// every process that is not hosting a driver.
|
||||
ioperm_switch(p);
|
||||
|
||||
cr3_loads[cpunum()]++;
|
||||
cpu_pgdir[cpunum()] = p->pgdir;
|
||||
lcr3(v2p(p->pgdir));
|
||||
popcli();
|
||||
}
|
||||
|
|
@ -695,9 +702,109 @@ walkpgdir(pml4e_t *pml4, const void *va, int alloc)
|
|||
return (pte_t*)&pgtab[PTX(va)];
|
||||
}
|
||||
|
||||
// --- Pages another processor may still have in its TLB -----------------
|
||||
//
|
||||
// Threads put one address space on several processors at once, and there is
|
||||
// no shootdown IPI: unmapping a page here invalidates it in this TLB only.
|
||||
// Freed at once, it could be handed to someone else while a thread on another
|
||||
// CPU still reaches it through a stale entry -- a write into a stranger's
|
||||
// memory with no fault to show for it.
|
||||
//
|
||||
// So a page unmapped from a shared address space waits. Every CR3 load is
|
||||
// counted per processor, and a batch of pages is freed once every processor
|
||||
// has reloaded CR3 since the batch was closed, or is not running a user
|
||||
// address space at all. A processor reloads at every switch, so a batch waits
|
||||
// a scheduling slice or two.
|
||||
#define DEFER_MAX 2048
|
||||
struct defer_batch {
|
||||
int n;
|
||||
uint64 snap[NCPU];
|
||||
uint64 pa[DEFER_MAX];
|
||||
};
|
||||
static struct defer_batch defer_q[2];
|
||||
static int defer_open; // which of the two is filling
|
||||
static int defer_closed_valid; // the other one is waiting to be freed
|
||||
static struct spinlock deferlock;
|
||||
static int defer_init;
|
||||
|
||||
static int
|
||||
defer_ready(struct defer_batch *b)
|
||||
{
|
||||
for (int c = 0; c < ncpu; c++)
|
||||
if (cr3_loads[c] == b->snap[c] && cpu_pgdir[c] != 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// With deferlock. Frees the closed batch if it can, then closes the open one.
|
||||
static void
|
||||
defer_reclaim(void)
|
||||
{
|
||||
struct defer_batch *closed = &defer_q[defer_open ^ 1];
|
||||
if (defer_closed_valid && defer_ready(closed)) {
|
||||
for (int i = 0; i < closed->n; i++)
|
||||
kfree((char*)P2V(closed->pa[i]));
|
||||
closed->n = 0;
|
||||
defer_closed_valid = 0;
|
||||
}
|
||||
struct defer_batch *open = &defer_q[defer_open];
|
||||
if (!defer_closed_valid && open->n > 0) {
|
||||
for (int c = 0; c < ncpu; c++)
|
||||
open->snap[c] = cr3_loads[c];
|
||||
defer_closed_valid = 1;
|
||||
defer_open ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vm_defer_tick(void)
|
||||
{
|
||||
if (!defer_init)
|
||||
return;
|
||||
acquire(&deferlock);
|
||||
defer_reclaim();
|
||||
release(&deferlock);
|
||||
}
|
||||
|
||||
static void
|
||||
defer_free(uint64 pa)
|
||||
{
|
||||
if (!defer_init) {
|
||||
initlock(&deferlock, "vmdefer");
|
||||
defer_init = 1;
|
||||
}
|
||||
for (;;) {
|
||||
acquire(&deferlock);
|
||||
struct defer_batch *open = &defer_q[defer_open];
|
||||
if (open->n == DEFER_MAX)
|
||||
defer_reclaim();
|
||||
open = &defer_q[defer_open];
|
||||
if (open->n < DEFER_MAX) {
|
||||
open->pa[open->n++] = pa;
|
||||
release(&deferlock);
|
||||
return;
|
||||
}
|
||||
release(&deferlock);
|
||||
yield(); // both batches full: let the CPUs switch
|
||||
}
|
||||
}
|
||||
|
||||
// Free a user page just unmapped from pgdir: now, or once no TLB can hold it.
|
||||
static void
|
||||
user_page_free(pml4e_t *pgdir, uint64 pa)
|
||||
{
|
||||
struct proc *p = myproc();
|
||||
if (p && p->pgdir == pgdir && mm_shared(p->mm))
|
||||
defer_free(pa);
|
||||
else
|
||||
kfree((char*)P2V(pa));
|
||||
}
|
||||
|
||||
void
|
||||
switchkvm(void)
|
||||
{
|
||||
cr3_loads[cpunum()]++;
|
||||
cpu_pgdir[cpunum()] = 0;
|
||||
lcr3(v2p(kpml4));
|
||||
}
|
||||
|
||||
|
|
@ -733,9 +840,9 @@ vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length)
|
|||
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
||||
if (pte && (*pte & PTE_P)) {
|
||||
uint64 pa = PTE_ADDR(*pte);
|
||||
kfree((char*)P2V(pa));
|
||||
*pte = 0;
|
||||
invlpg((void*)a);
|
||||
user_page_free(pgdir, pa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -883,11 +990,10 @@ deallocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz)
|
|||
pa = PTE_ADDR(*pte);
|
||||
if(pa == 0)
|
||||
panic("kfree");
|
||||
if(pa < PHYSTOP) {
|
||||
char *v = P2V(pa);
|
||||
kfree(v);
|
||||
}
|
||||
*pte = 0;
|
||||
invlpg((void*)a);
|
||||
if(pa < PHYSTOP)
|
||||
user_page_free(pgdir, pa);
|
||||
}
|
||||
}
|
||||
return newsz;
|
||||
|
|
|
|||
Loading…
Reference in a new issue