#include "types.h" #include "defs.h" #include "param.h" #include "memlayout.h" #include "mmu.h" #include "x86.h" #include "traps.h" #include "proc.h" #include "spinlock.h" #include "sleeplock.h" #include "sbuf.h" #include "dbg.h" #include "fs.h" #include "file.h" #include "posix.h" #include "errno.h" // Verify myproc()->fs_base offset matches the hardcoded 0x688 in trapasm.S. _Static_assert(__builtin_offsetof(struct proc, fs_base) == 0x18, "myproc()->fs_base offset changed — update trapasm.S WRITE_USER_FS macro (must be 0x18)"); struct { struct spinlock lock; struct proc proc[NPROC]; } ptable; static struct proc *initproc; int nextpid = 1; extern void forkret(void); extern void syscall_trapret(void); static void wakeup1(void *chan); void kstack_check_canary(const char *where) { if(myproc() == 0 || myproc()->kstack == 0) return; if(*(uint64*)(myproc()->kstack) != KSTACK_CANARY){ cprintf("kstack canary corrupted for pid %d (%s) at %s: got 0x%p\n", myproc()->pid, myproc()->name, where, *(uint64*)(myproc()->kstack)); panic("kstack overflow"); } } // --- what the debugger can be told about processes -------------------------- // // docs/architecture/debugging.md, D2. These are here rather than in kernel/dev/dbg.c // because this file owns ptable, and a view written anywhere else would need // the lock and the struct exported to get at it. One line at the bottom of the // file that owns the data is the whole discipline. static const char * state_name(enum procstate s) { switch (s) { case UNUSED: return "unused"; case EMBRYO: return "embryo"; case SLEEPING: return "sleep"; case RUNNABLE: return "ready"; case RUNNING: return "run"; case ZOMBIE: return "zombie"; default: return "?"; } } // "4" means only pid 4. Not a general query language -- just the one filter // that stops a table from being unreadable on a busy machine. static int want_pid(const char *arg) { int v = 0; if (!arg || !*arg) return 0; // no filter while (*arg >= '0' && *arg <= '9') v = v * 10 + (*arg++ - '0'); return v; } static void proc_dump(struct sbuf *out, const char *arg) { struct proc *p; int only = want_pid(arg); sb_puts(out, " pid ppid pgid state sz(KB) mmap_next " "name\n"); acquire(&ptable.lock); for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED) continue; if (only && p->pid != only) continue; sb_putu_pad(out, p->pid, 5); sb_putu_pad(out, p->parent ? p->parent->pid : 0, 6); sb_putu_pad(out, p->pgid, 6); sb_puts(out, " "); 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_puts(out, " "); sb_puts(out, p->name); if (p->killed) sb_puts(out, " [killed]"); if (p->wedged) sb_puts(out, " [wedged]"); sb_putc(out, '\n'); } release(&ptable.lock); } // Every mapping every process holds that it does not own the pages of. // // This is the table that decides whether exit() frees a page or leaves it // alone -- see proc_release_shared() in kernel/sys/sys_mem.c -- and the bug // that made the debugger worth building was a DMA block that was never in it. // A view of it is the cheapest possible check that the rule is being kept. static void vma_dump(struct sbuf *out, const char *arg) { struct proc *p; int any = 0; int only = want_pid(arg); sb_puts(out, " pid slot va bytes " "no_free shmid name\n"); acquire(&ptable.lock); for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED) continue; if (only && p->pid != only) continue; for (int i = 0; i < NVMA; i++) { if (!p->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); else sb_puts(out, " -"); sb_puts(out, " "); sb_puts(out, p->name); sb_putc(out, '\n'); } } release(&ptable.lock); if (!any) sb_puts(out, only ? " (that pid holds no mappings)\n" : " (no process is holding a mapping)\n"); } // Every page table that maps this physical page, and which VMA it falls in. // // This is the half of `whose` that no allocator can answer. An allocator knows // whether it has handed a page out; it has no idea whether anyone still has it // mapped, and "free, but three processes are still writing to it" is the exact // shape of the bug that prompted all of this. // // The walk is forward -- there is no reverse map -- so it costs one pass over // every mapped page of every live process. See vm_walk_user(). struct maphit { struct sbuf *out; struct whose *w; struct proc *p; }; static void maphit(void *ctx, uint64 va, uint64 pa, uint64 pte) { struct maphit *m = (struct maphit *)ctx; if (pa != m->w->phys) return; m->w->mappings++; sb_puts(m->out, " mapped pid "); sb_putu(m->out, m->p->pid); sb_puts(m->out, " ("); sb_puts(m->out, m->p->name); sb_puts(m->out, ") at 0x"); sb_puthex(m->out, va, 16); sb_puts(m->out, (pte & PTE_W) ? " rw" : " r-"); sb_puts(m->out, (pte & PTE_U) ? "u" : "-"); // And whether the process admits to holding it. A mapping with no VMA is // how the DMA blocks used to look, which is why exit() freed them twice. { int i, found = 0; for (i = 0; i < NVMA; i++) { if (!m->p->vma[i].used) continue; if (va < m->p->vma[i].addr || va >= m->p->vma[i].addr + m->p->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" : "]"); break; } if (!found) { if (va < m->p->sz) sb_puts(m->out, " heap"); else sb_puts(m->out, " NO VMA -- exit() will free this page"); } } sb_putc(m->out, '\n'); } static void proc_whose(struct sbuf *out, struct whose *w) { struct proc *p; acquire(&ptable.lock); for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { struct maphit m; if (p->state == UNUSED || p->pgdir == 0) continue; m.out = out; m.w = w; m.p = p; vm_walk_user(p->pgdir, maphit, &m); } release(&ptable.lock); } void pinit(void) { 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"); dbg_register_whose("mapped", proc_whose); } static struct proc* allocproc(void) { struct proc *p; char *sp; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) if(p->state == UNUSED) goto found; release(&ptable.lock); return 0; found: p->state = EMBRYO; p->pid = nextpid++; // ptable slots get reused once a previous occupant is reaped, but // nothing previously cleared the signal-delivery fields added for // SIGCHLD (sig_handler/sig_restorer/pending_signals/signal_mask/ // in_signal/saved_sigtf) — a freshly allocated process would silently // inherit whatever a totally unrelated earlier process left behind, // 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->signal_mask = 0; p->in_signal = 0; p->sigsusp_oldmask = 0; p->sigsusp_active = 0; // Same reasoning as the signal fields above: a recycled slot must not // inherit a dead process's identity or exit status. p->xstate = 0; p->killsig = 0; p->pgid = 0; p->sid = 0; p->uid = p->euid = p->suid = 0; p->gid = p->egid = p->sgid = 0; release(&ptable.lock); if((p->kstack = kalloc()) == 0){ p->state = UNUSED; return 0; } // Canary at the very bottom of the kstack page: if a deep/nested trap // ever overflows this 4096-byte stack, this is the first thing clobbered. // Checked on every trap()/syscall() entry (kstack_check_canary()). *(uint64*)(p->kstack) = KSTACK_CANARY; sp = p->kstack + KSTACKSIZE; sp -= sizeof(struct trapframe); p->tf = (struct trapframe*)sp; sp -= sizeof(addr_t); *(addr_t*)sp = (addr_t)syscall_trapret; sp -= sizeof(struct context); p->context = (struct context*)sp; memset(p->context, 0, sizeof *p->context); p->context->rip = (addr_t)forkret; p->syscall_map = 0; p->map_size = 0; p->mmap_next = 0; p->kfn = 0; memset(p->ofile_flags, 0, sizeof(p->ofile_flags)); return p; } void sinit(void) { struct proc *p; extern char _binary_initcode_start[], _binary_initcode_size[]; p = allocproc(); initproc = p; if((p->pgdir = setupkvm()) == 0) panic("sinit: setupkvm failed"); inituvm(p->pgdir, _binary_initcode_start, (uint64)_binary_initcode_size); if(allocuvm(p->pgdir, PGSIZE, 32 * PGSIZE) == 0) panic("sinit: allocuvm failed"); p->sz = 32 * PGSIZE; memset(p->tf, 0, sizeof(*p->tf)); p->tf->cs = (SEG_UCODE << 3) | DPL_USER; p->tf->ss = (SEG_UDATA << 3) | DPL_USER; p->tf->rflags = FL_IF; p->tf->rsp = p->sz; p->tf->rip = 0; // PID 1 leads its own session and process group, and runs as root. p->pgid = p->pid; p->sid = p->pid; p->uid = p->euid = p->suid = 0; p->gid = p->egid = p->sgid = 0; safestrcpy(p->name, "sinit", sizeof(p->name)); p->cwd = namei("/"); p->cwd_path[0] = '/'; p->cwd_path[1] = 0; p->fs_base = 0; p->state = RUNNABLE; } int growproc(int64 n) { addr_t sz; sz = myproc()->sz; if(n > 0){ if((sz = allocuvm(myproc()->pgdir, sz, sz + n)) == 0) return -1; } else if(n < 0){ if((sz = deallocuvm(myproc()->pgdir, sz, sz + n)) == 0) return -1; } myproc()->sz = sz; switchuvm(myproc()); return 0; } int fork(void) { int i, pid; struct proc *np; if((np = allocproc()) == 0) return -1; if((np->pgdir = copyuvm(myproc()->pgdir, myproc()->sz)) == 0){ kfree(np->kstack); np->kstack = 0; np->state = UNUSED; return -1; } np->sz = myproc()->sz; np->parent = 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)); for(i = 0; i < NVMA; i++){ if(!myproc()->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); kfree(np->kstack); np->kstack = 0; np->state = UNUSED; return -1; } np->vma[i] = myproc()->vma[i]; if(np->vma[i].shmid >= 0) shm_ref(np->vma[i].shmid); } np->mmap_next = myproc()->mmap_next; memmove(np->tf, myproc()->tf, sizeof(struct trapframe)); np->syscall_map = myproc()->syscall_map; np->map_size = myproc()->map_size; // fork() preserves the parent's signal handlers and mask (POSIX); the // 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_restorer, myproc()->sig_restorer, sizeof(np->sig_restorer)); np->signal_mask = myproc()->signal_mask; np->tf->rax = 0; 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]; } // POSIX: the child inherits process group, session and all four ids. np->pgid = myproc()->pgid; np->sid = myproc()->sid; np->uid = myproc()->uid; np->euid = myproc()->euid; np->suid = myproc()->suid; np->gid = myproc()->gid; np->egid = myproc()->egid; np->sgid = myproc()->sgid; np->cwd = idup(myproc()->cwd); memmove(np->cwd_path, myproc()->cwd_path, sizeof(myproc()->cwd_path)); np->fs_base = myproc()->fs_base; safestrcpy(np->name, myproc()->name, sizeof(myproc()->name)); pid = np->pid; __sync_synchronize(); np->state = RUNNABLE; return pid; } // status is already wait(2)-encoded -- see W_EXITCODE() in proc.h. Callers // that die from a fault or signal pass W_EXITCODE(0, sig); a normal _exit(n) // passes W_EXITCODE(n, 0). // PID 1 is a role, and the kernel guarantees the role is filled. // // This is the rule change docs/architecture/init.md exists for. Unix's rule is "PID 1 // is special and must never die", honoured since 1975 by being careful -- // which is not a mechanism, it is a hope, and the thing it hopes about is the // one program on the machine with no supervisor. // // So instead: tear nothing down, keep pid 1, and re-exec in place. // // The reason this is so short is the reason it is the right design. exec() // already replaces the address space, and everything else is *deliberately* // left alone: // // - the pid stays 1, so every orphan the kernel reparented to initproc is // still correctly parented and initproc is still the pointer it was // - the children survive, because nothing killed them // - the open files survive, so the new image still has a console to // complain to -- this runs before exit() closes anything, which is why // the call site is the first line of exit() and not the last // - the state survives, in /proc/schinit, which is the whole reason the // job table lives outside the process holding it // // A sibling init resurrecting a dead one would have to solve all four of those // and could not solve the first at all. This solves them by not creating them. // // The escalation ladder is what makes it safe to have. A machine that cannot // hold PID 1 up is not a machine that should keep trying for ever, and PSOGM // renders a stop code, a call chain and a QR code of the whole report -- which // is a far better end than a silent loop. // // generation 1 normal boot // generation 2-3 re-exec, keep the jobs, carry on // generation 4 re-exec, but ignore /etc/schinit entirely and start only // a console shell: a manifest that kills init must not // kill it for ever // generation 5 stop uint init_generation = 1; int proc_reincarnate(int status) { static char gen[12]; char *argv[3]; int n = 0, v; init_generation++; if (init_generation > 5) panic("init resurrection failed: five generations is enough"); // Decimal, by hand: this runs in a process that is dying and should call as // little as possible. v = (int)init_generation; if (v == 0) { gen[n++] = '0'; } else { char tmp[12]; int i = 0; while (v > 0) { tmp[i++] = '0' + (v % 10); v /= 10; } while (i-- > 0) gen[n++] = tmp[i]; } gen[n] = 0; cprintf("init: pid 1 exited with status %d -- reincarnating, generation %d\n", status, init_generation); if (init_generation >= 4) cprintf("init: generation %d starts a console shell only, " "ignoring /etc/schinit\n", init_generation); argv[0] = "schinit"; argv[1] = gen; argv[2] = 0; // Clear `killed` first. // // exit() was reached from the trap-return path *because* killed was set, and // that path checks it again on the way back out. Leaving it set would exit // the brand-new image before its first instruction, and the generation // counter would climb to five in a few microseconds -- a resurrection loop // that looks exactly like the crash it was meant to survive. myproc()->killed = 0; // No lock is held here and none may be taken: this is the first line of // exit(), before the file table is walked and before ptable.lock. exec() // does its own begin_op/end_op and is safe from process context. if (exec("/bin/schinit", argv, 0) < 0) return -1; return 0; } void exit(int status) { struct proc *p; int fd; if(myproc() == initproc) { // Returning from exit() is the *success* path here. exec() does not fail // to return -- it returns 0 having replaced the address space, and the new // image begins running when the kernel returns to userspace through the // trapframe it rewrote. So there is nothing more to do but go back. if (proc_reincarnate(status) == 0) return; // Only reached if the re-exec itself failed, which is the one case where // there is genuinely nothing left to try. panic("init resurrection failed: could not exec /bin/schinit"); } // 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. pciuser_release_all(myproc()); // And any interface it was acting as. Same reason: a driver that crashed // must not leave a link that claims to be up. See kernel/dev/netuser.c. netuser_release_all(myproc()); 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; } } begin_op(); iput(myproc()->cwd); end_op(); myproc()->cwd = 0; acquire(&ptable.lock); // Tell the parent a child exited — needed for mksh's sigsuspend()-based // job-control wait loop to have any chance of making progress (without // this, sigsuspend() can never return for the right reason and the // parent hangs unconditionally). Signal delivery itself is verified // working (handler invocation + sigreturn round-trip is clean); mksh's // own job-control bookkeeping had further gaps (see docs/history/kernel.md // §7) that this alone did not resolve, but // disabling it guarantees no progress, whereas leaving it enabled at // least allows the cases that don't hit those gaps to work. if (myproc()->parent) myproc()->parent->pending_signals |= (1ULL << SIGCHLD); wakeup1(myproc()->parent); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->parent == myproc()){ p->parent = initproc; if(p->state == ZOMBIE) wakeup1(initproc); } } myproc()->state = ZOMBIE; sched(); panic("zombie exit"); } int wait(void) { return waitpid(-1, 0, 0); } // waitpid(2)/wait4(2) proper: a specific pid (-1 = any child, matching // wait()'s old behavior), an optional status-out pointer (NULL is valid — // POSIX callers commonly do waitpid(pid, NULL, WNOHANG)), and WNOHANG. // // This used to be missing: sys_waitpid()/sys_wait4() both ignored pid and // options and called the any-child, always-blocking wait() above. A NULL // status pointer additionally got rejected by argptr() (page 0 is // unmapped) before ever reaching here. Together, a caller doing // waitpid(child, NULL, WNOHANG) right after a child exits — exactly what // mksh's job control does — always failed instead of immediately reaping // 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. int waitpid(int pid, int *status, int options) { struct proc *p; int havekids; acquire(&ptable.lock); for(;;){ havekids = 0; for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->parent != myproc()) continue; // POSIX pid selection: >0 one child, 0 = caller's group, // -1 = any child, < -1 = the process group with id -pid. if(pid > 0 && p->pid != pid) continue; if(pid == 0 && p->pgid != myproc()->pgid) continue; if(pid < -1 && p->pgid != -pid) continue; havekids = 1; if(p->state == ZOMBIE){ int rpid = p->pid; int rstatus = p->xstate; // copy out before the slot is recycled kfree(p->kstack); p->kstack = 0; // 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); p->pid = 0; p->parent = 0; p->name[0] = 0; p->killed = 0; p->xstate = 0; p->killsig = 0; p->pgid = 0; p->sid = 0; p->state = UNUSED; release(&ptable.lock); if (status) *status = rstatus; return rpid; } } // Two different failures, and they used to share one bare -1. // // musl reads a return in [-4095, -1] as -errno, so -1 arrived in // userspace as EPERM: "operation not permitted". zsh's compinit calls // wait, got that, printed "wait failed: operation not permitted", and // abandoned the rest of /etc/zsh/zshrc -- which looked like a broken // config file rather than a wait() that could not say what went wrong. if(!havekids){ release(&ptable.lock); return -ECHILD; // nothing to wait for, which is not an error } if(myproc()->killed){ release(&ptable.lock); return -EINTR; // a signal arrived while waiting } if (options & 1) { // WNOHANG release(&ptable.lock); return 0; } sleep(myproc(), &ptable.lock); } } // Stop a process running, by name, and never start it again. // // This exists for one reason: docs/architecture/init.md says the heartbeat's // timeout path should be "exercised rather than assumed", and a hang is the // one failure that cannot be produced by killing something. A wedged process // is still alive, still has its descriptors open, and still shows up in every // listing -- which is exactly what makes it hard to notice and exactly what // the heartbeat is for. // // Deliberately one-way. There is no unwedge: this is a test instrument for a // machine that is about to be rebooted, not a job-control mechanism. Real // SIGSTOP/SIGCONT is a separate thing and is not implemented. int proc_wedge(const char *name) { struct proc *p; int found = 0; acquire(&ptable.lock); for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED || p->pid <= 1) continue; if (strncmp(p->name, name, sizeof(p->name)) != 0) continue; p->wedged = 1; found = p->pid; break; } release(&ptable.lock); return found; } void scheduler(void) { struct proc *p; int skipped = 0; for(;;){ sti(); acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ // A wedged process is passed over for ever. It is still RUNNABLE, still // owns its descriptors and still appears in every listing -- which is // the point: it looks exactly like a process that is merely busy. if(p->state != RUNNABLE || p->wedged) { skipped++; continue; } skipped = 0; proc_set(p); switchuvm(p); p->state = RUNNING; swtch(&mycpu()->scheduler, p->context); switchkvm(); proc_set(0); } release(&ptable.lock); if (skipped > NPROC) { hlt(); skipped = 0; } } } void sched(void) { int intena; if(!holding(&ptable.lock)) panic("sched ptable.lock"); if(mycpu()->ncli != 1) panic("sched locks"); if(myproc()->state == RUNNING) panic("sched running"); if(readeflags()&FL_IF) panic("sched interruptible"); intena = mycpu()->intena; swtch(&myproc()->context, mycpu()->scheduler); mycpu()->intena = intena; } void yield(void) { acquire(&ptable.lock); myproc()->state = RUNNABLE; sched(); release(&ptable.lock); } void forkret(void) { static int first = 1; release(&ptable.lock); // A kernel thread starts here and never leaves. It shares the scheduler's // rotation with everything else, so it can hold sleeplocks and block on the // disk exactly as a syscall would -- which is the whole reason it is a // process rather than a timer callback. // // Ahead of the first-boot block below on purpose: a kernel thread is only // ever created long after the root filesystem is mounted, so it must not be // the one that runs that setup. if (myproc()->kfn) { myproc()->kfn(); panic("kthread returned"); } if (first) { first = 0; iinit(); mountinit(); // SchFS is the root filesystem. // // Mounted here rather than in main() because mounting reads blocks, // bread() sleeps, and sleeping needs a process context. // // schfs_kmount() reports failure by leaving the volume unmounted rather // than by returning -- it is a probe elsewhere, where "not SchFS" is an // ordinary answer. Here it is fatal, so ask afterwards. schfs_kmount(ROOTDEV); if (!schfs_kmounted()) panic("schfs root: no SchFS volume on the root device"); // Re-point pid 1's cwd at the real root, now that there is one. // // sinit() sets cwd = namei("/") back in main(), which is before this runs. // The old v1 root survived that because iget(ROOTDEV, ROOTINO) is two // compile-time constants and touches no disk -- the inode is filled in // lazily at the first ilock(). SchFS has no constant to offer: the root // object id comes off the uberblock, so namei("/") that early returns an // inode built from whatever schfs_kroot() reads out of an unmounted // volume. // // Nothing complains, because absolute paths never consult cwd -- exec // "/bin/init" works fine. It is the first *relative* lookup that fails, // and in init that is open("console"): no console means no fd 0/1/2, so // every shell it spawns exits immediately and init respawns it forever. // The symptom is a silent boot with no init: ready, which reads like a // hang and is not one. { struct inode *stale = myproc()->cwd; myproc()->cwd = iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV)); if (stale) iput(stale); } } } // Start a kernel thread: a process that runs `fn` in kernel mode and never // returns to userland. // // It gets a page directory of its own with only the kernel mapped, because the // scheduler calls switchuvm() for every process it runs and that panics on a // null pgdir. Sharing kpgdir directly would work equally well; a private one // costs a page and keeps the "every proc owns its pgdir" invariant intact. // // No files, no cwd, no parent: it is not in the process tree, cannot be waited // for and must never exit. Returns the pid, or -1. int kthread_create(void (*fn)(void), const char *name) { struct proc *p; if ((p = allocproc()) == 0) return -1; if ((p->pgdir = setupkvm()) == 0) { kfree(p->kstack); p->kstack = 0; p->state = UNUSED; return -1; } p->sz = PGSIZE; // never used; 0 upsets code that treats it as a size p->parent = 0; p->cwd = 0; p->kfn = fn; safestrcpy(p->name, name, sizeof(p->name)); acquire(&ptable.lock); p->state = RUNNABLE; release(&ptable.lock); return p->pid; } void sleep(void *chan, struct spinlock *lk) { if(myproc() == 0) panic("sleep"); if(lk == 0) panic("sleep without lk"); if(lk != &ptable.lock){ acquire(&ptable.lock); release(lk); } myproc()->chan = chan; myproc()->state = SLEEPING; sched(); myproc()->chan = 0; if(lk != &ptable.lock){ release(&ptable.lock); acquire(lk); } } static void wakeup1(void *chan) { struct proc *p; for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) if(p->state == SLEEPING && p->chan == chan) p->state = RUNNABLE; } void wakeup(void *chan) { acquire(&ptable.lock); wakeup1(chan); release(&ptable.lock); } // Raise a signal on a process: set the pending bit and wake it if it's // sleeping so blocking syscalls (sigsuspend, pause, read) re-check their // conditions promptly. Signals with no handler installed fall back to the // default action — termination for the fatal set, dropped otherwise. // mksh depends on kill(getpid(), SIGCHLD) genuinely re-raising SIGCHLD // (its held_sigchld recovery in j_startjob); the previous sys_kill // silently discarded every non-fatal signal. // PID 1 does not die from a signal it never asked to handle. // // POSIX, and Linux in sig_task_ignored(): a signal whose disposition is the // default action is discarded for init rather than delivered. Without this the // system has a single keystroke that kills it -- Ctrl-C sends SIGINT to the // foreground process group, init is pgid 1, and any moment where a terminal's fg_pgrp // is 1 (before a shell claims the terminal, after one exits, or in the window // between the two while the key is held down) lands SIGINT on init. init has // no handler, so killed is set, exit() runs, and exit() panics outright on // initproc: "init exiting". Spamming Ctrl-C at a frozen program is exactly the // input that finds that window. // // Applies to SIGKILL too, which is the point: init is not killable from // userspace on any Unix. A handler it did install is still delivered. static int init_ignores(struct proc *p, int sig) { return p == initproc && p->sig_handler[sig] == 0; } int sigsend(int pid, int sig) { struct proc *p; if(sig <= 0 || sig >= 32) return -1; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){ if(init_ignores(p, sig)){ release(&ptable.lock); return 0; // silently dropped, as POSIX requires } p->pending_signals |= 1ULL << sig; if(p->sig_handler[sig] == 0 && (sig == SIGKILL || sig == SIGTERM || sig == SIGABRT || sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS || sig == SIGILL || sig == SIGINT || sig == SIGQUIT || sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 || sig == SIGUSR2 || sig == SIGALRM)) p->killed = 1; if(sig == SIGKILL) p->killed = 1; if(p->killed && p->killsig == 0) p->killsig = sig; // so exit() can report the real cause if(p->state == SLEEPING) p->state = RUNNABLE; release(&ptable.lock); return 0; } } release(&ptable.lock); return -1; } // Deliver a signal to every member of a process group. This is what job // control actually needs: a shell sends SIGINT/SIGTSTP/SIGHUP to a *group*, // never to individual pids. sys_kill() with pid <= 0 routes here. // // Returns 0 if at least one process was signalled, -1 if the group is empty. int sigsend_pgrp(int pgid, int sig) { struct proc *p; int found = 0; if(sig < 0 || sig >= 32) return -1; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state == UNUSED || p->state == ZOMBIE) continue; if(p->pgid != pgid) continue; found = 1; if(sig == 0) continue; // signal 0 is an existence probe only if(init_ignores(p, sig)) continue; // see init_ignores(): this is the Ctrl-C case p->pending_signals |= 1ULL << sig; if(p->sig_handler[sig] == 0 && (sig == SIGKILL || sig == SIGTERM || sig == SIGABRT || sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS || sig == SIGILL || sig == SIGINT || sig == SIGQUIT || sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 || sig == SIGUSR2 || sig == SIGALRM)){ p->killed = 1; if(p->killsig == 0) p->killsig = sig; } if(sig == SIGKILL){ p->killed = 1; if(p->killsig == 0) p->killsig = sig; } if(p->state == SLEEPING) p->state = RUNNABLE; } release(&ptable.lock); return found ? 0 : -1; } // Look up a process's process-group id. Returns -1 if no such live process. int proc_pgid(int pid) { struct proc *p; int r = -1; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){ r = p->pgid; break; } } release(&ptable.lock); return r; } // setpgid(pid, pgid) with POSIX's argument shorthands: pid 0 means the caller, // pgid 0 means "use pid as the group id" (i.e. become a group leader). int proc_setpgid(int pid, int pgid) { struct proc *p; int r = -ESRCH; if(pid < 0 || pgid < 0) return -EINVAL; if(pid == 0) pid = myproc()->pid; if(pgid == 0) pgid = pid; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->pid != pid || p->state == UNUSED || p->state == ZOMBIE) continue; // A caller may only move itself or one of its own children, and never // across a session boundary. if(p != myproc() && p->parent != myproc()) r = -ESRCH; else if(p->sid != myproc()->sid) r = -EPERM; else { p->pgid = pgid; r = 0; } break; } release(&ptable.lock); return r; } // setsid(): become leader of a brand-new session and process group. Fails if // the caller already leads a process group, since that group would otherwise // end up split across two sessions. int proc_setsid(void) { struct proc *p; int r; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state != UNUSED && p->state != ZOMBIE && p->pgid == myproc()->pid){ release(&ptable.lock); return -EPERM; } } myproc()->sid = myproc()->pid; myproc()->pgid = myproc()->pid; r = myproc()->sid; release(&ptable.lock); return r; } int kill(int pid) { struct proc *p; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->pid == pid){ p->killed = 1; if(p->state == SLEEPING) p->state = RUNNABLE; release(&ptable.lock); return 0; } } release(&ptable.lock); return -1; } void procdump(void) { static char *states[] = { [UNUSED] "unused", [EMBRYO] "embryo", [SLEEPING] "sleep ", [RUNNABLE] "runble", [RUNNING] "run ", [ZOMBIE] "zombie" }; int i; struct proc *p; char *state; addr_t pc[10]; for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state == UNUSED) continue; if(p->state >= 0 && p->state < NELEM(states) && states[p->state]) state = states[p->state]; else state = "???"; cprintf("%d %s %s", p->pid, state, p->name); if(p->state == SLEEPING){ getstackpcs((addr_t*)p->context->rbp, pc); for(i=0; i<10 && pc[i] != 0; i++) cprintf(" %p", pc[i]); } cprintf("\n"); } } // --------------------------------------------------------------------------- // Snapshots, for /proc // // procfs must not walk ptable itself: it would need the lock, the struct // layout, and a rule about what happens when a process exits between the // readdir that listed it and the open that reads it. These two copy what is // needed out from under the lock and hand back a value, so a process that dies // mid-listing produces a stale line rather than a use-after-free. // --------------------------------------------------------------------------- // Fill `out` with the `idx`-th live process (0-based, in table order). // Returns 0 on success, -1 when idx is past the end. int proc_snapshot(int idx, struct procinfo *out) { struct proc *p; int n = 0; acquire(&ptable.lock); for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED) continue; if (n++ != idx) continue; out->pid = p->pid; out->ppid = p->parent ? p->parent->pid : 0; out->pgid = p->pgid; out->sid = p->sid; out->uid = p->uid; out->state = p->state; out->sz = p->sz; out->killed = p->killed; safestrcpy(out->name, p->name, sizeof(out->name)); safestrcpy(out->exe, p->exe, sizeof(out->exe)); release(&ptable.lock); return 0; } release(&ptable.lock); return -1; } // The same, by pid rather than index. int proc_snapshot_pid(int pid, struct procinfo *out) { int i; struct procinfo pi; for (i = 0; proc_snapshot(i, &pi) == 0; i++) if (pi.pid == pid) { *out = pi; return 0; } return -1; } int proc_num(void) { struct proc *p; int num = 0; acquire(&ptable.lock); for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) if (p->state != UNUSED) num++; release(&ptable.lock); return num; } int proc_exists(int pid) { struct proc *p; acquire(&ptable.lock); for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->pid == pid && p->state != UNUSED && p->state != ZOMBIE) { release(&ptable.lock); return 1; } } release(&ptable.lock); return 0; } // Make PID 1 exit at its next trap return, so proc_reincarnate() runs. // // Not a signal: init_ignores() drops signals to initproc and should, because // init is not killable from userspace on any Unix. This is the kernel // deciding on its own behalf, which is a different thing, and it exists so // the resurrection path can be exercised rather than reasoned about. void proc_kill_init(void) { if (initproc) initproc->killed = 1; }