lwIP 2.2.0, fetched by ports/lwip and compiled straight into the kernel. Not written from scratch, and the reason is not the packet formats: it is congestion control, retransmission, window management and twenty years of corner cases that only show up against a real peer. Not FreeBSD's netinet either, though the licence would have allowed it -- that stack is not a library, it reaches into mbufs, epoch(9), netisr, VNET and uma(9), so adopting it means porting FreeBSD's kernel infrastructure first. lwIP was written for exactly this situation: a small kernel with a raw Ethernet driver and a port layer of a few hundred lines. It is kernel code, so nothing is installed into the sysroot and there is no second build system: the top-level Makefile compiles the files it wants against our port headers. The file list is explicit rather than a wildcard, because lwIP ships IPv6, 6LoWPAN, PPP, SLIP, DNS-SD and a dozen apps in the same tree, and building what is not configured yields either dead code or a link error naming a port function nobody meant to write. Its sources are unmodified and its warnings are silenced for its files only -- patching them would mean maintaining a fork. The port layer is ours: a lock, a clock, the netif that hands frames to e1000, lwipopts.h, and a shim/ directory of tiny headers so lwIP's <string.h> and friends resolve to the kernel's functions instead of the host's, whose prototypes disagree and would otherwise be found on the default include path. NO_SYS=1, so lwIP has no threads of its own and one kernel thread feeds it. A thread rather than the interrupt handler, because a call into lwIP can reach the driver, the driver can sleep, and an interrupt handler cannot -- so the interrupt still only clears the cause register and the thread polls. kthread_create is new and generally useful: a process with a kernel-only page directory, no files, no cwd, no parent, dispatched from forkret, which can therefore sleep and be preempted like anything else. DHCP obtains an address at boot and the console says which one. AF_INET went in exactly where phase 2 said it would. socknew dispatches on the domain, kernel/net/inet.c implements the internet side on lwIP's raw API, and not one syscall changed shape: bind, connect, listen, accept, read and write reach the same descriptors they always did. TCP and UDP both work. cmd/nettest opens a connection, writes, and reads the answer back. Two things are owed and named here so they are not discovered later: recvfrom does not yet report the source address, and the socket paths poll on the timer tick instead of sleeping on the socket. Both are follow-ups, not redesigns. NOTICE carries lwIP's BSD-3 text. Clause 3 is no-endorsement, not the old advertising clause, so it sits with Apache-2.0 without difficulty. Verified: the tree builds clean, and the headless boot check passes with 27 [ok] and no failures with the stack compiled in and the thread running -- which is the part that would break first if netinit deadlocked or the thread starved anything. An end-to-end connection test belongs in that suite and is the next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
915 lines
No EOL
24 KiB
C
915 lines
No EOL
24 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 "fs.h"
|
|
#include "file.h"
|
|
#include "posix.h"
|
|
#include "errno.h"
|
|
|
|
// Verify myproc()->fs_base offset matches the hardcoded 0x688 in trapasm.S.
|
|
_Static_assert(__builtin_offsetof(struct proc, fs_base) == 0x18,
|
|
"myproc()->fs_base offset changed — update trapasm.S WRITE_USER_FS macro (must be 0x18)");
|
|
|
|
struct {
|
|
struct spinlock lock;
|
|
struct proc proc[NPROC];
|
|
} ptable;
|
|
|
|
static struct proc *initproc;
|
|
|
|
int nextpid = 1;
|
|
extern void forkret(void);
|
|
extern void syscall_trapret(void);
|
|
|
|
static void wakeup1(void *chan);
|
|
|
|
void
|
|
kstack_check_canary(const char *where)
|
|
{
|
|
if(myproc() == 0 || myproc()->kstack == 0)
|
|
return;
|
|
if(*(uint64*)(myproc()->kstack) != KSTACK_CANARY){
|
|
cprintf("kstack canary corrupted for pid %d (%s) at %s: got 0x%p\n",
|
|
myproc()->pid, myproc()->name, where, *(uint64*)(myproc()->kstack));
|
|
panic("kstack overflow");
|
|
}
|
|
}
|
|
|
|
void
|
|
pinit(void)
|
|
{
|
|
initlock(&ptable.lock, "ptable");
|
|
}
|
|
|
|
static struct proc*
|
|
allocproc(void)
|
|
{
|
|
struct proc *p;
|
|
char *sp;
|
|
|
|
acquire(&ptable.lock);
|
|
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++)
|
|
if(p->state == UNUSED)
|
|
goto found;
|
|
|
|
release(&ptable.lock);
|
|
return 0;
|
|
|
|
found:
|
|
p->state = EMBRYO;
|
|
p->pid = nextpid++;
|
|
|
|
// ptable slots get reused once a previous occupant is reaped, but
|
|
// nothing previously cleared the signal-delivery fields added for
|
|
// SIGCHLD (sig_handler/sig_restorer/pending_signals/signal_mask/
|
|
// in_signal/saved_sigtf) — a freshly allocated process would silently
|
|
// inherit whatever a totally unrelated earlier process left behind,
|
|
// including stale handler addresses. deliver_signals() (syscall.c)
|
|
// would then jump to garbage on this new process's very first syscall
|
|
// return, faulting at whatever bogus rip happened to be sitting there.
|
|
memset(p->sig_handler, 0, sizeof(p->sig_handler));
|
|
memset(p->sig_restorer, 0, sizeof(p->sig_restorer));
|
|
p->pending_signals = 0;
|
|
p->signal_mask = 0;
|
|
p->in_signal = 0;
|
|
p->sigsusp_oldmask = 0;
|
|
p->sigsusp_active = 0;
|
|
|
|
// Same reasoning as the signal fields above: a recycled slot must not
|
|
// inherit a dead process's identity or exit status.
|
|
p->xstate = 0;
|
|
p->killsig = 0;
|
|
p->pgid = 0;
|
|
p->sid = 0;
|
|
p->uid = p->euid = p->suid = 0;
|
|
p->gid = p->egid = p->sgid = 0;
|
|
|
|
release(&ptable.lock);
|
|
|
|
if((p->kstack = kalloc()) == 0){
|
|
p->state = UNUSED;
|
|
return 0;
|
|
}
|
|
// Canary at the very bottom of the kstack page: if a deep/nested trap
|
|
// ever overflows this 4096-byte stack, this is the first thing clobbered.
|
|
// Checked on every trap()/syscall() entry (kstack_check_canary()).
|
|
*(uint64*)(p->kstack) = KSTACK_CANARY;
|
|
sp = p->kstack + KSTACKSIZE;
|
|
|
|
sp -= sizeof(struct trapframe);
|
|
p->tf = (struct trapframe*)sp;
|
|
|
|
sp -= sizeof(addr_t);
|
|
*(addr_t*)sp = (addr_t)syscall_trapret;
|
|
|
|
sp -= sizeof(struct context);
|
|
p->context = (struct context*)sp;
|
|
memset(p->context, 0, sizeof *p->context);
|
|
p->context->rip = (addr_t)forkret;
|
|
|
|
p->syscall_map = 0;
|
|
p->map_size = 0;
|
|
p->mmap_next = 0;
|
|
p->kfn = 0;
|
|
|
|
memset(p->ofile_flags, 0, sizeof(p->ofile_flags));
|
|
|
|
return p;
|
|
}
|
|
|
|
void
|
|
sinit(void)
|
|
{
|
|
struct proc *p;
|
|
extern char _binary_initcode_start[], _binary_initcode_size[];
|
|
|
|
p = allocproc();
|
|
initproc = p;
|
|
|
|
if((p->pgdir = setupkvm()) == 0)
|
|
panic("sinit: setupkvm failed");
|
|
|
|
inituvm(p->pgdir, _binary_initcode_start, (uint64)_binary_initcode_size);
|
|
|
|
if(allocuvm(p->pgdir, PGSIZE, 32 * PGSIZE) == 0)
|
|
panic("sinit: allocuvm failed");
|
|
|
|
p->sz = 32 * PGSIZE;
|
|
|
|
memset(p->tf, 0, sizeof(*p->tf));
|
|
p->tf->cs = (SEG_UCODE << 3) | DPL_USER;
|
|
p->tf->ss = (SEG_UDATA << 3) | DPL_USER;
|
|
p->tf->rflags = FL_IF;
|
|
p->tf->rsp = p->sz;
|
|
p->tf->rip = 0;
|
|
|
|
// PID 1 leads its own session and process group, and runs as root.
|
|
p->pgid = p->pid;
|
|
p->sid = p->pid;
|
|
p->uid = p->euid = p->suid = 0;
|
|
p->gid = p->egid = p->sgid = 0;
|
|
|
|
safestrcpy(p->name, "sinit", sizeof(p->name));
|
|
p->cwd = namei("/");
|
|
p->cwd_path[0] = '/'; p->cwd_path[1] = 0;
|
|
p->fs_base = 0;
|
|
|
|
p->state = RUNNABLE;
|
|
}
|
|
|
|
int
|
|
growproc(int64 n)
|
|
{
|
|
addr_t sz;
|
|
|
|
sz = myproc()->sz;
|
|
if(n > 0){
|
|
if((sz = allocuvm(myproc()->pgdir, sz, sz + n)) == 0)
|
|
return -1;
|
|
} else if(n < 0){
|
|
if((sz = deallocuvm(myproc()->pgdir, sz, sz + n)) == 0)
|
|
return -1;
|
|
}
|
|
myproc()->sz = sz;
|
|
switchuvm(myproc());
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
fork(void)
|
|
{
|
|
int i, pid;
|
|
struct proc *np;
|
|
|
|
if((np = allocproc()) == 0)
|
|
return -1;
|
|
|
|
if((np->pgdir = copyuvm(myproc()->pgdir, myproc()->sz)) == 0){
|
|
kfree(np->kstack);
|
|
np->kstack = 0;
|
|
np->state = UNUSED;
|
|
return -1;
|
|
}
|
|
np->sz = myproc()->sz;
|
|
np->parent = myproc();
|
|
|
|
// copyuvm() only duplicates [0, sz) — mmap()'d regions live above that
|
|
// and were never copied, leaving the child's vma[] bookkeeping (stale
|
|
// from whichever process last occupied this ptable slot) pointing at
|
|
// memory that isn't actually mapped in np->pgdir. Reset and rebuild it
|
|
// from the parent's real, current mappings.
|
|
memset(np->vma, 0, sizeof(np->vma));
|
|
for(i = 0; i < NVMA; i++){
|
|
if(!myproc()->vma[i].used)
|
|
continue;
|
|
if(copy_vma_range(np->pgdir, myproc()->pgdir, myproc()->vma[i].addr,
|
|
myproc()->vma[i].length, myproc()->vma[i].no_free) < 0){
|
|
freevm(np->pgdir);
|
|
kfree(np->kstack);
|
|
np->kstack = 0;
|
|
np->state = UNUSED;
|
|
return -1;
|
|
}
|
|
np->vma[i] = myproc()->vma[i];
|
|
if(np->vma[i].shmid >= 0)
|
|
shm_ref(np->vma[i].shmid);
|
|
}
|
|
np->mmap_next = myproc()->mmap_next;
|
|
|
|
memmove(np->tf, myproc()->tf, sizeof(struct trapframe));
|
|
|
|
np->syscall_map = myproc()->syscall_map;
|
|
np->map_size = myproc()->map_size;
|
|
|
|
// fork() preserves the parent's signal handlers and mask (POSIX); the
|
|
// child starts with no pending signals of its own, though — those
|
|
// aren't inherited. allocproc() already zeroed pending_signals/
|
|
// in_signal/saved_sigtf for this fresh slot.
|
|
memmove(np->sig_handler, myproc()->sig_handler, sizeof(np->sig_handler));
|
|
memmove(np->sig_restorer, myproc()->sig_restorer, sizeof(np->sig_restorer));
|
|
np->signal_mask = myproc()->signal_mask;
|
|
|
|
np->tf->rax = 0;
|
|
|
|
for(i = 0; i < NOFILE; i++)
|
|
if(myproc()->ofile[i]) {
|
|
np->ofile[i] = filedup(myproc()->ofile[i]);
|
|
np->ofile_flags[i] = myproc()->ofile_flags[i];
|
|
}
|
|
// POSIX: the child inherits process group, session and all four ids.
|
|
np->pgid = myproc()->pgid;
|
|
np->sid = myproc()->sid;
|
|
np->uid = myproc()->uid; np->euid = myproc()->euid; np->suid = myproc()->suid;
|
|
np->gid = myproc()->gid; np->egid = myproc()->egid; np->sgid = myproc()->sgid;
|
|
|
|
np->cwd = idup(myproc()->cwd);
|
|
memmove(np->cwd_path, myproc()->cwd_path, sizeof(myproc()->cwd_path));
|
|
np->fs_base = myproc()->fs_base;
|
|
|
|
safestrcpy(np->name, myproc()->name, sizeof(myproc()->name));
|
|
|
|
pid = np->pid;
|
|
|
|
__sync_synchronize();
|
|
np->state = RUNNABLE;
|
|
|
|
return pid;
|
|
}
|
|
|
|
// status is already wait(2)-encoded -- see W_EXITCODE() in proc.h. Callers
|
|
// that die from a fault or signal pass W_EXITCODE(0, sig); a normal _exit(n)
|
|
// passes W_EXITCODE(n, 0).
|
|
void
|
|
exit(int status)
|
|
{
|
|
struct proc *p;
|
|
int fd;
|
|
|
|
if(myproc() == initproc)
|
|
panic("init exiting");
|
|
|
|
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 has further gaps (see docs/architecture/
|
|
// kernel-history.md) that this alone doesn't fully resolve, but
|
|
// disabling it guarantees no progress, whereas leaving it enabled at
|
|
// least allows the cases that don't hit those gaps to work.
|
|
if (myproc()->parent)
|
|
myproc()->parent->pending_signals |= (1ULL << SIGCHLD);
|
|
wakeup1(myproc()->parent);
|
|
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->parent == myproc()){
|
|
p->parent = initproc;
|
|
if(p->state == ZOMBIE)
|
|
wakeup1(initproc);
|
|
}
|
|
}
|
|
|
|
myproc()->state = ZOMBIE;
|
|
sched();
|
|
panic("zombie exit");
|
|
}
|
|
|
|
int
|
|
wait(void)
|
|
{
|
|
return waitpid(-1, 0, 0);
|
|
}
|
|
|
|
// waitpid(2)/wait4(2) proper: a specific pid (-1 = any child, matching
|
|
// wait()'s old behavior), an optional status-out pointer (NULL is valid —
|
|
// POSIX callers commonly do waitpid(pid, NULL, WNOHANG)), and WNOHANG.
|
|
//
|
|
// This used to be missing: sys_waitpid()/sys_wait4() both ignored pid and
|
|
// options and called the any-child, always-blocking wait() above. A NULL
|
|
// status pointer additionally got rejected by argptr() (page 0 is
|
|
// unmapped) before ever reaching here. Together, a caller doing
|
|
// waitpid(child, NULL, WNOHANG) right after a child exits — exactly what
|
|
// mksh's job control does — always failed instead of immediately reaping
|
|
// the already-zombie child. mksh then fell back to sigsuspend(), which
|
|
// this kernel can't wake for a real SIGCHLD (no signal delivery), so it
|
|
// silently retried forever: a real, low-CPU hang with no error printed.
|
|
int
|
|
waitpid(int pid, int *status, int options)
|
|
{
|
|
struct proc *p;
|
|
int havekids;
|
|
|
|
acquire(&ptable.lock);
|
|
for(;;){
|
|
havekids = 0;
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->parent != myproc())
|
|
continue;
|
|
// POSIX pid selection: >0 one child, 0 = caller's group,
|
|
// -1 = any child, < -1 = the process group with id -pid.
|
|
if(pid > 0 && p->pid != pid)
|
|
continue;
|
|
if(pid == 0 && p->pgid != myproc()->pgid)
|
|
continue;
|
|
if(pid < -1 && p->pgid != -pid)
|
|
continue;
|
|
havekids = 1;
|
|
if(p->state == ZOMBIE){
|
|
int rpid = p->pid;
|
|
int rstatus = p->xstate; // copy out before the slot is recycled
|
|
kfree(p->kstack);
|
|
p->kstack = 0;
|
|
// Give back anything shared before freevm() decides it owns the
|
|
// pages. See proc_release_shared() in kernel/sys/sys_mem.c.
|
|
proc_release_shared(p);
|
|
freevm(p->pgdir);
|
|
p->pid = 0;
|
|
p->parent = 0;
|
|
p->name[0] = 0;
|
|
p->killed = 0;
|
|
p->xstate = 0;
|
|
p->killsig = 0;
|
|
p->pgid = 0;
|
|
p->sid = 0;
|
|
p->state = UNUSED;
|
|
release(&ptable.lock);
|
|
if (status) *status = rstatus;
|
|
return rpid;
|
|
}
|
|
}
|
|
|
|
if(!havekids || myproc()->killed){
|
|
release(&ptable.lock);
|
|
return -1;
|
|
}
|
|
|
|
if (options & 1) { // WNOHANG
|
|
release(&ptable.lock);
|
|
return 0;
|
|
}
|
|
|
|
sleep(myproc(), &ptable.lock);
|
|
}
|
|
}
|
|
|
|
void
|
|
scheduler(void)
|
|
{
|
|
struct proc *p;
|
|
int skipped = 0;
|
|
for(;;){
|
|
sti();
|
|
acquire(&ptable.lock);
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->state != RUNNABLE) {
|
|
skipped++;
|
|
continue;
|
|
}
|
|
skipped = 0;
|
|
|
|
proc_set(p);
|
|
switchuvm(p);
|
|
p->state = RUNNING;
|
|
swtch(&mycpu()->scheduler, p->context);
|
|
switchkvm();
|
|
|
|
proc_set(0);
|
|
}
|
|
release(&ptable.lock);
|
|
if (skipped > NPROC) {
|
|
hlt();
|
|
skipped = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
sched(void)
|
|
{
|
|
int intena;
|
|
|
|
if(!holding(&ptable.lock))
|
|
panic("sched ptable.lock");
|
|
if(mycpu()->ncli != 1)
|
|
panic("sched locks");
|
|
if(myproc()->state == RUNNING)
|
|
panic("sched running");
|
|
if(readeflags()&FL_IF)
|
|
panic("sched interruptible");
|
|
intena = mycpu()->intena;
|
|
swtch(&myproc()->context, mycpu()->scheduler);
|
|
mycpu()->intena = intena;
|
|
}
|
|
|
|
void
|
|
yield(void)
|
|
{
|
|
acquire(&ptable.lock);
|
|
myproc()->state = RUNNABLE;
|
|
sched();
|
|
release(&ptable.lock);
|
|
}
|
|
|
|
void
|
|
forkret(void) {
|
|
static int first = 1;
|
|
release(&ptable.lock);
|
|
|
|
// A kernel thread starts here and never leaves. It shares the scheduler's
|
|
// rotation with everything else, so it can hold sleeplocks and block on the
|
|
// disk exactly as a syscall would -- which is the whole reason it is a
|
|
// process rather than a timer callback.
|
|
//
|
|
// Ahead of the first-boot block below on purpose: a kernel thread is only
|
|
// ever created long after the root filesystem is mounted, so it must not be
|
|
// the one that runs that setup.
|
|
if (myproc()->kfn) {
|
|
myproc()->kfn();
|
|
panic("kthread returned");
|
|
}
|
|
|
|
if (first) {
|
|
first = 0;
|
|
iinit();
|
|
mountinit();
|
|
|
|
// SchFS is the root filesystem.
|
|
//
|
|
// Mounted here rather than in main() because mounting reads blocks,
|
|
// bread() sleeps, and sleeping needs a process context.
|
|
//
|
|
// schfs_kmount() reports failure by leaving the volume unmounted rather
|
|
// than by returning -- it is a probe elsewhere, where "not SchFS" is an
|
|
// ordinary answer. Here it is fatal, so ask afterwards.
|
|
schfs_kmount(ROOTDEV);
|
|
if (!schfs_kmounted())
|
|
panic("schfs root: no SchFS volume on the root device");
|
|
// Re-point pid 1's cwd at the real root, now that there is one.
|
|
//
|
|
// sinit() sets cwd = namei("/") back in main(), which is before this runs.
|
|
// The old v1 root survived that because iget(ROOTDEV, ROOTINO) is two
|
|
// compile-time constants and touches no disk -- the inode is filled in
|
|
// lazily at the first ilock(). SchFS has no constant to offer: the root
|
|
// object id comes off the uberblock, so namei("/") that early returns an
|
|
// inode built from whatever schfs_kroot() reads out of an unmounted
|
|
// volume.
|
|
//
|
|
// Nothing complains, because absolute paths never consult cwd -- exec
|
|
// "/bin/init" works fine. It is the first *relative* lookup that fails,
|
|
// and in init that is open("console"): no console means no fd 0/1/2, so
|
|
// every shell it spawns exits immediately and init respawns it forever.
|
|
// The symptom is a silent boot with no init: ready, which reads like a
|
|
// hang and is not one.
|
|
{
|
|
struct inode *stale = myproc()->cwd;
|
|
myproc()->cwd = iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV));
|
|
if (stale)
|
|
iput(stale);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start a kernel thread: a process that runs `fn` in kernel mode and never
|
|
// returns to userland.
|
|
//
|
|
// It gets a page directory of its own with only the kernel mapped, because the
|
|
// scheduler calls switchuvm() for every process it runs and that panics on a
|
|
// null pgdir. Sharing kpgdir directly would work equally well; a private one
|
|
// costs a page and keeps the "every proc owns its pgdir" invariant intact.
|
|
//
|
|
// No files, no cwd, no parent: it is not in the process tree, cannot be waited
|
|
// for and must never exit. Returns the pid, or -1.
|
|
int
|
|
kthread_create(void (*fn)(void), const char *name)
|
|
{
|
|
struct proc *p;
|
|
|
|
if ((p = allocproc()) == 0)
|
|
return -1;
|
|
|
|
if ((p->pgdir = setupkvm()) == 0) {
|
|
kfree(p->kstack);
|
|
p->kstack = 0;
|
|
p->state = UNUSED;
|
|
return -1;
|
|
}
|
|
|
|
p->sz = PGSIZE; // never used; 0 upsets code that treats it as a size
|
|
p->parent = 0;
|
|
p->cwd = 0;
|
|
p->kfn = fn;
|
|
safestrcpy(p->name, name, sizeof(p->name));
|
|
|
|
acquire(&ptable.lock);
|
|
p->state = RUNNABLE;
|
|
release(&ptable.lock);
|
|
return p->pid;
|
|
}
|
|
|
|
void
|
|
sleep(void *chan, struct spinlock *lk)
|
|
{
|
|
if(myproc() == 0)
|
|
panic("sleep");
|
|
|
|
if(lk == 0)
|
|
panic("sleep without lk");
|
|
|
|
if(lk != &ptable.lock){
|
|
acquire(&ptable.lock);
|
|
release(lk);
|
|
}
|
|
|
|
myproc()->chan = chan;
|
|
myproc()->state = SLEEPING;
|
|
sched();
|
|
|
|
myproc()->chan = 0;
|
|
|
|
if(lk != &ptable.lock){
|
|
release(&ptable.lock);
|
|
acquire(lk);
|
|
}
|
|
}
|
|
|
|
static void
|
|
wakeup1(void *chan)
|
|
{
|
|
struct proc *p;
|
|
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++)
|
|
if(p->state == SLEEPING && p->chan == chan)
|
|
p->state = RUNNABLE;
|
|
}
|
|
|
|
void
|
|
wakeup(void *chan)
|
|
{
|
|
acquire(&ptable.lock);
|
|
wakeup1(chan);
|
|
release(&ptable.lock);
|
|
}
|
|
|
|
// Raise a signal on a process: set the pending bit and wake it if it's
|
|
// sleeping so blocking syscalls (sigsuspend, pause, read) re-check their
|
|
// conditions promptly. Signals with no handler installed fall back to the
|
|
// default action — termination for the fatal set, dropped otherwise.
|
|
// mksh depends on kill(getpid(), SIGCHLD) genuinely re-raising SIGCHLD
|
|
// (its held_sigchld recovery in j_startjob); the previous sys_kill
|
|
// silently discarded every non-fatal signal.
|
|
// PID 1 does not die from a signal it never asked to handle.
|
|
//
|
|
// POSIX, and Linux in sig_task_ignored(): a signal whose disposition is the
|
|
// default action is discarded for init rather than delivered. Without this the
|
|
// system has a single keystroke that kills it -- Ctrl-C sends SIGINT to the
|
|
// foreground process group, init is pgid 1, and any moment where tty_fg_pgrp
|
|
// is 1 (before a shell claims the terminal, after one exits, or in the window
|
|
// between the two while the key is held down) lands SIGINT on init. init has
|
|
// no handler, so killed is set, exit() runs, and exit() panics outright on
|
|
// initproc: "init exiting". Spamming Ctrl-C at a frozen program is exactly the
|
|
// input that finds that window.
|
|
//
|
|
// Applies to SIGKILL too, which is the point: init is not killable from
|
|
// userspace on any Unix. A handler it did install is still delivered.
|
|
static int
|
|
init_ignores(struct proc *p, int sig)
|
|
{
|
|
return p == initproc && p->sig_handler[sig] == 0;
|
|
}
|
|
|
|
int
|
|
sigsend(int pid, int sig)
|
|
{
|
|
struct proc *p;
|
|
|
|
if(sig <= 0 || sig >= 32)
|
|
return -1;
|
|
|
|
acquire(&ptable.lock);
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){
|
|
if(init_ignores(p, sig)){
|
|
release(&ptable.lock);
|
|
return 0; // silently dropped, as POSIX requires
|
|
}
|
|
p->pending_signals |= 1ULL << sig;
|
|
if(p->sig_handler[sig] == 0 &&
|
|
(sig == SIGKILL || sig == SIGTERM || sig == SIGABRT ||
|
|
sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS ||
|
|
sig == SIGILL || sig == SIGINT || sig == SIGQUIT ||
|
|
sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 ||
|
|
sig == SIGUSR2 || sig == SIGALRM))
|
|
p->killed = 1;
|
|
if(sig == SIGKILL)
|
|
p->killed = 1;
|
|
if(p->killed && p->killsig == 0)
|
|
p->killsig = sig; // so exit() can report the real cause
|
|
if(p->state == SLEEPING)
|
|
p->state = RUNNABLE;
|
|
release(&ptable.lock);
|
|
return 0;
|
|
}
|
|
}
|
|
release(&ptable.lock);
|
|
return -1;
|
|
}
|
|
|
|
// Deliver a signal to every member of a process group. This is what job
|
|
// control actually needs: a shell sends SIGINT/SIGTSTP/SIGHUP to a *group*,
|
|
// never to individual pids. sys_kill() with pid <= 0 routes here.
|
|
//
|
|
// Returns 0 if at least one process was signalled, -1 if the group is empty.
|
|
int
|
|
sigsend_pgrp(int pgid, int sig)
|
|
{
|
|
struct proc *p;
|
|
int found = 0;
|
|
|
|
if(sig < 0 || sig >= 32)
|
|
return -1;
|
|
|
|
acquire(&ptable.lock);
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->state == UNUSED || p->state == ZOMBIE)
|
|
continue;
|
|
if(p->pgid != pgid)
|
|
continue;
|
|
found = 1;
|
|
if(sig == 0)
|
|
continue; // signal 0 is an existence probe only
|
|
if(init_ignores(p, sig))
|
|
continue; // see init_ignores(): this is the Ctrl-C case
|
|
p->pending_signals |= 1ULL << sig;
|
|
if(p->sig_handler[sig] == 0 &&
|
|
(sig == SIGKILL || sig == SIGTERM || sig == SIGABRT ||
|
|
sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS ||
|
|
sig == SIGILL || sig == SIGINT || sig == SIGQUIT ||
|
|
sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 ||
|
|
sig == SIGUSR2 || sig == SIGALRM)){
|
|
p->killed = 1;
|
|
if(p->killsig == 0)
|
|
p->killsig = sig;
|
|
}
|
|
if(sig == SIGKILL){
|
|
p->killed = 1;
|
|
if(p->killsig == 0)
|
|
p->killsig = sig;
|
|
}
|
|
if(p->state == SLEEPING)
|
|
p->state = RUNNABLE;
|
|
}
|
|
release(&ptable.lock);
|
|
|
|
return found ? 0 : -1;
|
|
}
|
|
|
|
// Look up a process's process-group id. Returns -1 if no such live process.
|
|
int
|
|
proc_pgid(int pid)
|
|
{
|
|
struct proc *p;
|
|
int r = -1;
|
|
|
|
acquire(&ptable.lock);
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){
|
|
r = p->pgid;
|
|
break;
|
|
}
|
|
}
|
|
release(&ptable.lock);
|
|
return r;
|
|
}
|
|
|
|
// setpgid(pid, pgid) with POSIX's argument shorthands: pid 0 means the caller,
|
|
// pgid 0 means "use pid as the group id" (i.e. become a group leader).
|
|
int
|
|
proc_setpgid(int pid, int pgid)
|
|
{
|
|
struct proc *p;
|
|
int r = -ESRCH;
|
|
|
|
if(pid < 0 || pgid < 0)
|
|
return -EINVAL;
|
|
if(pid == 0)
|
|
pid = myproc()->pid;
|
|
if(pgid == 0)
|
|
pgid = pid;
|
|
|
|
acquire(&ptable.lock);
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->pid != pid || p->state == UNUSED || p->state == ZOMBIE)
|
|
continue;
|
|
// A caller may only move itself or one of its own children, and never
|
|
// across a session boundary.
|
|
if(p != myproc() && p->parent != myproc())
|
|
r = -ESRCH;
|
|
else if(p->sid != myproc()->sid)
|
|
r = -EPERM;
|
|
else {
|
|
p->pgid = pgid;
|
|
r = 0;
|
|
}
|
|
break;
|
|
}
|
|
release(&ptable.lock);
|
|
return r;
|
|
}
|
|
|
|
// setsid(): become leader of a brand-new session and process group. Fails if
|
|
// the caller already leads a process group, since that group would otherwise
|
|
// end up split across two sessions.
|
|
int
|
|
proc_setsid(void)
|
|
{
|
|
struct proc *p;
|
|
int r;
|
|
|
|
acquire(&ptable.lock);
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->state != UNUSED && p->state != ZOMBIE && p->pgid == myproc()->pid){
|
|
release(&ptable.lock);
|
|
return -EPERM;
|
|
}
|
|
}
|
|
myproc()->sid = myproc()->pid;
|
|
myproc()->pgid = myproc()->pid;
|
|
r = myproc()->sid;
|
|
release(&ptable.lock);
|
|
return r;
|
|
}
|
|
|
|
int
|
|
kill(int pid)
|
|
{
|
|
struct proc *p;
|
|
|
|
acquire(&ptable.lock);
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->pid == pid){
|
|
p->killed = 1;
|
|
if(p->state == SLEEPING)
|
|
p->state = RUNNABLE;
|
|
release(&ptable.lock);
|
|
return 0;
|
|
}
|
|
}
|
|
release(&ptable.lock);
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
procdump(void)
|
|
{
|
|
static char *states[] = {
|
|
[UNUSED] "unused",
|
|
[EMBRYO] "embryo",
|
|
[SLEEPING] "sleep ",
|
|
[RUNNABLE] "runble",
|
|
[RUNNING] "run ",
|
|
[ZOMBIE] "zombie"
|
|
};
|
|
int i;
|
|
struct proc *p;
|
|
char *state;
|
|
addr_t pc[10];
|
|
|
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
|
if(p->state == UNUSED)
|
|
continue;
|
|
if(p->state >= 0 && p->state < NELEM(states) && states[p->state])
|
|
state = states[p->state];
|
|
else
|
|
state = "???";
|
|
cprintf("%d %s %s", p->pid, state, p->name);
|
|
if(p->state == SLEEPING){
|
|
getstackpcs((addr_t*)p->context->rbp, pc);
|
|
for(i=0; i<10 && pc[i] != 0; i++)
|
|
cprintf(" %p", pc[i]);
|
|
}
|
|
cprintf("\n");
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Snapshots, for /proc
|
|
//
|
|
// procfs must not walk ptable itself: it would need the lock, the struct
|
|
// layout, and a rule about what happens when a process exits between the
|
|
// readdir that listed it and the open that reads it. These two copy what is
|
|
// needed out from under the lock and hand back a value, so a process that dies
|
|
// mid-listing produces a stale line rather than a use-after-free.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Fill `out` with the `idx`-th live process (0-based, in table order).
|
|
// Returns 0 on success, -1 when idx is past the end.
|
|
int
|
|
proc_snapshot(int idx, struct procinfo *out)
|
|
{
|
|
struct proc *p;
|
|
int n = 0;
|
|
|
|
acquire(&ptable.lock);
|
|
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
|
if (p->state == UNUSED)
|
|
continue;
|
|
if (n++ != idx)
|
|
continue;
|
|
out->pid = p->pid;
|
|
out->ppid = p->parent ? p->parent->pid : 0;
|
|
out->pgid = p->pgid;
|
|
out->sid = p->sid;
|
|
out->uid = p->uid;
|
|
out->state = p->state;
|
|
out->sz = p->sz;
|
|
out->killed = p->killed;
|
|
safestrcpy(out->name, p->name, sizeof(out->name));
|
|
safestrcpy(out->exe, p->exe, sizeof(out->exe));
|
|
release(&ptable.lock);
|
|
return 0;
|
|
}
|
|
release(&ptable.lock);
|
|
return -1;
|
|
}
|
|
|
|
// The same, by pid rather than index.
|
|
int
|
|
proc_snapshot_pid(int pid, struct procinfo *out)
|
|
{
|
|
int i;
|
|
struct procinfo pi;
|
|
|
|
for (i = 0; proc_snapshot(i, &pi) == 0; i++)
|
|
if (pi.pid == pid) { *out = pi; return 0; }
|
|
return -1;
|
|
}
|
|
|
|
int proc_num(void)
|
|
{
|
|
struct proc *p;
|
|
int num = 0;
|
|
acquire(&ptable.lock);
|
|
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
|
|
if (p->state != UNUSED) num++;
|
|
release(&ptable.lock);
|
|
return num;
|
|
}
|
|
|
|
int proc_exists(int pid)
|
|
{
|
|
struct proc *p;
|
|
acquire(&ptable.lock);
|
|
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
|
if (p->pid == pid && p->state != UNUSED && p->state != ZOMBIE) {
|
|
release(&ptable.lock);
|
|
return 1;
|
|
}
|
|
}
|
|
release(&ptable.lock);
|
|
return 0;
|
|
} |