diff --git a/include/core/defs.h b/include/core/defs.h index 2066a9b..482bf73 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -62,6 +62,31 @@ void pciuser_release_all(struct proc*); int pciuser_owner_pid(int idx); int proc_reincarnate(int status); int sig_default_stops(int); +struct mm* mm_alloc(void); +void mm_get(struct mm*); +void mm_put(struct mm*); +int mm_shared(struct mm*); +void mm_lock(struct mm*); +void mm_unlock(struct mm*); +struct files* files_alloc(void); +void files_get(struct files*); +void files_put(struct files*); +void files_lock(void); +void files_unlock(void); +struct sighand* sighand_alloc(void); +void sighand_get(struct sighand*); +void sighand_put(struct sighand*); +struct proc* proc_leader(struct proc*); +int proc_thread_count(int); +void thread_exit(void) __attribute__((noreturn)); +int futex_wake_key(void*, int); +int futex_op(addr_t, int, int, addr_t, addr_t, int); +int clone_proc(uint64, uint64, uint64, uint64, uint64); +void proc_exec_threads(void); +void proc_exec_done(void); +void thread_reaper_start(void); +void vm_defer_tick(void); +void mm_release_shared(struct mm*); void proc_stop(int); int sig_attention(struct proc*); int sig_blocking_check(void); @@ -774,7 +799,7 @@ uint64 memfd_size(int id); int memfd_truncate(int id, uint64 len); int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing); void shm_detach(int id); -void proc_release_shared(struct proc *); + int copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int is_fb); void clearpteu(pml4e_t *pgdir, char *uva); diff --git a/include/core/proc.h b/include/core/proc.h index 602a6f8..a8a6965 100644 --- a/include/core/proc.h +++ b/include/core/proc.h @@ -31,6 +31,40 @@ struct vma { int shmid; // shared segment this came from, or -1 }; +// --- What threads share --------------------------------------------------- +// +// A thread is a struct proc like any other, scheduled like any other. What +// makes several of them one program is that they point at the same three +// objects below, each counted, each freed with its last user: clone() with +// CLONE_VM, CLONE_FILES and CLONE_SIGHAND shares them, fork() copies them. + +// The address space. pgdir is mirrored in every struct proc that uses it, +// because the scheduler, trapasm and half the kernel read it from there; it +// only changes at exec, which makes a new mm. +struct mm { + int ref; + int locked; // mm_lock(): one mmap/munmap/brk at a time + uint64 sz; // top of the heap; p->sz mirrors it + uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap) + pml4e_t *pgdir; + struct vma vma[NVMA]; // VMA tracking for mmap +}; + +// The descriptor table, and FD_CLOEXEC beside it. +struct files { + int ref; + struct file *ofile[NOFILE]; + char flags[NOFILE]; +}; + +// Signal dispositions: what sigaction() sets. +struct sighand { + int ref; + uint64 handler[32]; // 0 SIG_DFL, 1 SIG_IGN, else an address + uint64 restorer[32]; + uint64 flags[32]; // sa_flags; SA_RESTART is the one read +}; + // Per-CPU state struct cpu { uchar id; @@ -176,6 +210,8 @@ struct procinfo { char exe[64]; // full path, for /proc//exe int kthread; // a kernel thread, not a program int stopped; // stopped by a signal (ps shows T) + int tgid; // the process this thread belongs to + int nthreads; // threads in that process }; struct proc { @@ -191,7 +227,7 @@ struct proc { void *chan; // [+0x40] int killed; // [+0x48] int _pad; // [+0x4c] - struct file *ofile[NOFILE]; // Open files [+0x50] + struct files *files; // Open files, shared by threads [+0x50] struct inode *cwd; // follows char name[16]; // // The path exec() was given. `name` is only the last component and only 15 @@ -238,7 +274,6 @@ struct proc { // alarmd, in kernel/proc/proc.c, is what posts it. uint alarm_at; uint alarm_every; - char ofile_flags[NOFILE]; // Real signal delivery (added for SIGCHLD — see kernel/sys/syscall.c's // deliver_signals() and kernel/proc/proc.c's exit()). sig_handler[sig]/ @@ -247,9 +282,7 @@ struct proc { // the pre-signal trapframe so sys_rt_sigreturn() can restore it; in_signal // prevents re-entering a handler while one is already running (we don't // support nested signal delivery). - uint64 sig_handler[32]; - uint64 sig_restorer[32]; - uint64 sig_flags[32]; // sa_flags; SA_RESTART is the one read + struct sighand *sighand; // dispositions, shared by threads struct trapframe saved_sigtf; int in_signal; @@ -260,8 +293,7 @@ struct proc { uint64 sigsusp_oldmask; int sigsusp_active; - struct vma vma[NVMA]; // VMA tracking for mmap - uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap) + struct mm *mm; // address space, shared by threads // ABI translation (Linux compat syscall number map) int *syscall_map; @@ -339,6 +371,19 @@ struct proc { int stopsig; int stop_report; int cont_report; + + // Threads. tgid is the pid of the thread group's leader -- what getpid() + // returns -- and equal to pid for every ordinary process. + int tgid; + uint64 clear_child_tid; // CLONE_CHILD_CLEARTID: zeroed and woken at exit + void *futex_key; // the futex word this thread waits on, or 0 + int futex_woken; + int autoreap; // a thread: reapd frees it, nobody waits for it + struct proc *vfork_parent; // CLONE_VFORK: released at exec or exit + int vfork_wait; // the vfork child this process is waiting on + int thread_dying; // killed with its group: exits as a thread + int group_status; // a thread's exit_group status, for the leader + int group_status_set; }; #define FD_CLOEXEC 1 diff --git a/include/uapi/errno.h b/include/uapi/errno.h index a061593..0edee24 100644 --- a/include/uapi/errno.h +++ b/include/uapi/errno.h @@ -39,6 +39,7 @@ #define ENAMETOOLONG 36 // File name too long #define ENOLCK 37 // No record locks available #define ENOSYS 38 // Function not implemented +#define ETIMEDOUT 110 // Connection timed out (and a futex wait that ran out) #define ENOTEMPTY 39 // Directory not empty #define ELOOP 40 // Too many symbolic links encountered diff --git a/kernel/arch/x86_64/vm.c b/kernel/arch/x86_64/vm.c index 04e61ea..20a20e5 100644 --- a/kernel/arch/x86_64/vm.c +++ b/kernel/arch/x86_64/vm.c @@ -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; diff --git a/kernel/core/console.c b/kernel/core/console.c index b7c3f55..3cc6901 100644 --- a/kernel/core/console.c +++ b/kernel/core/console.c @@ -711,9 +711,21 @@ consoleread(struct inode *ip, uint64 off, char *dst, int n) iunlock(ip); target = n; + uint t0 = ticks; acquire(&input_lock); while(n > 0){ while(v->in.r == v->in.w){ + // Raw mode with VMIN 0 is a poll: VTIME tenths of a second at most, and + // none at all when that is 0 too. It waited for a key regardless, and + // btop -- which drains its input with exactly such reads -- hung on the + // read after the last key, so `q` never quit it. + if (!(v->tio.c_lflag & ICANON) && v->tio.c_cc[VMIN] == 0 && + (target - n > 0 || + (uint)(ticks - t0) >= (uint)v->tio.c_cc[VTIME] * (HZ / 10))) { + release(&input_lock); + ilock(ip); + return target - n; + } if (v->in.eof) { v->in.eof = 0; release(&input_lock); @@ -752,7 +764,11 @@ consoleread(struct inode *ip, uint64 off, char *dst, int n) acquire(&input_lock); continue; } - sleep(&v->in.r, &input_lock); + // A VTIME wait has to see the clock move as well as a key arrive. + if (!(v->tio.c_lflag & ICANON) && v->tio.c_cc[VMIN] == 0) + sleep(&ticks, &input_lock); + else + sleep(&v->in.r, &input_lock); } c = v->in.buf[v->in.r++ % VT_INPUT_BUF]; *dst++ = c; diff --git a/kernel/core/main.c b/kernel/core/main.c index a626a58..9d9dbfb 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -407,6 +407,7 @@ kmain(uint64 fb_addr) netuser_init(); dbg_thread_start(); alarm_thread_start(); + thread_reaper_start(); if (boot_verbose) cprintf("verbose boot: dbg thread started\n"); // The greeting is not printed here. diff --git a/kernel/dev/pciuser.c b/kernel/dev/pciuser.c index ffd1293..30b0560 100644 --- a/kernel/dev/pciuser.c +++ b/kernel/dev/pciuser.c @@ -664,7 +664,7 @@ do_dma(uint64 argp) struct vma *v = 0; for (int i = 0; i < NVMA; i++) - if (!p->vma[i].used) { v = &p->vma[i]; break; } + if (!p->mm->vma[i].used) { v = &p->mm->vma[i]; break; } if (!v) { blk->proc = 0; return -ENOMEM; @@ -672,9 +672,9 @@ do_dma(uint64 argp) // Its own mapping in the caller, taken from the same bump allocator mmap // uses so the two cannot collide. - if (p->mmap_next == 0) - p->mmap_next = 0x40000000; - uint64 va = p->mmap_next; + if (p->mm->mmap_next == 0) + p->mm->mmap_next = 0x40000000; + uint64 va = p->mm->mmap_next; if (va + size < va || va + size > KERNBASE) { blk->proc = 0; return -ENOMEM; @@ -685,7 +685,7 @@ do_dma(uint64 argp) blk->proc = 0; return -ENOMEM; } - p->mmap_next += size; + p->mm->mmap_next += size; uint64 phys = dma_phys(kva); if (mappages(p->pgdir, (void *)va, size, phys, PTE_W | PTE_U) < 0) { diff --git a/kernel/fs/exec.c b/kernel/fs/exec.c index 5f3c4f1..ab565f0 100644 --- a/kernel/fs/exec.c +++ b/kernel/fs/exec.c @@ -369,7 +369,8 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) uint64 sz, sp; struct elfhdr elf; struct inode *ip; - pml4e_t *pgdir, *oldpgdir; + pml4e_t *pgdir; + struct mm *oldmm, *newmm; // The arguments are measured before anything is loaded, so that too many // of them is E2BIG with nothing to undo -- and so the stack can be sized to @@ -540,10 +541,10 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) safestrcpy(myproc()->exe, path, sizeof(myproc()->exe)); for(int i = 0; i < NOFILE; i++){ - if(myproc()->ofile[i] && (myproc()->ofile_flags[i] & FD_CLOEXEC)){ - fileclose(myproc()->ofile[i]); - myproc()->ofile[i] = 0; - myproc()->ofile_flags[i] = 0; + if(myproc()->files->ofile[i] && (myproc()->files->flags[i] & FD_CLOEXEC)){ + fileclose(myproc()->files->ofile[i]); + myproc()->files->ofile[i] = 0; + myproc()->files->flags[i] = 0; } } @@ -557,18 +558,19 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) myproc()->map_size = 0; } - // Detach shared segments before anything forgets which mappings they were. - // - // This has to happen while myproc()->pgdir and myproc()->vma[] still describe the - // *old* image: the memset below erases the bookkeeping, and freevm() at the - // end would then free a shared memory segment's pages as though they were - // ordinary heap. Doing it here rather than there is the difference between - // detaching a segment and corrupting whoever else has it mapped. - proc_release_shared(myproc()); - - oldpgdir = myproc()->pgdir; + // A new image is a new address space: a fresh mm, and the old one dropped + // at the end. mm_put() detaches shared segments before freeing anything, + // and does nothing at all while a vfork parent is still using it. + if ((newmm = mm_alloc()) == 0) + goto bad; + proc_exec_threads(); + newmm->pgdir = pgdir; + newmm->sz = sz; + oldmm = myproc()->mm; + myproc()->mm = newmm; myproc()->pgdir = pgdir; myproc()->sz = sz; + pgdir = 0; // newmm owns it now; not bad:'s to free // New address space: reset all per-process mmap/signal/TLS state. // sig_handler/sig_restorer point into the *old* image — POSIX resets @@ -579,26 +581,25 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) // Ignored signals stay ignored, and the mask is inherited (execve(2)): // neither points into the old image. `nohup` and every program that ignores // SIGPIPE before running a helper depend on it. - memset(myproc()->vma, 0, sizeof(myproc()->vma)); for (int i = 0; i < 32; i++) - if (myproc()->sig_handler[i] != 1) - myproc()->sig_handler[i] = 0; - memset(myproc()->sig_restorer, 0, sizeof(myproc()->sig_restorer)); + if (myproc()->sighand->handler[i] != 1) + myproc()->sighand->handler[i] = 0; + memset(myproc()->sighand->restorer, 0, sizeof(myproc()->sighand->restorer)); myproc()->fs_base = 0; myproc()->pending_signals = 0; myproc()->in_signal = 0; myproc()->sigsusp_active = 0; - myproc()->mmap_next = 0; + myproc()->mm->mmap_next = 0; // The interpreter is ordinary memory to everything after this: fork copies // it, exit frees it. mmap carries on above it. if (interp[0]) { - myproc()->vma[0].addr = INTERP_BASE; - myproc()->vma[0].length = PGROUNDUP(interp_end); - myproc()->vma[0].used = 1; - myproc()->vma[0].no_free = 0; - myproc()->vma[0].shmid = -1; - myproc()->mmap_next = INTERP_BASE + PGROUNDUP(interp_end); + myproc()->mm->vma[0].addr = INTERP_BASE; + myproc()->mm->vma[0].length = PGROUNDUP(interp_end); + myproc()->mm->vma[0].used = 1; + myproc()->mm->vma[0].no_free = 0; + myproc()->mm->vma[0].shmid = -1; + myproc()->mm->mmap_next = INTERP_BASE + PGROUNDUP(interp_end); } myproc()->tf->rax = 0; @@ -637,7 +638,8 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) // old one's registers. memmove(myproc()->fpu, fpu_default, sizeof(myproc()->fpu)); fxrstor(myproc()->fpu); - freevm(oldpgdir); + mm_put(oldmm); + proc_exec_done(); // a vfork parent may have its memory back kfree((char*)scratch); kfree(bounce); fsstat.exec_cycles += rdtsc() - _t0; diff --git a/kernel/fs/procfs.c b/kernel/fs/procfs.c index e1878e0..d80b1f1 100644 --- a/kernel/fs/procfs.c +++ b/kernel/fs/procfs.c @@ -70,7 +70,8 @@ #define PF_SCHEDSTAT 6 #define PF_FD 7 // the directory /proc//fd #define PF_TASK 8 // the directory /proc//task -#define PF_MAX 8 +#define PF_COMM 9 +#define PF_MAX 9 #define PF_FDENT 0x80 // | fd: one link in it #define INO_PID(ino) ((int)((ino) >> 8)) @@ -137,11 +138,13 @@ static const struct global_file pidfiles[] = { // One symlink per open descriptor, named by its number and pointing at the // path it was opened by, or at "socket:[ino]" and the like. { "fd", PF_FD }, - // The process's threads, one directory each, named by thread id. A process - // here is one thread, so task/ holds a single entry, the pid, and it *is* - // /proc/ -- the same inode. htop reads every process through it and + // The process's threads, one directory each, named by thread id; each is + // /proc/ -- the same inode. htop reads every process through it and // shows nothing at all without it. { "task", PF_TASK }, + // The name alone, a line of it. btop reads this and nothing else for the + // Program column. + { "comm", PF_COMM }, }; #define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0]))) @@ -236,7 +239,9 @@ gen_pid_stat(struct sb *s, const struct procinfo *pi) sbputc(s, ' '); sbputi(s, (int64)pi->cutime); sbputc(s, ' '); sbputi(s, (int64)pi->cstime); - sbputs(s, " 20 0 1 0 0 "); // priority nice num_threads itrealvalue starttime + sbputs(s, " 20 0 "); // priority nice + sbputi(s, pi->nthreads); // 20 num_threads + sbputs(s, " 0 0 "); // itrealvalue starttime sbputi(s, (int64)pi->sz); // 23 vsize, in bytes sbputc(s, ' '); sbputi(s, (int64)(pi->sz / PGSIZE)); // 24 rss, in pages @@ -248,7 +253,7 @@ gen_pid_status(struct sb *s, const struct procinfo *pi) { sbputs(s, "Name:\t"); sbputs(s, pi->name); sbputc(s, '\n'); sbputs(s, "State:\t"); sbputc(s, pi->stopped ? 'T' : state_char(pi->state)); - sbputs(s, "\nTgid:\t"); sbputi(s, pi->pid); + sbputs(s, "\nTgid:\t"); sbputi(s, pi->tgid); sbputs(s, "\nPid:\t"); sbputi(s, pi->pid); sbputs(s, "\nPPid:\t"); sbputi(s, pi->ppid); sbputs(s, "\nUid:\t"); sbputi(s, pi->uid); @@ -256,7 +261,7 @@ gen_pid_status(struct sb *s, const struct procinfo *pi) sbputc(s, '\t'); sbputi(s, pi->uid); sbputc(s, '\t'); sbputi(s, pi->uid); sbputs(s, "\nVmSize:\t"); sbputi(s, (int64)(pi->sz / 1024)); - sbputs(s, " kB\nThreads:\t1\n"); + sbputs(s, " kB\nThreads:\t"); sbputi(s, pi->nthreads); sbputc(s, '\n'); // Where Linux keeps them, under the names Linux uses. The two are worth // telling apart: a process with a high voluntary count is waiting on @@ -315,6 +320,7 @@ gen_file(uint64 ino, char *buf, int cap) case PF_CMDLINE: gen_pid_cmdline(&s, &pi); break; case PF_STATM: gen_pid_statm(&s, &pi); break; case PF_EXE: sbputs(&s, pi.exe[0] ? pi.exe : pi.name); break; + case PF_COMM: sbputs(&s, pi.name); sbputc(&s, '\n'); break; case PF_SCHEDSTAT: sbputi(&s, (int64)pi.vruntime); sbputc(&s, ' '); sbputi(&s, (int64)pi.nice); sbputc(&s, ' '); @@ -521,11 +527,13 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff) INO_FILE(dp->inum) == PF_TASK ? (dp->inum & ~0xFFULL) : PROCFS_ROOT); - // Inside /proc//task/: the one thread, which is the process. + // Inside /proc//task/: a thread of this process, which is /proc/. if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_TASK) { - if (name_to_pid(name) != INO_PID(dp->inum)) + int tid = name_to_pid(name); + if (tid <= 0 || proc_snapshot_pid(tid, &pi) < 0 || + pi.tgid != (int)INO_PID(dp->inum)) return 0; - return iget(dp->dev, (dp->inum & ~0xFFULL) | PF_DIR); + return iget(dp->dev, ((uint64)tid << 8) | PF_DIR); } // Inside /proc//fd/: a number that is an open descriptor. @@ -647,7 +655,11 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off) EMIT(PROCFS_SELF, DT_LNK_, "self"); for (i = 0; i < NGLOBALS; i++) EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name); + // Processes only. A thread is reachable as /proc/, as on Linux, + // but listed under its process's task/ rather than here. for (i = 0; proc_snapshot(i, &pi) == 0; i++) { + if (pi.tgid != pi.pid) + continue; utoa((uint)pi.pid, name); EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name); } @@ -664,8 +676,12 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off) } else if (INO_FILE(dp->inum) == PF_TASK) { EMIT(dp->inum, DT_DIR_, "."); EMIT(dp->inum & ~0xFFULL, DT_DIR_, ".."); - utoa((uint)INO_PID(dp->inum), name); - EMIT((dp->inum & ~0xFFULL) | PF_DIR, DT_DIR_, name); + for (i = 0; proc_snapshot(i, &pi) == 0; i++) { + if (pi.tgid != (int)INO_PID(dp->inum)) + continue; + utoa((uint)pi.pid, name); + EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name); + } } else { EMIT(dp->inum, DT_DIR_, "."); EMIT(PROCFS_ROOT, DT_DIR_, ".."); diff --git a/kernel/net/inet.c b/kernel/net/inet.c index 0e43fd9..57ab806 100644 --- a/kernel/net/inet.c +++ b/kernel/net/inet.c @@ -169,7 +169,7 @@ net_interrupted(void) return 1; uint64 live = p->pending_signals & ~p->signal_mask; for (int sig = 1; sig < 32; sig++) - if ((live & (1ULL << sig)) && p->sig_handler[sig] > 1) // not DFL/IGN + if ((live & (1ULL << sig)) && p->sighand->handler[sig] > 1) // not DFL/IGN return 1; return 0; } diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index 7ffb240..f64ce1c 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -15,6 +15,7 @@ #include "uapi/posix.h" #include "uapi/errno.h" #include "fs/blkdev.h" +#include "core/uaccess.h" // Verify myproc()->fs_base offset matches the hardcoded 0x688 in trapasm.S. _Static_assert(__builtin_offsetof(struct proc, fs_base) == 0x18, @@ -140,7 +141,7 @@ proc_dump(struct sbuf *out, const char *arg) sb_pad(out, state_name(p->state), 8); sb_putu_pad(out, p->sz / 1024, 6); sb_puts(out, " 0x"); - sb_puthex(out, p->mmap_next, 16); + sb_puthex(out, p->mm ? p->mm->mmap_next : 0, 16); sb_puts(out, " "); sb_puts(out, p->name); if (p->killed) @@ -174,18 +175,20 @@ vma_dump(struct sbuf *out, const char *arg) continue; if (only && p->pid != only) continue; + if (!p->mm || (p->tgid && p->tgid != p->pid)) + continue; // a thread: its leader lists the mappings for (int i = 0; i < NVMA; i++) { - if (!p->vma[i].used) + if (!p->mm->vma[i].used) continue; any = 1; sb_putu_pad(out, p->pid, 5); sb_putu_pad(out, i, 6); sb_puts(out, " 0x"); - sb_puthex(out, p->vma[i].addr, 16); - sb_putu_pad(out, p->vma[i].length, 12); - sb_puts(out, p->vma[i].no_free ? " yes " : " no "); - if (p->vma[i].shmid >= 0) - sb_putu_pad(out, p->vma[i].shmid, 5); + sb_puthex(out, p->mm->vma[i].addr, 16); + sb_putu_pad(out, p->mm->vma[i].length, 12); + sb_puts(out, p->mm->vma[i].no_free ? " yes " : " no "); + if (p->mm->vma[i].shmid >= 0) + sb_putu_pad(out, p->mm->vma[i].shmid, 5); else sb_puts(out, " -"); sb_puts(out, " "); @@ -239,15 +242,15 @@ maphit(void *ctx, uint64 va, uint64 pa, uint64 pte) { int i, found = 0; for (i = 0; i < NVMA; i++) { - if (!m->p->vma[i].used) + if (!m->p->mm->vma[i].used) continue; - if (va < m->p->vma[i].addr || - va >= m->p->vma[i].addr + m->p->vma[i].length) + if (va < m->p->mm->vma[i].addr || + va >= m->p->mm->vma[i].addr + m->p->mm->vma[i].length) continue; found = 1; sb_puts(m->out, " vma["); sb_putu(m->out, i); - sb_puts(m->out, m->p->vma[i].no_free ? "] no_free" : "]"); + sb_puts(m->out, m->p->mm->vma[i].no_free ? "] no_free" : "]"); break; } if (!found) { @@ -276,9 +279,196 @@ proc_whose(struct sbuf *out, struct whose *w) release(&ptable.lock); } +// --- Shared objects -------------------------------------------------------- +// +// One of each per slot is enough by construction: every struct proc holds at +// most one mm, one files and one sighand, so NPROC of each can never run out. +// ref 0 is free; -1 is being torn down and not yet free, so that a slot whose +// last user is still freeing it cannot be handed out from under it. +// +// objlock covers the counts and nothing else, and is never held while taking +// ptable.lock. mm_lock() sleeps on mmwait, a separate lock for the same reason. +static struct mm mmtab[NPROC]; +static struct files filestab[NPROC]; +static struct sighand sightab[NPROC]; +static struct spinlock objlock, mmwait; + +struct mm * +mm_alloc(void) +{ + acquire(&objlock); + for (struct mm *m = mmtab; m < &mmtab[NPROC]; m++) + if (m->ref == 0) { + memset(m, 0, sizeof(*m)); + for (int i = 0; i < NVMA; i++) + m->vma[i].shmid = -1; + m->ref = 1; + release(&objlock); + return m; + } + release(&objlock); + return 0; +} + +void +mm_get(struct mm *m) +{ + acquire(&objlock); + m->ref++; + release(&objlock); +} + +// Drop a reference. The last one releases shared segments and frees the page +// tables, which nobody can be running on any more: a thread's reference is +// only dropped once it is off the CPU for good. +void +mm_put(struct mm *m) +{ + if (m == 0) + return; + acquire(&objlock); + int last = --m->ref == 0; + if (last) + m->ref = -1; + release(&objlock); + if (!last) + return; + mm_release_shared(m); + if (m->pgdir) + freevm(m->pgdir); + m->pgdir = 0; + acquire(&objlock); + m->ref = 0; + release(&objlock); +} + +int +mm_shared(struct mm *m) +{ + return m && m->ref > 1; +} + +// One mmap, munmap or brk at a time per address space. Sleeps: an mmap of a +// file reads it. +void +mm_lock(struct mm *m) +{ + acquire(&mmwait); + while (m->locked) + sleep(m, &mmwait); + m->locked = 1; + release(&mmwait); +} + +void +mm_unlock(struct mm *m) +{ + acquire(&mmwait); + m->locked = 0; + wakeup(m); + release(&mmwait); +} + +struct files * +files_alloc(void) +{ + acquire(&objlock); + for (struct files *f = filestab; f < &filestab[NPROC]; f++) + if (f->ref == 0) { + memset(f, 0, sizeof(*f)); + f->ref = 1; + release(&objlock); + return f; + } + release(&objlock); + return 0; +} + +void +files_get(struct files *f) +{ + acquire(&objlock); + f->ref++; + release(&objlock); +} + +// The last reference closes every descriptor. fileclose can sleep, so this is +// for process context only -- exit(), not the reaper. +void +files_put(struct files *f) +{ + if (f == 0) + return; + acquire(&objlock); + int last = --f->ref == 0; + if (last) + f->ref = -1; + release(&objlock); + if (!last) + return; + for (int fd = 0; fd < NOFILE; fd++) + if (f->ofile[fd]) { + struct file *x = f->ofile[fd]; + f->ofile[fd] = 0; + f->flags[fd] = 0; + fileclose(x); + } + acquire(&objlock); + f->ref = 0; + release(&objlock); +} + +// Slot allocation in a table two threads may be filling at once. +void +files_lock(void) +{ + acquire(&mmwait); +} + +void +files_unlock(void) +{ + release(&mmwait); +} + +struct sighand * +sighand_alloc(void) +{ + acquire(&objlock); + for (struct sighand *s = sightab; s < &sightab[NPROC]; s++) + if (s->ref == 0) { + memset(s, 0, sizeof(*s)); + s->ref = 1; + release(&objlock); + return s; + } + release(&objlock); + return 0; +} + +void +sighand_get(struct sighand *s) +{ + acquire(&objlock); + s->ref++; + release(&objlock); +} + +void +sighand_put(struct sighand *s) +{ + if (s == 0) + return; + acquire(&objlock); + s->ref--; + release(&objlock); +} + void pinit(void) { + initlock(&objlock, "procobj"); + initlock(&mmwait, "mmwait"); initlock(&ptable.lock, "ptable"); dbg_register("proc", proc_dump, "every process, with its state and size"); dbg_register("vma", vma_dump, "mappings a process holds but does not own"); @@ -312,8 +502,6 @@ found: // including stale handler addresses. deliver_signals() (syscall.c) // would then jump to garbage on this new process's very first syscall // return, faulting at whatever bogus rip happened to be sitting there. - memset(p->sig_handler, 0, sizeof(p->sig_handler)); - memset(p->sig_restorer, 0, sizeof(p->sig_restorer)); p->pending_signals = 0; p->alarm_at = p->alarm_every = 0; // a child's timers start disarmed p->utime = p->stime = p->cutime = p->cstime = 0; @@ -368,10 +556,24 @@ found: p->syscall_map = 0; p->map_size = 0; - p->mmap_next = 0; p->kfn = 0; + p->tgid = p->pid; + p->clear_child_tid = 0; + p->futex_key = 0; + p->futex_woken = 0; + p->autoreap = 0; + p->vfork_parent = 0; + p->vfork_wait = 0; + p->thread_dying = 0; + p->group_status_set = 0; - memset(p->ofile_flags, 0, sizeof(p->ofile_flags)); + // Every process starts with its own of each; clone() swaps in the parent's + // to make a thread. The pools cannot run dry -- see mm_alloc(). + p->mm = mm_alloc(); + p->files = files_alloc(); + p->sighand = sighand_alloc(); + if (!p->mm || !p->files || !p->sighand) + panic("allocproc: object pools"); return p; } @@ -394,6 +596,8 @@ sinit(void) panic("sinit: allocuvm failed"); p->sz = 32 * PGSIZE; + p->mm->sz = p->sz; + p->mm->pgdir = p->pgdir; memset(p->tf, 0, sizeof(*p->tf)); p->tf->cs = (SEG_UCODE << 3) | DPL_USER; @@ -421,19 +625,54 @@ growproc(int64 n) { addr_t sz; - sz = myproc()->sz; + struct mm *m = myproc()->mm; + mm_lock(m); + sz = m->sz; if(n > 0){ - if((sz = allocuvm(myproc()->pgdir, sz, sz + n)) == 0) + if((sz = allocuvm(myproc()->pgdir, sz, sz + n)) == 0){ + mm_unlock(m); return -1; + } } else if(n < 0){ - if((sz = deallocuvm(myproc()->pgdir, sz, sz + n)) == 0) + if((sz = deallocuvm(myproc()->pgdir, sz, sz + n)) == 0){ + mm_unlock(m); return -1; + } } + m->sz = sz; myproc()->sz = sz; + mm_unlock(m); switchuvm(myproc()); return 0; } +// The leader of p's thread group: the process, as its parent sees it. +struct proc * +proc_leader(struct proc *p) +{ + if (p->tgid == p->pid) + return p; + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->pid == p->tgid && q->state != UNUSED) + return q; + return p; +} + +// Give back a slot that fork() or clone() could not finish. +static void +fork_undo(struct proc *np) +{ + mm_put(np->mm); // frees the half-copied pgdir with it + files_put(np->files); + sighand_put(np->sighand); + np->mm = 0; + np->files = 0; + np->sighand = 0; + np->pgdir = 0; + np->kstack = 0; // the slot keeps its stack; see kstacks[] + np->state = UNUSED; +} + int fork(void) { @@ -447,35 +686,41 @@ fork(void) // them -- so the child's copy is taken straight from there. fxsave(np->fpu); - if((np->pgdir = copyuvm(myproc()->pgdir, myproc()->sz)) == 0){ - np->kstack = 0; // the slot keeps its stack; see kstacks[] - np->state = UNUSED; + // Another thread must not be reshaping the address space mid-copy. + struct mm *pm = myproc()->mm; + mm_lock(pm); + if((np->pgdir = copyuvm(myproc()->pgdir, pm->sz)) == 0){ + mm_unlock(pm); + fork_undo(np); return -1; } - np->sz = myproc()->sz; - np->parent = myproc(); + np->sz = pm->sz; + np->mm->sz = pm->sz; + np->mm->pgdir = np->pgdir; + // A child of any thread is a child of the process. + np->parent = proc_leader(myproc()); // copyuvm() only duplicates [0, sz) — mmap()'d regions live above that // and were never copied, leaving the child's vma[] bookkeeping (stale // from whichever process last occupied this ptable slot) pointing at // memory that isn't actually mapped in np->pgdir. Reset and rebuild it // from the parent's real, current mappings. - memset(np->vma, 0, sizeof(np->vma)); + memset(np->mm->vma, 0, sizeof(np->mm->vma)); for(i = 0; i < NVMA; i++){ - if(!myproc()->vma[i].used) + if(!myproc()->mm->vma[i].used) continue; - if(copy_vma_range(np->pgdir, myproc()->pgdir, myproc()->vma[i].addr, - myproc()->vma[i].length, myproc()->vma[i].no_free) < 0){ - freevm(np->pgdir); - np->kstack = 0; // the slot keeps its stack; see kstacks[] - np->state = UNUSED; + if(copy_vma_range(np->pgdir, myproc()->pgdir, myproc()->mm->vma[i].addr, + myproc()->mm->vma[i].length, myproc()->mm->vma[i].no_free) < 0){ + mm_unlock(pm); + fork_undo(np); return -1; } - np->vma[i] = myproc()->vma[i]; - if(np->vma[i].shmid >= 0) - shm_ref(np->vma[i].shmid); + np->mm->vma[i] = myproc()->mm->vma[i]; + if(np->mm->vma[i].shmid >= 0) + shm_ref(np->mm->vma[i].shmid); } - np->mmap_next = myproc()->mmap_next; + np->mm->mmap_next = myproc()->mm->mmap_next; + mm_unlock(pm); memmove(np->tf, myproc()->tf, sizeof(struct trapframe)); @@ -486,18 +731,20 @@ fork(void) // child starts with no pending signals of its own, though — those // aren't inherited. allocproc() already zeroed pending_signals/ // in_signal/saved_sigtf for this fresh slot. - memmove(np->sig_handler, myproc()->sig_handler, sizeof(np->sig_handler)); - memmove(np->sig_flags, myproc()->sig_flags, sizeof(np->sig_flags)); - memmove(np->sig_restorer, myproc()->sig_restorer, sizeof(np->sig_restorer)); + memmove(np->sighand->handler, myproc()->sighand->handler, sizeof(np->sighand->handler)); + memmove(np->sighand->flags, myproc()->sighand->flags, sizeof(np->sighand->flags)); + memmove(np->sighand->restorer, myproc()->sighand->restorer, sizeof(np->sighand->restorer)); np->signal_mask = myproc()->signal_mask; np->tf->rax = 0; + files_lock(); for(i = 0; i < NOFILE; i++) - if(myproc()->ofile[i]) { - np->ofile[i] = filedup(myproc()->ofile[i]); - np->ofile_flags[i] = myproc()->ofile_flags[i]; + if(myproc()->files->ofile[i]) { + np->files->ofile[i] = filedup(myproc()->files->ofile[i]); + np->files->flags[i] = myproc()->files->flags[i]; } + files_unlock(); // POSIX: the child inherits process group, session and all four ids. np->pgid = myproc()->pgid; np->sid = myproc()->sid; @@ -633,6 +880,480 @@ proc_reincarnate(int status) return 0; } +// --- Threads --------------------------------------------------------------- + +static void sched_enqueue(struct proc *p); + +static int reap_chan; + +// Every other live thread of self's group: killed, and woken so it notices. +// Non-leaders leave as threads; the leader, if it is one of them, leaves as +// the process. With ptable.lock. +static void +group_kill(struct proc *self) +{ + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) { + if (q == self || q->tgid != self->tgid) + continue; + if (q->state == UNUSED || q->state == ZOMBIE || q->state == EMBRYO) + continue; + q->killed = 1; + if (q->tgid != q->pid) + q->thread_dying = 1; + if (q->state == SLEEPING) { + q->state = RUNNABLE; + sched_enqueue(q); + } + } +} + +// A non-leader thread ending the program: the leader exits with its status. +static void +group_exit(struct proc *p, int status) +{ + acquire(&ptable.lock); + struct proc *l = proc_leader(p); + if (l != p && !l->group_status_set) { + l->group_status = status; + l->group_status_set = 1; + } + group_kill(p); + release(&ptable.lock); +} + +// Live threads in group tgid, the caller included. +int +proc_thread_count(int tgid) +{ + int n = 0; + acquire(&ptable.lock); + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->tgid == tgid && q->state != UNUSED && q->state != ZOMBIE) + n++; + release(&ptable.lock); + return n; +} + +// A vfork parent waits until its child has its own address space or is gone. +static void +vfork_release(struct proc *p) +{ + struct proc *vp = p->vfork_parent; + if (vp == 0) + return; + acquire(&ptable.lock); + if (vp->vfork_wait == p->pid) { + vp->vfork_wait = 0; + wakeup1(&vp->vfork_wait); + } + p->vfork_parent = 0; + release(&ptable.lock); +} + +void +proc_exec_done(void) +{ + vfork_release(myproc()); +} + +// An exec leaves one thread: the others die, and the caller becomes the +// process if it was not already. Dispositions are unshared, ignoring stays +// ignored and handlers reset -- exec does the resetting on its own copy. +void +proc_exec_threads(void) +{ + struct proc *p = myproc(); + + acquire(&ptable.lock); + group_kill(p); + if (p->tgid != p->pid) { + struct proc *l = proc_leader(p); + p->parent = l->parent; + p->tgid = p->pid; + p->autoreap = 0; + } + release(&ptable.lock); + + if (p->sighand->ref > 1) { + struct sighand *s = sighand_alloc(); + if (s) { + memmove(s->handler, p->sighand->handler, sizeof(s->handler)); + memmove(s->flags, p->sighand->flags, sizeof(s->flags)); + sighand_put(p->sighand); + p->sighand = s; + } + } +} + +// CLONE_CHILD_CLEARTID: the word pthread_join waits on. +static void +clear_tid(struct proc *p) +{ + if (p->clear_child_tid == 0 || p->pgdir == 0) + return; + int *k = (int*)uva2ka(p->pgdir, (char*)p->clear_child_tid); + if (k && ((addr_t)k & 3) == 0) { + *k = 0; + futex_wake_key(k, 1); + } + p->clear_child_tid = 0; +} + +// The end of one thread. Its descriptors and directories go now; its slot and +// its hold on the address space go when reapd sees it is off the CPU. +void +thread_exit(void) +{ + struct proc *p = myproc(); + + clear_tid(p); + vfork_release(p); + files_put(p->files); + p->files = 0; + begin_op(); + if (p->cwd) + iput(p->cwd); + if (p->root) + iput(p->root); + end_op(); + p->cwd = 0; + p->root = 0; + + acquire(&ptable.lock); + p->autoreap = 1; + p->state = ZOMBIE; + wakeup1(&reap_chan); + sched(); + panic("zombie thread exit"); +} + +// Frees threads that have exited. A kernel thread because the last reference +// to an address space may be dropped here, and freeing one is not something +// to do inside the scheduler or under ptable.lock. +static void +reapd(void) +{ + acquire(&ptable.lock); + for (;;) { + struct proc *p, *found = 0; + for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) + if (p->state == ZOMBIE && p->autoreap) { + found = p; + break; + } + if (!found) { + sleep(&reap_chan, &ptable.lock); + continue; + } + p = found; + struct mm *m = p->mm; + struct sighand *s = p->sighand; + struct proc *l = proc_leader(p); + if (l != p) { + l->utime += p->utime; // a program's time is all of its threads' + l->stime += p->stime; + } else { + reaped_utime += p->utime; + reaped_stime += p->stime; + } + p->mm = 0; + p->sighand = 0; + p->pgdir = 0; + p->kstack = 0; + p->pid = 0; + p->tgid = 0; + p->parent = 0; + p->name[0] = 0; + p->killed = 0; + p->thread_dying = 0; + p->autoreap = 0; + p->xstate = 0; + p->killsig = 0; + p->pgid = 0; + p->sid = 0; + p->state = UNUSED; + // The leader may have been waiting on its threads to be reapable. + if (l != p && l->state == ZOMBIE && l->parent) + wakeup1(l->parent); + release(&ptable.lock); + mm_put(m); + sighand_put(s); + acquire(&ptable.lock); + } +} + +void +thread_reaper_start(void) +{ + if (kthread_create(reapd, "reapd") < 0) + cprintf("proc: no reaper; exited threads will not be freed\n"); +} + +#define CLONE_FILES_ 0x00000400 +#define CLONE_VFORK_ 0x00004000 +#define CLONE_PARENT_ 0x00008000 +#define CLONE_SETTLS_ 0x00080000 +#define CLONE_PARENT_SETTID_ 0x00100000 +#define CLONE_CHILD_CLEARTID_ 0x00200000 +#define CLONE_CHILD_SETTID_ 0x01000000 + +// clone(2), Linux's argument order: flags, stack, parent_tid, child_tid, tls. +// +// What musl's pthread_create asks for -- VM, FS, FILES, SIGHAND, THREAD, +// SYSVSEM, SETTLS, PARENT_SETTID, CHILD_CLEARTID -- and what its posix_spawn +// asks for -- VM, VFORK and a stack -- are the two shapes that matter. +int +clone_proc(uint64 flags, uint64 stack, uint64 ptid, uint64 ctid, uint64 tls) +{ + struct proc *cur = myproc(), *np; + int i, pid; + + if (!(flags & CLONE_VM)) { + if (stack || (flags & (CLONE_THREAD | CLONE_SIGHAND))) + return -EINVAL; + return fork(); // plain fork, which is all that is left + } + if ((flags & CLONE_THREAD) && !(flags & CLONE_SIGHAND)) + return -EINVAL; + + if ((np = allocproc()) == 0) + return -EAGAIN; + + mm_put(np->mm); + np->mm = cur->mm; + mm_get(cur->mm); + np->pgdir = cur->pgdir; + np->sz = cur->mm->sz; + + if (flags & CLONE_FILES_) { + files_put(np->files); + np->files = cur->files; + files_get(cur->files); + } else { + files_lock(); + for (i = 0; i < NOFILE; i++) + if (cur->files->ofile[i]) { + np->files->ofile[i] = filedup(cur->files->ofile[i]); + np->files->flags[i] = cur->files->flags[i]; + } + files_unlock(); + } + + if (flags & CLONE_SIGHAND) { + sighand_put(np->sighand); + np->sighand = cur->sighand; + sighand_get(cur->sighand); + } else { + memmove(np->sighand->handler, cur->sighand->handler, sizeof(np->sighand->handler)); + memmove(np->sighand->flags, cur->sighand->flags, sizeof(np->sighand->flags)); + memmove(np->sighand->restorer, cur->sighand->restorer, sizeof(np->sighand->restorer)); + } + + fxsave(np->fpu); + memmove(np->tf, cur->tf, sizeof(struct trapframe)); + np->tf->rax = 0; + if (stack) + np->tf->rsp = stack; // the top of the trapframe is the sysret RSP + np->fs_base = (flags & CLONE_SETTLS_) ? tls : cur->fs_base; + + np->syscall_map = cur->syscall_map; + np->map_size = cur->map_size; + np->signal_mask = cur->signal_mask; + np->pgid = cur->pgid; + np->sid = cur->sid; + np->ctty = cur->ctty; + np->uid = cur->uid; np->euid = cur->euid; np->suid = cur->suid; + np->gid = cur->gid; np->egid = cur->egid; np->sgid = cur->sgid; + np->cwd = idup(cur->cwd); + np->root = cur->root ? idup(cur->root) : 0; + memmove(np->cwd_path, cur->cwd_path, sizeof(cur->cwd_path)); + safestrcpy(np->name, cur->name, sizeof(cur->name)); + safestrcpy(np->exe, cur->exe, sizeof(cur->exe)); + np->strace = cur->strace; + np->umask = cur->umask; + sched_set_nice(np, cur->nice); + np->vruntime = cur->vruntime; + + acquire(&ptable.lock); + struct proc *leader = proc_leader(cur); + if (flags & CLONE_THREAD) { + np->tgid = cur->tgid; + np->parent = leader->parent; + np->autoreap = 1; + } else { + np->tgid = np->pid; + np->parent = (flags & CLONE_PARENT_) ? leader->parent : leader; + } + release(&ptable.lock); + + pid = np->pid; + if (flags & CLONE_CHILD_CLEARTID_) + np->clear_child_tid = ctid; + if (flags & CLONE_PARENT_SETTID_) + put_user(pid, ptid); + if (flags & CLONE_CHILD_SETTID_) + put_user(pid, ctid); // the same address space, so the same word + + if (flags & CLONE_VFORK_) { + np->vfork_parent = cur; + cur->vfork_wait = pid; + } + + __sync_synchronize(); + acquire(&ptable.lock); + np->state = RUNNABLE; + sched_enqueue(np); + // vfork: the parent sleeps until the child has execed or exited, because + // until then they share one stack's worth of memory. + if (flags & CLONE_VFORK_) + while (cur->vfork_wait == pid && !cur->killed) + sleep(&cur->vfork_wait, &ptable.lock); + release(&ptable.lock); + return pid; +} + +// --- Futexes --------------------------------------------------------------- +// +// futex() used to say EAGAIN to every wait and succeed at every wake, which +// was enough while there was one thread per process. A waiter is keyed by the +// kernel address of the word -- the physical page, in effect -- so two +// mappings of one shared page meet on the same key, as they must. + +int +futex_wake_key(void *k, int n) +{ + int woke = 0; + acquire(&ptable.lock); + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC] && woke < n; q++) { + if (q->futex_key != k || q->futex_woken) + continue; + q->futex_woken = 1; + woke++; + if (q->state == SLEEPING) { + q->state = RUNNABLE; + sched_enqueue(q); + } + } + release(&ptable.lock); + return woke; +} + +static int * +futex_word(addr_t uaddr) +{ + if (uaddr & 3) + return 0; + return (int*)uva2ka(myproc()->pgdir, (char*)uaddr); +} + +// Wait while *uaddr == val, for at most `timeout` ticks (0: no limit). +static int +futex_wait(addr_t uaddr, int val, uint64 timeout) +{ + struct proc *p = myproc(); + int *k = futex_word(uaddr); + int r; + + if (k == 0) + return -EFAULT; + acquire(&ptable.lock); + // Checked under the lock every waker takes, so a wake between the check + // and the sleep cannot be missed. + if (*(volatile int*)k != val) { + release(&ptable.lock); + return -EAGAIN; + } + p->futex_key = k; + p->futex_woken = 0; + uint t0 = ticks; + for (;;) { + if (p->futex_woken) { r = 0; break; } + if (p->killed || sig_attention(p)) { r = -EINTR; break; } + if (timeout && (uint64)(uint)(ticks - t0) >= timeout) { r = -ETIMEDOUT; break; } + sleep(timeout ? (void*)&ticks : (void*)k, &ptable.lock); + } + p->futex_key = 0; + release(&ptable.lock); + return r; +} + +// Move up to n waiters from one word to another, still waiting. +static int +futex_requeue(int *from, int *to, int n) +{ + int moved = 0; + acquire(&ptable.lock); + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC] && moved < n; q++) + if (q->futex_key == from && !q->futex_woken) { + q->futex_key = to; + moved++; + } + release(&ptable.lock); + return moved; +} + +static uint64 +ts_ticks(const struct timespec *ts) +{ + if (ts->tv_sec < 0 || ts->tv_nsec < 0) + return 1; + uint64 t = (uint64)ts->tv_sec * HZ + + ((uint64)ts->tv_nsec * HZ + 999999999) / 1000000000; + return t ? t : 1; +} + +int +futex_op(addr_t uaddr, int op, int val, addr_t utime, addr_t uaddr2, int val3) +{ + int cmd = op & 0x7f; // without PRIVATE and CLOCK_REALTIME + struct timespec ts; + uint64 timeout = 0; + + switch (cmd) { + case 0: // FUTEX_WAIT: relative timeout + if (utime) { + if (copy_from_user(&ts, utime, sizeof(ts)) < 0) + return -EFAULT; + timeout = ts_ticks(&ts); + } + return futex_wait(uaddr, val, timeout); + case 9: { // FUTEX_WAIT_BITSET: absolute timeout + if (utime) { + if (copy_from_user(&ts, utime, sizeof(ts)) < 0) + return -EFAULT; + uint64 sec, usec; + wallclock(&sec, &usec); + if (!(op & 256)) + sec -= boot_epoch; // CLOCK_MONOTONIC unless asked otherwise + long ds = ts.tv_sec - (long)sec, dn = ts.tv_nsec - (long)(usec * 1000); + if (dn < 0) { dn += 1000000000; ds--; } + if (ds < 0) + return -ETIMEDOUT; + ts.tv_sec = ds; + ts.tv_nsec = dn; + timeout = ts_ticks(&ts); + } + return futex_wait(uaddr, val, timeout); + } + case 1: // FUTEX_WAKE + case 10: { // FUTEX_WAKE_BITSET + int *k = futex_word(uaddr); + return k ? futex_wake_key(k, val) : -EFAULT; + } + case 3: // FUTEX_REQUEUE + case 4: { // FUTEX_CMP_REQUEUE + int *k = futex_word(uaddr), *k2 = futex_word(uaddr2); + if (!k || !k2) + return -EFAULT; + if (cmd == 4 && *(volatile int*)k != val3) + return -EAGAIN; + int n = futex_wake_key(k, val); + return n + futex_requeue(k, k2, (int)utime); // val2 rides in utime + } + } + return -ENOSYS; +} + void exit(int status) { @@ -651,6 +1372,23 @@ exit(int status) panic("init resurrection failed: could not exec /bin/schinit"); } + // A thread that is not the leader ends the whole program -- that is what + // exit_group, a fatal signal and a fault mean -- unless it is already dying + // with it, and then leaves as a thread. + if (myproc()->tgid != myproc()->pid) { + if (!myproc()->thread_dying) + group_exit(myproc(), status); + thread_exit(); + } + // The leader: any other thread goes too, and if one of them asked for this + // with exit_group, its status is the program's. + acquire(&ptable.lock); + if (myproc()->group_status_set) + status = myproc()->group_status; + group_kill(myproc()); + release(&ptable.lock); + vfork_release(myproc()); + // Give back any device this process had claimed. A driver that crashed must // not keep its hardware locked away from the process that replaces it -- // that is the entire point of running drivers outside the kernel. @@ -661,13 +1399,10 @@ exit(int status) myproc()->xstate = status; - for(fd = 0; fd < NOFILE; fd++){ - if(myproc()->ofile[fd]){ - fileclose(myproc()->ofile[fd]); - myproc()->ofile[fd] = 0; - myproc()->ofile_flags[fd] = 0; - } - } + // Shared with other threads, the table lives on; the last one closes it. + (void)fd; + files_put(myproc()->files); + myproc()->files = 0; // Record locks belong to the process, not to any one descriptor. lock_exit(myproc()->pid); @@ -727,6 +1462,16 @@ wait(void) // the already-zombie child. mksh then fell back to sigsuspend(), which // this kernel can't wake for a real SIGCHLD (no signal delivery), so it // silently retried forever: a real, low-CPU hang with no error printed. +// Whether any thread of p's group but p still has a slot. With ptable.lock. +static int +group_has_others(struct proc *p) +{ + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q != p && q->tgid == p->pid && q->state != UNUSED) + return 1; + return 0; +} + int waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st) { @@ -747,7 +1492,11 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st) continue; if(pid < -1 && p->pgid != -pid) continue; + if(p->tgid != p->pid) + continue; // a thread, which nobody waits for havekids = 1; + if(p->state == ZOMBIE && group_has_others(p)) + continue; // reaped once its last thread is if(p->state == ZOMBIE){ int rpid = p->pid; int rstatus = p->xstate; // copy out before the slot is recycled @@ -764,10 +1513,12 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st) if (ut) *ut = p->utime + p->cutime; if (st) *st = p->stime + p->cstime; p->kstack = 0; // the slot keeps its stack; see kstacks[] - // Give back anything shared before freevm() decides it owns the - // pages. See proc_release_shared() in kernel/sys/sys_mem.c. - proc_release_shared(p); - freevm(p->pgdir); + // The address space goes with its last user. See mm_put(). + mm_put(p->mm); + p->mm = 0; + p->pgdir = 0; + sighand_put(p->sighand); + p->sighand = 0; p->pid = 0; p->parent = 0; p->name[0] = 0; @@ -776,6 +1527,8 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st) p->killsig = 0; p->pgid = 0; p->sid = 0; + p->tgid = 0; + p->group_status_set = 0; p->state = UNUSED; release(&ptable.lock); if (status) *status = rstatus; @@ -1454,12 +2207,13 @@ kthread_create(void (*fn)(void), const char *name) return -1; if ((p->pgdir = setupkvm()) == 0) { - p->kstack = 0; // the slot keeps its stack; see kstacks[] - p->state = UNUSED; + fork_undo(p); return -1; } p->sz = PGSIZE; // never used; 0 upsets code that treats it as a size + p->mm->sz = p->sz; + p->mm->pgdir = p->pgdir; p->parent = 0; p->cwd = 0; p->kfn = fn; @@ -1544,7 +2298,7 @@ wakeup(void *chan) static int init_ignores(struct proc *p, int sig) { - return p == initproc && p->sig_handler[sig] == 0; + return p == initproc && p->sighand->handler[sig] == 0; } // Whether a signal's default action is to end the process. @@ -1572,7 +2326,7 @@ sig_kills_now(struct proc *p, int sig) return 1; if(p->signal_mask & (1ULL << sig)) return 0; - return p->sig_handler[sig] == 0 && sig_default_kills(sig); + return p->sighand->handler[sig] == 0 && sig_default_kills(sig); } // SIGALRM, on time. @@ -1622,6 +2376,7 @@ alarm_thread(void) sleep(&ticks, &tickslock); uint now = ticks; release(&tickslock); + vm_defer_tick(); // pages waiting out other CPUs' TLBs if (alarms_armed == 0) continue; @@ -1745,7 +2500,7 @@ proc_stop(int sig) static int sig_interrupts(struct proc *p, int sig) { - return p->sig_handler[sig] > 1 && !(p->sig_flags[sig] & SA_RESTART); + return p->sighand->handler[sig] > 1 && !(p->sighand->flags[sig] & SA_RESTART); } // Whether a pending signal should end a blocking wait: one that will stop the @@ -1760,7 +2515,7 @@ sig_attention(struct proc *p) for(int sig = 1; sig < 32; sig++) if((d & (1ULL << sig)) && (sig == SIGSTOP || sig_interrupts(p, sig) || - (sig_default_stops(sig) && p->sig_handler[sig] == 0))) + (sig_default_stops(sig) && p->sighand->handler[sig] == 0))) return 1; return 0; } @@ -1781,7 +2536,7 @@ sig_blocking_check(void) for(int sig = 1; sig < 32 && !stop; sig++){ if(!(d & (1ULL << sig))) continue; - if(sig == SIGSTOP || (sig_default_stops(sig) && p->sig_handler[sig] == 0)){ + if(sig == SIGSTOP || (sig_default_stops(sig) && p->sighand->handler[sig] == 0)){ p->pending_signals &= ~(1ULL << sig); stop = sig; } else if(sig_interrupts(p, sig)){ @@ -1799,6 +2554,22 @@ sig_blocking_check(void) } } +// The other threads of p's group, for a stop or a resume. With ptable.lock. +static void +sig_to_threads(struct proc *p, int sig) +{ + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) { + if (q == p || q->tgid != p->tgid || q->state == UNUSED || q->state == ZOMBIE) + continue; + q->pending_signals |= 1ULL << sig; + sig_job_control(q, sig); + if (q->state == SLEEPING) { + q->state = RUNNABLE; + sched_enqueue(q); + } + } +} + int sigsend(int pid, int sig) { @@ -1816,6 +2587,9 @@ sigsend(int pid, int sig) } p->pending_signals |= 1ULL << sig; sig_job_control(p, sig); + // A stop or a resume is the whole program's, so every thread gets it. + if(sig == SIGCONT || sig_default_stops(sig)) + sig_to_threads(p, sig); if(sig_kills_now(p, sig)) p->killed = 1; if(sig == SIGKILL) @@ -1859,6 +2633,11 @@ sigsend_pgrp(int pgid, int sig) continue; // signal 0 is an existence probe only if(init_ignores(p, sig)) continue; // see init_ignores(): this is the Ctrl-C case + // A group signal is the process's, and goes to the leader -- once, not + // once per thread, or a SIGWINCH handler would run in each of them. A + // stop or a resume is the exception: every thread has to stop. + if(p->tgid != p->pid && sig != SIGCONT && !sig_default_stops(sig)) + continue; p->pending_signals |= 1ULL << sig; sig_job_control(p, sig); if(sig_kills_now(p, sig)){ @@ -2043,7 +2822,14 @@ proc_peek(int idx, struct procinfo *out) if (n++ != idx) continue; out->pid = p->pid; - out->ppid = p->parent ? p->parent->pid : 0; + out->ppid = p->parent ? p->parent->tgid : 0; + out->tgid = p->tgid; + out->nthreads = 0; + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->tgid == p->tgid && q->state != UNUSED && q->state != ZOMBIE) + out->nthreads++; + if (out->nthreads == 0) + out->nthreads = 1; out->state = p->state; out->sz = p->sz; out->killed = p->killed; @@ -2066,7 +2852,14 @@ proc_snapshot(int idx, struct procinfo *out) if (n++ != idx) continue; out->pid = p->pid; - out->ppid = p->parent ? p->parent->pid : 0; + out->ppid = p->parent ? p->parent->tgid : 0; + out->tgid = p->tgid; + out->nthreads = 0; + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->tgid == p->tgid && q->state != UNUSED && q->state != ZOMBIE) + out->nthreads++; + if (out->nthreads == 0) + out->nthreads = 1; out->pgid = p->pgid; out->sid = p->sid; out->uid = p->uid; @@ -2119,8 +2912,8 @@ proc_fd_mask(int pid, uint64 mask[2]) for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED || p->pid != pid) continue; - for (int fd = 0; fd < NOFILE && fd < 128; fd++) - if (p->ofile[fd]) + for (int fd = 0; fd < NOFILE && fd < 128 && p->files; fd++) + if (p->files->ofile[fd]) mask[fd / 64] |= 1ULL << (fd % 64); r = 0; break; @@ -2141,8 +2934,8 @@ proc_fd_link(int pid, int fd, char *buf, int cap) for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED || p->pid != pid) continue; - if (p->ofile[fd]) - r = file_describe(p->ofile[fd], buf, cap); + if (p->files && p->files->ofile[fd]) + r = file_describe(p->files->ofile[fd], buf, cap); break; } release(&ptable.lock); diff --git a/kernel/sys/sys_compat.c b/kernel/sys/sys_compat.c index 6e67b34..56a3509 100644 --- a/kernel/sys/sys_compat.c +++ b/kernel/sys/sys_compat.c @@ -32,10 +32,12 @@ sys_enable_linux_abi(void) addr_t sys_set_tid_address(void) { + // The word zeroed and woken when this thread exits -- what pthread_join + // waits on. It used to be *written*, with the tid, which is CHILD_SETTID's + // job and not this one's. addr_t tidp; argaddr(0, &tidp); - if (tidp) - put_user(myproc()->pid, tidp); + myproc()->clear_child_tid = tidp; return myproc()->pid; } @@ -46,12 +48,15 @@ addr_t sys_set_robust_list(void) { return 0; } addr_t sys_futex(void) { - int op; + addr_t uaddr, utime, uaddr2; + int op, val, val3; + argaddr(0, &uaddr); argint(1, &op); - op &= ~(FUTEX_WAIT_PRIVATE - FUTEX_WAIT); - if (op == FUTEX_WAKE || op == FUTEX_WAKE_PRIVATE) - return 0; - return -EAGAIN; // no blocking, musl retries + argint(2, &val); + argaddr(3, &utime); + argaddr(4, &uaddr2); + argint(5, &val3); + return futex_op(uaddr, op, val, utime, uaddr2, val3); } // arch_prctl: save FS_BASE for TLS. diff --git a/kernel/sys/sys_event.c b/kernel/sys/sys_event.c index dd0b0d3..1b7c814 100644 --- a/kernel/sys/sys_event.c +++ b/kernel/sys/sys_event.c @@ -143,7 +143,7 @@ ev_open(struct evobj *o, int flags, int readable, int writable) return -EMFILE; } if (flags & O_CLOEXEC) - myproc()->ofile_flags[fd] |= FD_CLOEXEC; + myproc()->files->flags[fd] |= FD_CLOEXEC; return fd; } @@ -163,7 +163,7 @@ ev_arg(int n, int kind, struct file **pf) { int fd; struct file *f; - if (argint(n, &fd) < 0 || fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (argint(n, &fd) < 0 || fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return 0; if (f->type != FD_EVENT || f->ev->kind != kind) return 0; @@ -497,7 +497,7 @@ signalfd_common(int fd, addr_t umask, uint64 size, int flags) // An existing signalfd: change its mask, keep the descriptor. if (fd != -1) { struct file *f; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd]) || + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd]) || f->type != FD_EVENT || f->ev->kind != EV_SIGNALFD) return -EINVAL; acquiresleep(&f->ev->lock); @@ -606,9 +606,9 @@ sys_epoll_ctl(void) return -EBADF; if (argint(1, &op) < 0 || argint(2, &fd) < 0 || argaddr(3, &uev) < 0) return -EINVAL; - if (fd < 0 || fd >= NOFILE || !myproc()->ofile[fd]) + if (fd < 0 || fd >= NOFILE || !myproc()->files->ofile[fd]) return -EBADF; - struct file *tf = myproc()->ofile[fd]; + struct file *tf = myproc()->files->ofile[fd]; // A set cannot watch itself. (Nor contain a set that contains it; that // check is the deep one Linux does, and nothing here builds such loops.) if (tf == ef) @@ -662,7 +662,7 @@ ep_ready(struct evobj *o, struct epoll_event *out, int max) acquiresleep(&o->lock); for (int i = 0; i < o->ep.n && got < max; ) { struct epitem *it = &o->ep.items[i]; - struct file *now = (it->fd >= 0 && it->fd < NOFILE) ? myproc()->ofile[it->fd] : 0; + struct file *now = (it->fd >= 0 && it->fd < NOFILE) ? myproc()->files->ofile[it->fd] : 0; if (now != it->f) { ep_drop(o, i); // closed: it leaves the set continue; diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index dd15e9c..b572030 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -143,7 +143,7 @@ argfd(int n, int *pfd, struct file **pf) struct file *f; if (argint(n, &fd) < 0) return -1; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return -1; if (pfd) *pfd = fd; if (pf) *pf = f; @@ -155,13 +155,18 @@ argfd(int n, int *pfd, struct file **pf) int fdalloc(struct file *f) { + // Locked: threads share the table, and two picking the same empty slot + // would each believe they owned it. + files_lock(); for (int fd = 0; fd < NOFILE; fd++) { - if (!myproc()->ofile[fd]) { - myproc()->ofile[fd] = f; - myproc()->ofile_flags[fd] = 0; + if (!myproc()->files->ofile[fd]) { + myproc()->files->ofile[fd] = f; + myproc()->files->flags[fd] = 0; + files_unlock(); return fd; } } + files_unlock(); return -1; } @@ -178,7 +183,7 @@ file_setpath(struct file *f, int dirfd, const char *path) struct file *d; if (dirfd == AT_FDCWD) base = myproc()->cwd_path; - else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->ofile[dirfd]) && d->path) + else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->files->ofile[dirfd]) && d->path) base = d->path; else return; @@ -218,8 +223,8 @@ static struct inode * at_dir(int dirfd) { if (dirfd == AT_FDCWD) return myproc()->cwd; - if (dirfd < 0 || dirfd >= NOFILE || !myproc()->ofile[dirfd]) return 0; - struct file *f = myproc()->ofile[dirfd]; + if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) return 0; + struct file *f = myproc()->files->ofile[dirfd]; if (f->type != FD_INODE) return 0; return f->ip; } @@ -393,12 +398,12 @@ addr_t sys_dup2(void) int oldfd, newfd; struct file *f; if (argint(0, &oldfd) < 0 || argint(1, &newfd) < 0) return -1; - if (oldfd < 0 || oldfd >= NOFILE || !(f = myproc()->ofile[oldfd])) return -EBADF; + if (oldfd < 0 || oldfd >= NOFILE || !(f = myproc()->files->ofile[oldfd])) return -EBADF; if (newfd < 0 || newfd >= NOFILE) return -EBADF; if (oldfd == newfd) return newfd; - if (myproc()->ofile[newfd]) fileclose(myproc()->ofile[newfd]); - myproc()->ofile[newfd] = filedup(f); - myproc()->ofile_flags[newfd] = 0; // dup2 never carries FD_CLOEXEC over + if (myproc()->files->ofile[newfd]) fileclose(myproc()->files->ofile[newfd]); + myproc()->files->ofile[newfd] = filedup(f); + myproc()->files->flags[newfd] = 0; // dup2 never carries FD_CLOEXEC over return newfd; } @@ -412,9 +417,9 @@ addr_t sys_dup3(void) // Unlike dup2, equal fds are an error (dup3(2)). Without this check we // would fileclose(f) and then filedup() the file we just released. if (oldfd == newfd) return -EINVAL; - if (myproc()->ofile[newfd]) fileclose(myproc()->ofile[newfd]); - myproc()->ofile[newfd] = filedup(f); - myproc()->ofile_flags[newfd] = 0; + if (myproc()->files->ofile[newfd]) fileclose(myproc()->files->ofile[newfd]); + myproc()->files->ofile[newfd] = filedup(f); + myproc()->files->flags[newfd] = 0; return newfd; } @@ -423,8 +428,8 @@ addr_t sys_close(void) int fd; struct file *f; if (argfd(0, &fd, &f) < 0) return -EBADF; - myproc()->ofile[fd] = 0; - myproc()->ofile_flags[fd] = 0; + myproc()->files->ofile[fd] = 0; + myproc()->files->flags[fd] = 0; lock_close_fd(f); fileclose(f); return 0; @@ -444,23 +449,23 @@ sys_fcntl(void) { int fd, cmd, arg; if (argint(0, &fd) < 0 || argint(1, &cmd) < 0 || argint(2, &arg) < 0) return -1; - if (fd < 0 || fd >= NOFILE || !myproc()->ofile[fd]) return -1; - struct file *f = myproc()->ofile[fd]; + if (fd < 0 || fd >= NOFILE || !myproc()->files->ofile[fd]) return -1; + struct file *f = myproc()->files->ofile[fd]; switch (cmd) { case F_DUPFD: case F_DUPFD_CLOEXEC: { if (arg < 0) return -EINVAL; for (int nfd = arg; nfd < NOFILE; nfd++) { - if (!myproc()->ofile[nfd]) { - myproc()->ofile[nfd] = filedup(f); - myproc()->ofile_flags[nfd] = (cmd == F_DUPFD_CLOEXEC) ? FD_CLOEXEC : 0; + if (!myproc()->files->ofile[nfd]) { + myproc()->files->ofile[nfd] = filedup(f); + myproc()->files->flags[nfd] = (cmd == F_DUPFD_CLOEXEC) ? FD_CLOEXEC : 0; return nfd; } } return -EMFILE; } - case F_GETFD: return myproc()->ofile_flags[fd]; - case F_SETFD: myproc()->ofile_flags[fd] = arg; return 0; + case F_GETFD: return myproc()->files->flags[fd]; + case F_SETFD: myproc()->files->flags[fd] = arg; return 0; case F_GETFL: { int flags = 0; if (f->readable && f->writable) flags = O_RDWR; @@ -592,7 +597,7 @@ addr_t sys_pipe(void) int fd0 = -1, fd1; if (argptr(0, (void*)&fd, 2*sizeof(fd[0])) < 0 || pipealloc(&rf, &wf) < 0) return -1; if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) { - if (fd0 >= 0) myproc()->ofile[fd0] = 0; + if (fd0 >= 0) myproc()->files->ofile[fd0] = 0; fileclose(rf); fileclose(wf); return -1; } @@ -614,14 +619,14 @@ addr_t sys_pipe2(void) if (!fds || pipealloc(&rf, &wf) < 0) return -1; fd0 = fd1 = -1; if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) { - if (fd0 >= 0) myproc()->ofile[fd0] = 0; + if (fd0 >= 0) myproc()->files->ofile[fd0] = 0; fileclose(rf); fileclose(wf); return -1; } rf->nonblock = wf->nonblock = (flags & O_NONBLOCK) != 0; if (flags & O_CLOEXEC) { - myproc()->ofile_flags[fd0] |= FD_CLOEXEC; - myproc()->ofile_flags[fd1] |= FD_CLOEXEC; + myproc()->files->flags[fd0] |= FD_CLOEXEC; + myproc()->files->flags[fd1] |= FD_CLOEXEC; } fds[0] = fd0; fds[1] = fd1; return 0; @@ -1518,7 +1523,7 @@ addr_t sys_ioctl(void) if (f->type != FD_INODE || !f->ip || !f->writable) return -EBADF; - if (sfd < 0 || sfd >= NOFILE || (sf = pr->ofile[sfd]) == 0) + if (sfd < 0 || sfd >= NOFILE || (sf = pr->files->ofile[sfd]) == 0) return -EBADF; if (sf->type != FD_INODE || !sf->ip || !sf->readable) return -EBADF; @@ -2344,8 +2349,8 @@ addr_t sys_fstatat(void) // AT_EMPTY_PATH: if path is empty, fstat the dirfd itself. if ((flags & AT_EMPTY_PATH) && path[0] == '\0') { - if (dirfd < 0 || dirfd >= NOFILE || !myproc()->ofile[dirfd]) return -1; - struct file *f = myproc()->ofile[dirfd]; + if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) return -1; + struct file *f = myproc()->files->ofile[dirfd]; if (f->type != FD_INODE) return -1; ilock(f->ip); if (myproc()->syscall_map) stati_linux(f->ip, (struct linux_stat*)st); diff --git a/kernel/sys/sys_mem.c b/kernel/sys/sys_mem.c index bede4e9..d859eb9 100644 --- a/kernel/sys/sys_mem.c +++ b/kernel/sys/sys_mem.c @@ -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) diff --git a/kernel/sys/sys_net.c b/kernel/sys/sys_net.c index a5f1e41..3b9c358 100644 --- a/kernel/sys/sys_net.c +++ b/kernel/sys/sys_net.c @@ -123,7 +123,7 @@ sock_to_fd(struct socket *s, int sflags) f->off = 0; f->nonblock = (sflags & SOCK_NONBLOCK) != 0; if (sflags & SOCK_CLOEXEC) - myproc()->ofile_flags[fd] |= FD_CLOEXEC; + myproc()->files->flags[fd] |= FD_CLOEXEC; return fd; } @@ -136,7 +136,7 @@ argsock(int n, struct socket **ps) if (argint(n, &fd) < 0) return -EBADF; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return -EBADF; if (f->type != FD_SOCKET) return -ENOTSOCK; @@ -471,7 +471,7 @@ SYSCALL_DEFINE(socketpair) return fd0; } if ((fd1 = sock_to_fd(b, sflags)) < 0) { - myproc()->ofile[fd0] = 0; + myproc()->files->ofile[fd0] = 0; sockclose(a); sockclose(b); return fd1; @@ -479,8 +479,8 @@ SYSCALL_DEFINE(socketpair) int sv[2] = { fd0, fd1 }; if (copy_to_user(usv, sv, sizeof(sv)) < 0) { - myproc()->ofile[fd0] = 0; - myproc()->ofile[fd1] = 0; + myproc()->files->ofile[fd0] = 0; + myproc()->files->ofile[fd1] = 0; sockclose(a); sockclose(b); return -EFAULT; @@ -808,7 +808,7 @@ argsockfile(int n, struct socket **ps, struct file **pf) if (argint(n, &fd) < 0) return -EBADF; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return -EBADF; if (f->type != FD_SOCKET) return -ENOTSOCK; @@ -879,7 +879,7 @@ scm_gather(struct msghdr *m, struct file **fs, int *nfp) r = -EINVAL; goto fail; } - if (ufd < 0 || ufd >= NOFILE || !(f = myproc()->ofile[ufd])) { + if (ufd < 0 || ufd >= NOFILE || !(f = myproc()->files->ofile[ufd])) { r = -EBADF; goto fail; } @@ -1078,7 +1078,7 @@ SYSCALL_DEFINE(recvmsg) if (d < 0) break; if (flags & MSG_CMSG_CLOEXEC) - myproc()->ofile_flags[d] |= FD_CLOEXEC; + myproc()->files->flags[d] |= FD_CLOEXEC; ufd[installed++] = d; } diff --git a/kernel/sys/sys_poll.c b/kernel/sys/sys_poll.c index cdcfac3..44f4df6 100644 --- a/kernel/sys/sys_poll.c +++ b/kernel/sys/sys_poll.c @@ -79,12 +79,12 @@ do_poll(struct pollfd *fds, int nfds, int timeout_ticks) fds[i].revents = 0; if (fds[i].fd < 0) continue; // negative fd: ignore, per poll(2) - if (fds[i].fd >= NOFILE || !myproc()->ofile[fds[i].fd]) { + if (fds[i].fd >= NOFILE || !myproc()->files->ofile[fds[i].fd]) { fds[i].revents = POLLNVAL; nready++; continue; } - int rev = file_poll(myproc()->ofile[fds[i].fd], fds[i].events); + int rev = file_poll(myproc()->files->ofile[fds[i].fd], fds[i].events); if (rev) { fds[i].revents = (short)rev; nready++; @@ -217,7 +217,7 @@ sys_select(void) int want_w = fdset_isset(wr, fd); if (!want_r && !want_w) continue; - struct file *f = myproc()->ofile[fd]; + struct file *f = myproc()->files->ofile[fd]; if (!f) return -EBADF; int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0)); @@ -292,7 +292,7 @@ sys_pselect6(void) int want_w = fdset_isset(wr, fd); if (!want_r && !want_w) continue; - struct file *f = myproc()->ofile[fd]; + struct file *f = myproc()->files->ofile[fd]; if (!f) return -EBADF; int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0)); diff --git a/kernel/sys/sys_proc.c b/kernel/sys/sys_proc.c index 67ccca7..1d0df2a 100644 --- a/kernel/sys/sys_proc.c +++ b/kernel/sys/sys_proc.c @@ -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) diff --git a/kernel/sys/sys_sig.c b/kernel/sys/sys_sig.c index bb5c4c9..18356cb 100644 --- a/kernel/sys/sys_sig.c +++ b/kernel/sys/sys_sig.c @@ -115,12 +115,12 @@ sys_rt_sigaction(void) struct proc *p = myproc(); if (oldactp) { - put_user(p->sig_handler[sig], oldactp + SA_HANDLER_OFF); - put_user(p->sig_restorer[sig], oldactp + SA_RESTORER_OFF(p)); + put_user(p->sighand->handler[sig], oldactp + SA_HANDLER_OFF); + put_user(p->sighand->restorer[sig], oldactp + SA_RESTORER_OFF(p)); if (p->syscall_map) { - put_user(p->sig_flags[sig], oldactp + SA_FLAGS_OFF(p)); + put_user(p->sighand->flags[sig], oldactp + SA_FLAGS_OFF(p)); } else { - uint32 fl = (uint32)p->sig_flags[sig]; + uint32 fl = (uint32)p->sighand->flags[sig]; put_user(fl, oldactp + SA_FLAGS_OFF(p)); } } @@ -134,9 +134,9 @@ sys_rt_sigaction(void) // SIGKILL and SIGSTOP cannot be caught or ignored. if (sig == SIGKILL || sig == SIGSTOP) return -EINVAL; - p->sig_handler[sig] = h; - p->sig_restorer[sig] = r; - p->sig_flags[sig] = fl; + p->sighand->handler[sig] = h; + p->sighand->restorer[sig] = r; + p->sighand->flags[sig] = fl; } return 0; } @@ -218,16 +218,16 @@ deliver_signals(struct trapframe *tf) // SIGSTOP cannot be caught or ignored; the other three stop only by // default. Stopped here and resumed here, by SIGCONT or SIGKILL. if (sig == SIGSTOP || - (sig_default_stops(sig) && myproc()->sig_handler[sig] == 0)) { + (sig_default_stops(sig) && myproc()->sighand->handler[sig] == 0)) { myproc()->pending_signals &= ~(1ULL << sig); proc_stop(sig); if (myproc()->killed) exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); continue; } - if (myproc()->sig_handler[sig] <= 1) { + if (myproc()->sighand->handler[sig] <= 1) { myproc()->pending_signals &= ~(1ULL << sig); - if (myproc()->sig_handler[sig] == 0 && sig_default_kills(sig)) { + if (myproc()->sighand->handler[sig] == 0 && sig_default_kills(sig)) { myproc()->killed = 1; if (myproc()->killsig == 0) myproc()->killsig = sig; @@ -283,8 +283,8 @@ deliver_signals(struct trapframe *tf) // shifted rsp, and its ret fell into the sigreturn trampoline, // silently undoing everything. exec() handles this same quirk by // setting tf->rcx = elf.entry. - tf->rip = myproc()->sig_handler[sig]; - tf->rcx = myproc()->sig_handler[sig]; + tf->rip = myproc()->sighand->handler[sig]; + tf->rcx = myproc()->sighand->handler[sig]; return; } } @@ -344,7 +344,7 @@ addr_t sys_rt_sigsuspend(void) uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask; int will_deliver = 0; for (int sig = 1; sig < 32; sig++) - if ((deliverable & (1ULL << sig)) && myproc()->sig_handler[sig]) + if ((deliverable & (1ULL << sig)) && myproc()->sighand->handler[sig]) { will_deliver = 1; break; } if (will_deliver && !myproc()->in_signal) {