Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
1828 lines
60 KiB
C
1828 lines
60 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "arch/memlayout.h"
|
|
#include "arch/mmu.h"
|
|
#include "arch/x86.h"
|
|
#include "arch/traps.h"
|
|
#include "core/proc.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "core/sbuf.h"
|
|
#include "core/dbg.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/file.h"
|
|
#include "uapi/posix.h"
|
|
#include "uapi/errno.h"
|
|
#include "fs/blkdev.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;
|
|
|
|
// A recycled slot must not inherit a dead process's place in the queue --
|
|
// in either direction. sched_init_proc starts it at min_vruntime, which is
|
|
// level with everything currently runnable: neither owed CPU nor in debt.
|
|
// fork() then copies the parent's vruntime over this, which is what stops a
|
|
// process from getting a fresh share of the CPU simply by forking.
|
|
sched_init_proc(p, 0);
|
|
memmove(p->fpu, fpu_default, sizeof(p->fpu));
|
|
|
|
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;
|
|
|
|
// The parent's registers are live in the CPU -- the kernel never touches
|
|
// 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;
|
|
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));
|
|
|
|
// The child inherits the parent's place in the queue, priority and all.
|
|
//
|
|
// Both halves matter. Inheriting *nice* is what POSIX says and what makes
|
|
// `nice make -j8` mean anything at all -- the point of renicing a build is
|
|
// that the compilers it forks are reniced too. Inheriting *vruntime* is the
|
|
// part with teeth: were the child to start level with everyone, a process
|
|
// could reset its own CPU debt by forking, and something that forks per
|
|
// request would collect a fresh full share every time while a long-running
|
|
// process kept paying for everything it had ever used.
|
|
sched_set_nice(np, myproc()->nice);
|
|
np->vruntime = myproc()->vruntime;
|
|
|
|
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;
|
|
}
|
|
|
|
// --- The FPU ------------------------------------------------------------
|
|
//
|
|
// Every process has its own x87 and SSE registers, and they were never saved.
|
|
// musl's memcpy, the compiler's struct copies and anything doing arithmetic on
|
|
// doubles keep live values in %xmm across a context switch, so two processes
|
|
// sharing a CPU wrote over each other's: two shells counting in a loop on one
|
|
// processor took a NULL+8 fault within seconds, at a different rip each time.
|
|
// It had always been there, and every crash with no cause anyone could find
|
|
// was a candidate.
|
|
//
|
|
// The kernel is built with -mgeneral-regs-only, so it never touches these
|
|
// registers itself and they stay the user's from trap entry to return. That
|
|
// makes saving them a switch-time job and nothing more: sched() saves the
|
|
// process leaving the CPU, scheduler() restores the one arriving. No lazy
|
|
// switching (CR0.TS and a #NM handler), which would save the copy for a
|
|
// process that never uses SSE -- there is no such process; musl's string
|
|
// functions use it.
|
|
__attribute__((aligned(16)))
|
|
const uint8 fpu_default[512] = {
|
|
[0] = 0x7F, [1] = 0x03, // FCW 0x037F
|
|
[24] = 0x80, [25] = 0x1F, // MXCSR 0x1F80
|
|
};
|
|
|
|
// --- The scheduler ------------------------------------------------------
|
|
//
|
|
// It was round-robin: walk ptable from end to end, run everything RUNNABLE in
|
|
// slot order, and let the timer tick take the CPU back after 10 ms. That is
|
|
// xv6's scheduler, and three things are wrong with it on a machine that now
|
|
// runs a shell, a network stack and a display server at the same time.
|
|
//
|
|
// **Slot order is not fairness.** A process that blocks on a read after one
|
|
// microsecond and a process that burns its whole slice are treated as equals,
|
|
// because nothing counts what anyone actually used. The compile and the shell
|
|
// you are typing into get alternate turns; the shell feels it.
|
|
//
|
|
// **Every tick was a context switch.** `yield()` unconditionally on the timer
|
|
// means a process alone on a CPU still pays a full switch -- `switchuvm`, a
|
|
// `%cr3` reload, a flushed TLB -- a hundred times a second, to be handed
|
|
// straight back to itself.
|
|
//
|
|
// **There is no way to say one thing matters more than another.** No nice, no
|
|
// priority, nothing for a driver thread that must answer a device promptly.
|
|
//
|
|
// ## What replaces it
|
|
//
|
|
// Fair share by *virtual runtime*, which is the idea behind both Linux's CFS
|
|
// and Zircon's fair scheduler, and is much simpler than either implementation
|
|
// of it. Each process carries `vruntime`: the CPU time it has consumed,
|
|
// divided by its weight. The scheduler always runs the RUNNABLE process with
|
|
// the smallest vruntime.
|
|
//
|
|
// That single rule produces everything the old one lacked:
|
|
//
|
|
// - **Fairness falls out.** Running advances your clock; the process that
|
|
// has had least CPU is by definition the one whose clock is furthest
|
|
// behind, so it runs next. Equal weights converge on equal shares without
|
|
// anything having to enforce it.
|
|
//
|
|
// - **Interactivity falls out too, with no heuristic.** A process that
|
|
// spends its life blocked on a keystroke barely advances its vruntime, so
|
|
// when the keystroke arrives it is already at the front. No "is this
|
|
// interactive?" guess, which is the part of older schedulers that was
|
|
// always either gameable or wrong.
|
|
//
|
|
// - **Priority is one multiplication.** Weight scales how fast your clock
|
|
// runs. A high-weight process's vruntime advances slowly, so it is chosen
|
|
// more often, in exact proportion.
|
|
//
|
|
// ## Deliberately not a red-black tree
|
|
//
|
|
// CFS keeps the runnable set in a red-black tree and Zircon in a WAVL tree,
|
|
// so "smallest vruntime" is O(log n) and the leftmost node is cached. Here it
|
|
// is a linear scan of ptable, O(NPROC), the same scan the old scheduler did.
|
|
//
|
|
// That is not laziness. NPROC is 128 slots of a few words each; the scan is a
|
|
// handful of cache lines and runs with the lock already held, whereas a
|
|
// balanced tree is three hundred lines of rotations that must stay correct
|
|
// through every exit path in this file. The scan is the right trade until
|
|
// something measures it as the problem, and the rule this kernel keeps is
|
|
// that the measurement comes first. Swapping in a tree later changes
|
|
// pick_next() and nothing else, which is the point of it being one function.
|
|
//
|
|
// ## Still one lock, still one queue
|
|
//
|
|
// Every CPU picks out of the same table under `ptable.lock`. Per-CPU run
|
|
// queues with work stealing are the other half of a real SMP scheduler and
|
|
// they are a separate change -- they need a load balancer, and a load
|
|
// balancer that is wrong is worse than a global lock that is merely
|
|
// contended. With the number of cores this actually runs on, the lock is not
|
|
// yet what hurts.
|
|
|
|
// Weight per nice level, indexed by nice + 20.
|
|
//
|
|
// The ratio between adjacent levels is 1.25, so one step of nice is about a
|
|
// 10% change in CPU share when two processes compete -- the convention UNIX
|
|
// has used for long enough that programs and people both expect it. The table
|
|
// is 1024 / 1.25^nice, rounded, with nice 0 at 1024 so the common case
|
|
// multiplies by one.
|
|
static const uint32 nice_weight[40] = {
|
|
88818, 71054, 56843, 45475, 36380, 29104, 23283, 18626,
|
|
14901, 11921, 9537, 7629, 6104, 4883, 3906, 3125,
|
|
2500, 2000, 1600, 1280, 1024, 819, 655, 524,
|
|
419, 336, 268, 215, 172, 137, 110, 88,
|
|
70, 56, 45, 36, 29, 23, 18, 15,
|
|
};
|
|
#define WEIGHT_NICE0 1024u
|
|
|
|
// The period in which every runnable process should get a turn, and the floor
|
|
// on how short a turn may be.
|
|
//
|
|
// A slice is SCHED_LATENCY_NS divided between the runnable processes, so with
|
|
// few of them each gets a long one and switches are rare; as the count grows
|
|
// the slice shrinks until it hits SCHED_MIN_GRAN_NS, below which the switch
|
|
// costs more than the work done in it and the period is allowed to stretch
|
|
// instead. 20 ms and 2 ms on a kernel whose timer runs at 100 Hz: the minimum
|
|
// is deliberately shorter than a tick, because the tick is when the decision
|
|
// is *checked*, not the granularity at which time is *measured*.
|
|
#define SCHED_LATENCY_NS 20000000ull
|
|
#define SCHED_MIN_GRAN_NS 2000000ull
|
|
|
|
// The floor of every runnable vruntime, which is what keeps a process that
|
|
// slept from being owed the world.
|
|
//
|
|
// Without it a process blocked for ten seconds comes back ten seconds of
|
|
// vruntime behind everything else and monopolises the CPU until it catches
|
|
// up -- the classic way a naive fair scheduler turns one sleeper into a
|
|
// system-wide stall. A waking process is pulled forward to no worse than
|
|
// min_vruntime - SCHED_LATENCY_NS: far enough ahead in the queue to be
|
|
// scheduled promptly, not so far as to be owed a debt.
|
|
//
|
|
// It only ever moves forward, and it is guarded by ptable.lock like
|
|
// everything else here.
|
|
static uint64 min_vruntime;
|
|
|
|
// TSC cycles to nanoseconds.
|
|
//
|
|
// tsc_hz is measured against the PIT at boot (kernel/arch/x86_64/lapic.c), so
|
|
// this is real time rather than a cycle count that means something different
|
|
// on every machine. The multiply is done in 64 bits after dividing the clock
|
|
// down to MHz: cycles * 1000 / mhz overflows only after some centuries of
|
|
// uptime, where cycles * 1000000000 would overflow in seconds.
|
|
static uint64
|
|
tsc_to_ns(uint64 cycles)
|
|
{
|
|
uint64 mhz = tsc_hz / 1000000;
|
|
if (mhz == 0)
|
|
return 0; // before lapicinit; nothing to charge yet
|
|
return (cycles * 1000) / mhz;
|
|
}
|
|
|
|
// Give a process its scheduling state. Called from allocproc, before it is
|
|
// ever RUNNABLE.
|
|
void
|
|
sched_init_proc(struct proc *p, int nice)
|
|
{
|
|
if (nice < -20) nice = -20;
|
|
if (nice > 19) nice = 19;
|
|
p->nice = nice;
|
|
p->weight = nice_weight[nice + 20];
|
|
p->vruntime = min_vruntime; // start level with everyone, owed nothing
|
|
p->ran_at = 0;
|
|
p->slice_ns = SCHED_MIN_GRAN_NS;
|
|
p->slice_used = 0;
|
|
p->nvcsw = p->nivcsw = 0;
|
|
}
|
|
|
|
// setpriority(2)/getpriority(2), the only way nice is ever set.
|
|
//
|
|
// PRIO_PROCESS only. PRIO_PGRP and PRIO_USER are real and are not hard, but
|
|
// answering them requires deciding what "the priority of a user" returns when
|
|
// their processes disagree, and a wrong answer is worse than -EINVAL.
|
|
//
|
|
// Lowering nice -- asking for *more* CPU -- is root's alone, which is the one
|
|
// permission check in this file that is actually enforced. Without it nice is
|
|
// not a courtesy, it is a way for any process to outrank the rest of the
|
|
// system by asking.
|
|
int
|
|
proc_setnice(int pid, int nice)
|
|
{
|
|
struct proc *p;
|
|
int r = -ESRCH;
|
|
|
|
if (nice < -20) nice = -20;
|
|
if (nice > 19) nice = 19;
|
|
|
|
acquire(&ptable.lock);
|
|
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
|
if (p->state == UNUSED || (pid != 0 && p->pid != pid))
|
|
continue;
|
|
if (pid == 0 && p != myproc())
|
|
continue;
|
|
|
|
// Two separate rules, and POSIX gives them two separate errnos.
|
|
//
|
|
// EPERM: you may only renice something you own. Without it any user can
|
|
// push every root process to nice 19 and have the run of the machine.
|
|
// EACCES: *lowering* nice -- asking for more CPU -- is root's alone.
|
|
//
|
|
// Both are inert today, because everything runs as root and euid is 0
|
|
// everywhere. They are written now because the alternative is a scheduler
|
|
// that grows a priority knob and a permission model separately, and the
|
|
// window between the two is exactly the bug.
|
|
if (myproc()->euid != 0 && myproc()->euid != p->uid) {
|
|
r = -EPERM;
|
|
break;
|
|
}
|
|
if (nice < p->nice && myproc()->euid != 0) {
|
|
r = -EACCES;
|
|
break;
|
|
}
|
|
sched_set_nice(p, nice);
|
|
r = 0;
|
|
break;
|
|
}
|
|
release(&ptable.lock);
|
|
return r;
|
|
}
|
|
|
|
int
|
|
proc_getnice(int pid, int *out)
|
|
{
|
|
struct proc *p;
|
|
int r = -ESRCH;
|
|
|
|
acquire(&ptable.lock);
|
|
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
|
if (p->state == UNUSED || (pid != 0 && p->pid != pid))
|
|
continue;
|
|
if (pid == 0 && p != myproc())
|
|
continue;
|
|
*out = p->nice;
|
|
r = 0;
|
|
break;
|
|
}
|
|
release(&ptable.lock);
|
|
return r;
|
|
}
|
|
|
|
// Change a process's priority. ptable.lock must be held.
|
|
//
|
|
// vruntime is left alone rather than rescaled: it is a record of time already
|
|
// spent, and rewriting history so that renicing hands out a retroactive
|
|
// refund (or bill) is how a nice() call becomes a way to jump the queue.
|
|
void
|
|
sched_set_nice(struct proc *p, int nice)
|
|
{
|
|
if (nice < -20) nice = -20;
|
|
if (nice > 19) nice = 19;
|
|
p->nice = nice;
|
|
p->weight = nice_weight[nice + 20];
|
|
}
|
|
|
|
// Charge a process for the time it has just run, and advance its vruntime.
|
|
// ptable.lock must be held, and p must be the process leaving the CPU.
|
|
static void
|
|
sched_charge(struct proc *p)
|
|
{
|
|
uint64 now = rdtsc(), ns;
|
|
|
|
if (p->ran_at == 0 || now <= p->ran_at) {
|
|
p->ran_at = 0;
|
|
return;
|
|
}
|
|
ns = tsc_to_ns(now - p->ran_at);
|
|
p->ran_at = 0;
|
|
|
|
p->slice_used += ns;
|
|
|
|
// The weighting, and the only line in the scheduler where nice does
|
|
// anything: a process of twice the weight is charged half the vruntime for
|
|
// the same real time, so its clock falls behind and pick_next() returns to
|
|
// it sooner.
|
|
//
|
|
// The guard is for the one way weight can be zero: a ptable slot is BSS,
|
|
// and BSS is zeros until allocproc() runs sched_init_proc() on it. Nothing
|
|
// reaches here on such a slot today -- only a process that has run gets
|
|
// charged, and a process that has run went through allocproc -- but the
|
|
// failure if that ever stops being true is a divide-by-zero #GP inside the
|
|
// scheduler, which is a fault with no good place to report itself.
|
|
if (p->weight == 0)
|
|
p->weight = WEIGHT_NICE0;
|
|
p->vruntime += (ns * WEIGHT_NICE0) / p->weight;
|
|
}
|
|
|
|
// How long a slice should be, given how many processes want one.
|
|
static uint64
|
|
sched_slice(int nrunnable)
|
|
{
|
|
uint64 slice;
|
|
|
|
if (nrunnable <= 1)
|
|
return SCHED_LATENCY_NS;
|
|
slice = SCHED_LATENCY_NS / (uint64)nrunnable;
|
|
return slice < SCHED_MIN_GRAN_NS ? SCHED_MIN_GRAN_NS : slice;
|
|
}
|
|
|
|
// Place a process that has just become runnable, and keep min_vruntime
|
|
// honest. ptable.lock must be held.
|
|
//
|
|
// Called from wakeup1() and from anywhere else that moves a process into
|
|
// RUNNABLE from a state where it was not accruing vruntime.
|
|
static void
|
|
sched_enqueue(struct proc *p)
|
|
{
|
|
uint64 floor = min_vruntime > SCHED_LATENCY_NS
|
|
? min_vruntime - SCHED_LATENCY_NS : 0;
|
|
|
|
if (p->vruntime < floor)
|
|
p->vruntime = floor;
|
|
p->slice_used = 0;
|
|
}
|
|
|
|
// The runnable process with the smallest vruntime, and a count of how many
|
|
// were in the running. ptable.lock must be held.
|
|
static struct proc *
|
|
pick_next(int *nrunnable)
|
|
{
|
|
struct proc *p, *best = 0;
|
|
int n = 0;
|
|
|
|
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)
|
|
continue;
|
|
n++;
|
|
if (best == 0 || p->vruntime < best->vruntime)
|
|
best = p;
|
|
}
|
|
*nrunnable = n;
|
|
return best;
|
|
}
|
|
|
|
// Whether the running process has used up its slice, so trap() knows whether
|
|
// the timer tick should become a yield(). No lock: it reads only the current
|
|
// process's own fields, and the only writer of them is the current process.
|
|
//
|
|
// This is the line that removed a hundred context switches a second from an
|
|
// idle machine. The old code yielded on *every* tick unconditionally.
|
|
int
|
|
sched_slice_expired(void)
|
|
{
|
|
struct proc *p = myproc();
|
|
uint64 now, ns;
|
|
|
|
if (p == 0 || p->ran_at == 0)
|
|
return 0;
|
|
now = rdtsc();
|
|
if (now <= p->ran_at)
|
|
return 0;
|
|
ns = tsc_to_ns(now - p->ran_at);
|
|
return (p->slice_used + ns) >= p->slice_ns;
|
|
}
|
|
|
|
void
|
|
scheduler(void)
|
|
{
|
|
struct proc *p;
|
|
int nrunnable;
|
|
|
|
for(;;){
|
|
sti();
|
|
|
|
acquire(&ptable.lock);
|
|
p = pick_next(&nrunnable);
|
|
if (p == 0) {
|
|
// Nothing to run. Drop the lock before halting -- holding it through a
|
|
// hlt() would stop every other CPU from scheduling until an interrupt
|
|
// happened to arrive here.
|
|
release(&ptable.lock);
|
|
hlt();
|
|
continue;
|
|
}
|
|
|
|
// min_vruntime tracks the front of the queue and never moves backwards,
|
|
// so a process that sleeps through a busy period cannot return with a
|
|
// claim on all the CPU it missed. See the comment on the variable.
|
|
if (p->vruntime > min_vruntime)
|
|
min_vruntime = p->vruntime;
|
|
|
|
p->slice_ns = sched_slice(nrunnable);
|
|
p->slice_used = 0;
|
|
p->ran_at = rdtsc();
|
|
|
|
proc_set(p);
|
|
switchuvm(p);
|
|
fxrstor(p->fpu);
|
|
p->state = RUNNING;
|
|
swtch(&mycpu()->scheduler, p->context);
|
|
switchkvm();
|
|
proc_set(0);
|
|
|
|
// Back from swtch: p has given the CPU up, through sched(). It has
|
|
// already been charged there, where the reason for the switch was known.
|
|
release(&ptable.lock);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// Charge for the time just run before handing the CPU back. Here rather
|
|
// than in scheduler(), because this is the one point every path off a CPU
|
|
// goes through -- yield(), sleep() and exit() alike -- and doing it on the
|
|
// way out means the vruntime the scheduler then compares is already
|
|
// up to date rather than a tick stale.
|
|
sched_charge(myproc());
|
|
fxsave(myproc()->fpu);
|
|
|
|
swtch(&myproc()->context, mycpu()->scheduler);
|
|
mycpu()->intena = intena;
|
|
}
|
|
|
|
void
|
|
yield(void)
|
|
{
|
|
acquire(&ptable.lock);
|
|
myproc()->state = RUNNABLE;
|
|
myproc()->nivcsw++; // involuntary: the slice ran out
|
|
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();
|
|
if (dev >= BLKDEV_FIRST)
|
|
return blkdev_present(dev);
|
|
// 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)
|
|
{
|
|
// The fixed devices, with the registered ones -- SATA, NVMe, and every
|
|
// partition on them -- between USB and IDE. A partition GPT types as SchFS
|
|
// goes ahead of the rest of the registry, because it was labelled that way
|
|
// on purpose; the others are probed too, so a volume in an untyped or MBR
|
|
// partition, or on a whole unpartitioned disk, still boots.
|
|
struct { uint dev; const char *what; } cand[4 + 2 * NBLKDEV];
|
|
int ncand = 0;
|
|
#ifdef RAMDISK_ROOT
|
|
cand[ncand].dev = RAMDISK_DEV; cand[ncand++].what = "ramdisk";
|
|
#endif
|
|
cand[ncand].dev = USBDISK_DEV; cand[ncand++].what = "USB disk";
|
|
for (int pass = 0; pass < 2; pass++)
|
|
for (int i = 0, d; (d = blkdev_nth(i)) >= 0; i++)
|
|
if (blkdev_is_schfs(d) == !pass) {
|
|
cand[ncand].dev = d;
|
|
cand[ncand++].what = blkdev_name(d);
|
|
}
|
|
cand[ncand].dev = 1; cand[ncand++].what = "IDE disk 1";
|
|
cand[ncand].dev = 0; cand[ncand++].what = "IDE disk 0";
|
|
|
|
for (int i = 0; i < ncand; 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 < ncand; 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;
|
|
myproc()->nvcsw++; // voluntary: it asked to wait
|
|
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;
|
|
// Pull it forward to the front of the timeline if it has been asleep
|
|
// long enough to have fallen off the back of it -- see sched_enqueue.
|
|
sched_enqueue(p);
|
|
}
|
|
}
|
|
|
|
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;
|
|
sched_enqueue(p); // a signal is a wakeup too; same floor
|
|
}
|
|
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;
|
|
sched_enqueue(p); // a signal is a wakeup too; same floor
|
|
}
|
|
}
|
|
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;
|
|
sched_enqueue(p); // a signal is a wakeup too; same floor
|
|
}
|
|
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;
|
|
out->nice = p->nice;
|
|
out->vruntime = p->vruntime;
|
|
out->nvcsw = p->nvcsw;
|
|
out->nivcsw = p->nivcsw;
|
|
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;
|
|
}
|