`ps` listed the shell and nothing the shell had started -- background jobs
included, which is how it was noticed.
p->ctty was set by TIOCSCTTY and never inherited, so every child of a shell had
ctty 0. `ps` with no arguments shows only processes on *this* terminal, so a
job running on your terminal was invisible because the only process that had
ever claimed one was the shell itself.
The controlling terminal is a property of the session, not of the process that
happened to call the ioctl. It crosses fork() with pgid and sid, three lines
above where it now sits.
# sleep 60 & ; sleep 61 &
# ps
PID TTY TIME CMD
14 1:11 00:00:00 sleep
9 1:11 00:00:00 zsh
15 1:11 00:00:00 sleep
17 1:11 00:00:00 ps
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1399 lines
43 KiB
C
1399 lines
43 KiB
C
#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;
|
|
|
|
// Kernel stacks, one per ptable slot, statically.
|
|
//
|
|
// **They used to be a single kalloc() page**, and a measurement said that was
|
|
// not enough: painting the stack at allocation and scanning it on every trap
|
|
// put the high-water mark at 3736 of 4096 bytes -- 91%, in netd, inside lwIP's
|
|
// input path. 360 bytes of headroom on a kernel that takes interrupts on the
|
|
// current stack is not headroom. A timer landing at that depth pushes a
|
|
// trapframe and runs trap() straight off the bottom of the page and into
|
|
// whatever kalloc handed out next, which is very often another process's
|
|
// kernel stack.
|
|
//
|
|
// That is a precise match for the symptom that had been written off as a
|
|
// mystery: roughly one shell in four boots died with `trap 14 ... rip 0x0`, a
|
|
// user-mode instruction fetch at address zero, having "jumped" somewhere it
|
|
// never branched to. A process does not corrupt itself into that state; it
|
|
// gets corrupted.
|
|
//
|
|
// The canary at kstack[0] did not catch it, and could not have. It is checked
|
|
// on trap entry and exit, and a frame allocated in one `sub $N, %rsp` can step
|
|
// clean over the bottom eight bytes and write below them without ever touching
|
|
// the canary. It catches slow leaks past the bottom, not one deep call.
|
|
//
|
|
// Static and per-slot rather than a bigger kalloc, because kalloc() hands out
|
|
// exactly one page and there is no contiguous multi-page allocator to ask. A
|
|
// process's ptable slot is fixed for its lifetime, so the stack that belongs
|
|
// to that slot is the natural answer: no allocation to fail, no free to
|
|
// forget, and the one kalloc failure path in allocproc() disappears with it.
|
|
//
|
|
// The cost is NPROC * KSTACKSIZE = 1 MB of BSS, paid at link time. That is
|
|
// what a kernel stack per process costs; it was being paid out of kalloc
|
|
// before, one page at a time, and undercounted.
|
|
__attribute__((aligned(PGSIZE)))
|
|
static char kstacks[NPROC][KSTACKSIZE];
|
|
|
|
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->utime = p->stime = p->cutime = p->cstime = 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);
|
|
|
|
// The stack that belongs to this slot. Cannot fail, which is why there is no
|
|
// failure branch here any more -- see kstacks[] above.
|
|
p->kstack = kstacks[p - ptable.proc];
|
|
// Canary at the very bottom: a slow walk past the end clobbers this first,
|
|
// and it is checked on every trap()/syscall() entry. It does *not* catch a
|
|
// single frame large enough to step over it, which is the case kstacks[]
|
|
// being two pages is there to prevent.
|
|
*(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){
|
|
np->kstack = 0; // the slot keeps its stack; see kstacks[]
|
|
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);
|
|
np->kstack = 0; // the slot keeps its stack; see kstacks[]
|
|
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;
|
|
// The controlling terminal is a property of the *session*, so it crosses
|
|
// fork() with the session id rather than being re-established. It was set
|
|
// only by TIOCSCTTY and never inherited, so every child of a shell had
|
|
// ctty 0 -- which is why plain `ps` listed the shell and nothing it had
|
|
// started, background jobs included. A job that is running on your terminal
|
|
// is on your terminal whoever forked it.
|
|
np->ctty = myproc()->ctty;
|
|
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, 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, uint64 *ut, uint64 *st)
|
|
{
|
|
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
|
|
// RUSAGE_CHILDREN: the child's own time *and* whatever it had already
|
|
// reaped, which is what makes the total transitive down a whole tree
|
|
// of processes rather than one generation deep.
|
|
myproc()->cutime += p->utime + p->cutime;
|
|
myproc()->cstime += p->stime + p->cstime;
|
|
// wait4()'s fourth argument wants the *child's* usage, not the
|
|
// parent's running total, so it has to be read here -- three lines
|
|
// further down the slot is UNUSED and these are zero.
|
|
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);
|
|
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);
|
|
}
|
|
// Which device the root filesystem is on.
|
|
//
|
|
// It was `#define ROOTDEV 1` -- IDE disk 1 -- so a kernel could only ever root
|
|
// on the disk it was *built* expecting. Booting the same image from a USB
|
|
// stick was impossible by construction, and that is the one thing standing
|
|
// between this kernel and its own acceptance test, which begins "boot on real
|
|
// hardware from a USB stick".
|
|
//
|
|
// A probe rather than a boot argument, because the answer is discoverable and
|
|
// asking someone to name a device number is a worse interface than looking.
|
|
// schfs_kmount() is already written to be used this way -- its own comment
|
|
// says "the uberblock scan doubles as the probe: no valid uberblock means this
|
|
// is simply not a SchFS volume, which is not an error worth reporting loudly"
|
|
// -- so each candidate costs one scan and answers yes or no. A failed probe
|
|
// leaves the slot unmounted and reusable, which is what makes a loop over
|
|
// candidates legal at all.
|
|
//
|
|
// **The order is preference, not confidence.** A ramdisk root is an explicit
|
|
// build choice and wins outright. After it, USB comes before the fixed disks:
|
|
// a stick that is plugged in was plugged in on purpose, and a machine with
|
|
// both should boot the thing the person is holding.
|
|
uint rootdev;
|
|
|
|
static int
|
|
rootdev_usable(uint dev)
|
|
{
|
|
if (dev == RAMDISK_DEV)
|
|
return ramdisk_present();
|
|
if (dev == USBDISK_DEV)
|
|
return usb_storage_present();
|
|
// iderw() panics on an absent drive rather than failing, so a probe has to
|
|
// ask first. That is the whole reason ide_disk_present() exists.
|
|
return ide_disk_present((int)dev);
|
|
}
|
|
|
|
static void
|
|
rootdev_mount(void)
|
|
{
|
|
static const struct { uint dev; const char *what; } cand[] = {
|
|
#ifdef RAMDISK_ROOT
|
|
{ RAMDISK_DEV, "ramdisk" },
|
|
#endif
|
|
{ USBDISK_DEV, "USB disk" },
|
|
{ 1, "IDE disk 1" },
|
|
{ 0, "IDE disk 0" },
|
|
};
|
|
|
|
for (int i = 0; i < (int)NELEM(cand); i++) {
|
|
if (!rootdev_usable(cand[i].dev))
|
|
continue;
|
|
schfs_kmount(cand[i].dev);
|
|
if (schfs_kmounted()) {
|
|
rootdev = cand[i].dev;
|
|
kinfo("root: %s (device %d)\n", cand[i].what, cand[i].dev);
|
|
return;
|
|
}
|
|
kinfo("root: %s (device %d) carries no SchFS volume\n",
|
|
cand[i].what, cand[i].dev);
|
|
}
|
|
|
|
// Name what was looked at. "no root filesystem" with no list is the least
|
|
// useful thing a kernel can say at the one moment somebody needs to know
|
|
// which disk it expected.
|
|
// Plain %s and no width. cprintf() has no field-width specifier -- it
|
|
// prints "%-11s" literally and then reads the *next* argument for the %d,
|
|
// which is how the first version of this message came out as
|
|
// `%-11s device 1596400`. See print_int() in kernel/core/console.c.
|
|
cprintf("root: no SchFS volume on any candidate device. Looked at:\n");
|
|
for (int i = 0; i < (int)NELEM(cand); i++)
|
|
cprintf(" device %d, %s: %s\n", cand[i].dev, cand[i].what,
|
|
rootdev_usable(cand[i].dev) ? "present, but no SchFS on it"
|
|
: "not present");
|
|
panic("no root filesystem");
|
|
}
|
|
|
|
|
|
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, on whichever device turns out to have
|
|
// one. Here rather than in main() because mounting reads blocks, bread()
|
|
// sleeps, and sleeping needs a process context.
|
|
rootdev_mount();
|
|
// 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) {
|
|
p->kstack = 0; // the slot keeps its stack; see kstacks[]
|
|
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;
|
|
out->ctty = p->ctty;
|
|
out->utime = p->utime;
|
|
out->stime = p->stime;
|
|
out->cutime = p->cutime;
|
|
out->cstime = p->cstime;
|
|
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;
|
|
}
|