Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
838
kernel/proc/proc.c
Normal file
838
kernel/proc/proc.c
Normal file
|
|
@ -0,0 +1,838 @@
|
|||
#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;
|
||||
|
||||
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);
|
||||
|
||||
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());
|
||||
if (stale)
|
||||
iput(stale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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){
|
||||
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
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in a new issue