SchOS/kernel/proc/proc.c
Nexoniarz 03cc316001 schfs: the commit barrier, reflink, and attributes with an index over them
The last three phases of the filesystem, which were written together and
touch the same files throughout -- kmount.c, objw.c and vfs.c are each
rewritten by all three -- so they land as one commit rather than three
that could not be built separately.

Durability. A transaction that reaches the platter out of order is worse
than one that never reached it: the uberblock can name a tree whose
blocks are still in the drive's write cache. bdirty/bflush give the
buffer layer an explicit barrier, ideflush issues ATA FLUSH CACHE and
usb_storage_flush the SCSI SYNCHRONIZE CACHE that means the same thing,
and a commit now waits for the data before it writes the root that
points at it. The uberblock groups are read newest-first with fallback,
so a volume whose newest roots are torn mounts at an older one and says
which generations were lost.

Reflink. Extents carry a refcount, so two objects can name the same
blocks and neither frees them while the other holds them. Exposed as
FICLONE -- Linux's ioctl value, so `cp --reflink` works unmodified --
and as cmd/reflink for the case where there is no cp to hand.

Attributes, and the one genuinely new idea here. attr.c stores named
values on an object, reachable through setxattr/getxattr/removexattr at
Linux's syscall numbers. On top of that, schfs_index declares an index
over an attribute name and schfs_query answers "which objects carry this
value" from the B-tree rather than by walking. A query against an
unindexed attribute is refused with an error, never silently answered by
a full scan: a query that quietly becomes a scan is worse than one that
admits it cannot help. cmd/schattr drives both.

Three bugs fixed on the way, each found by the numbers being wrong
rather than by inspection:

  - statfs's f_fsid was declared int64[2]. Linux's fsid_t is two *ints*,
    so every field after it landed one slot late: userland read f_frsize
    out of f_namelen, got 255, and df reported a 32 MB root filesystem
    as 1992 KB. It hid for as long as it did because the fields used to
    be invented constants, so wrong numbers looked like the usual ones.
  - struct inode's nlink was a short against SchFS's uint32, so a file
    past 32767 names came back negative -- and iput frees an object
    whose count reaches zero. It is an int now, signed on purpose, so an
    underflow lands somewhere a check can see rather than wrapping.
  - filewrite split every write to fit v1's log. That limit belongs to
    the journal, not to writing, and SchFS makes one transaction of
    whatever it is handed: a 1 MB write was becoming 170 commits and
    170 flushes for nothing. fs_ops carries max_write now, and SchFS
    sets no limit.

Also here: a real mount table, so `mount -t schfs /dev/hdb /mnt` shows
up in /proc/mounts -- df looks a path up there before it calls statfs at
all, so a mount missing from that list was invisible to it -- several
volumes mounted at once, snapshots mounted by name, and /proc/fsstat
with an rdtsc-based counter, which is the only instrument fine enough to
see inside a single syscall.

Verified: `make schfs-check` passes in full, including the sweep that
crashes the volume at each of 119 points mid-transaction and requires
every one to mount with exactly its committed data, and the case where
both newest uberblocks are rubbish and the volume still comes back. The
headless boot check passes with 27 [ok] and no failures, on a SchFS root.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 16:02:02 +02:00

864 lines
No EOL
23 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;
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(SCHFS_DEV));
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.
// 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;
}