//! Processes: a thread + an address space (`Mm`) + a file descriptor table //! (+ arguments, environment, a current directory, a parent, a process //! group and signal state). //! //! **How Unix makes processes: fork + exec.** There's no "start program X" //! call. Instead: //! * `fork()` duplicates the calling process. Both copies return from the //! same fork call: the parent gets the child's pid, the child gets 0. //! The child has a copy of everything: memory, open files, cwd, signal //! handlers. //! * `execve(path, argv, envp)` replaces the *running* program with //! another one: new code, new memory, same process (pid, open files, //! cwd, parent). //! Why split it? Because between the two the child is still running the //! *parent's* code, and can set itself up with all the normal syscalls: //! `dup2` a pipe onto stdout, `chdir`, `setpgid`, reset signal handlers, //! close files. No "spawn options" structure has to anticipate every //! possible setup: a shell does `cmd > file` as fork, open, dup2, exec. //! Copying all memory just to throw it away at exec would be slow; that's //! what copy-on-write (mm.rs) is for: fork only copies page tables. //! //! `spawn` (our syscall 500 from stage 9, and `spawn_path` for the kernel) //! stays as a convenience: new process + exec in one step, like //! `posix_spawn`. //! //! **Exit and wait.** A process's exit status is stored in it, its parent //! gets SIGCHLD and is woken; `wait4` returns the status and removes the //! child from the process table ("reaps" it -- until then it's a *zombie*). //! If the parent exits first, the children become orphans (parent = //! ORPHAN) and remove themselves when they exit; Unix hands them to //! `init` instead. //! //! **Process groups** (job control): every process is in a group, named by //! the pid of its first member. A shell puts each pipeline in its own group //! and tells the terminal which group is in the *foreground*; Ctrl+C then //! sends SIGINT to that whole group -- every program of the pipeline, but //! not the shell (tty.rs). //! //! **Memory lifetime.** A process's frames and page tables are freed when //! the last `Arc` goes away (dropping its `Mm`). That's always //! after its thread is gone: the thread holds an Arc until the scheduler //! frees it, which happens only once the CPU has switched to another //! thread and address space. So no CPU can still be using the page tables. use crate::elf::{self, ElfError}; use crate::errno::Errno; use crate::fs::{self, file::File}; use crate::mm::Mm; use crate::signal::{self, SigInfo, SignalState}; use crate::thread::{self, WaitQueue}; use crate::trap::TrapFrame; use crate::{serial_println, tty::Console}; use alloc::{collections::BTreeMap, string::String, sync::Arc, vec, vec::Vec}; use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering}; use spin::Mutex; use x86_64::instructions::interrupts::without_interrupts; pub use crate::mm::{in_user_stack_guard, USER_STACK_GUARD, USER_STACK_SIZE, USER_STACK_TOP, USER_STACK_ZONE_START}; /// `parent` of a process whose parent exited first. pub const ORPHAN: u64 = u64::MAX; /// Max open files per process. pub const MAX_FDS: usize = 64; /// Max total size of a program's arguments + environment strings. pub const MAX_ARGS_SIZE: usize = 64 * 1024; /// Max processes at once: fork() fails with EAGAIN beyond that, which is /// what stops a fork bomb from eating all memory. pub const MAX_PROCESSES: usize = 64; /// Environment of processes the kernel starts. pub const DEFAULT_ENV: [&str; 3] = ["PATH=/bin:/mnt/disk0/bin", "HOME=/", "TERM=linux"]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ExitStatus { /// Called exit(code). Exited(i64), /// Killed by signal number .0; .1 says why (for messages): the signal /// name, or e.g. "page fault" / "stack overflow" for SIGSEGV. Killed(u32, &'static str), } impl ExitStatus { /// The number a shell shows: the exit code, or 128 + the signal number /// (so 139 = 128 + SIGSEGV, 130 = 128 + SIGINT). pub fn code(self) -> i64 { match self { ExitStatus::Exited(code) => code, ExitStatus::Killed(sig, _) => 128 + sig as i64, } } /// The `int status` of wait4 (decoded by the W* macros, see ulib.rs): /// exit code in bits 8-15, or the killing signal in bits 0-6. pub fn wait_status(self) -> i32 { match self { ExitStatus::Exited(code) => ((code & 0xff) << 8) as i32, ExitStatus::Killed(sig, _) => sig as i32, } } } /// Open files: fd number -> file, plus "close on exec" flags. #[derive(Clone)] pub struct FdTable { files: Vec>>, /// Bit n: close fd n when the process execs (FD_CLOEXEC / O_CLOEXEC). cloexec: u64, } impl FdTable { fn new(stdio: [Arc; 3]) -> FdTable { FdTable { files: stdio.into_iter().map(Some).collect(), cloexec: 0 } } fn get(&self, fd: u64) -> Result, Errno> { self.files.get(fd as usize).and_then(|f| f.clone()).ok_or(Errno::EBADF) } /// Put `file` into the lowest free slot >= `min` (Unix's rule). fn add(&mut self, file: Arc, min: usize, cloexec: bool) -> Result { let fd = match (min..MAX_FDS).find(|&i| self.files.get(i).is_none_or(|f| f.is_none())) { Some(fd) => fd, None => return Err(Errno::EMFILE), }; self.set(fd, file, cloexec); Ok(fd as u64) } /// Put `file` at `fd` (whatever was there is returned, to be closed). fn set(&mut self, fd: usize, file: Arc, cloexec: bool) -> Option> { if self.files.len() <= fd { self.files.resize(fd + 1, None); } self.set_cloexec(fd, cloexec); self.files[fd].replace(file) } fn set_cloexec(&mut self, fd: usize, on: bool) { if on { self.cloexec |= 1 << fd } else { self.cloexec &= !(1 << fd) } } /// execve: close the close-on-exec ones. Returns them (close them /// after unlocking). fn exec(&mut self) -> Vec> { let mut closed = Vec::new(); for (fd, slot) in self.files.iter_mut().enumerate() { if self.cloexec & (1 << fd) != 0 { closed.extend(slot.take()); } } self.cloexec = 0; closed } } pub struct Process { pub pid: u64, /// Parent pid; 0 = started by the kernel itself, ORPHAN = parent exited. pub parent: AtomicU64, /// Process group (job control; see the top of this file). pgid: AtomicU64, /// Program name (changes with execve). name: Mutex, /// Print every syscall this process makes (`run --trace`; survives exec). pub trace: AtomicBool, /// Current directory (always a normalized absolute path). pub cwd: Mutex, /// The address space. pub mm: Mutex, /// Its thread's id (set right after the thread is created). tid: AtomicUsize, /// CPUs its thread has run on (bit per CPU; kept up by the scheduler). pub ran_on: AtomicU32, /// Registers its thread starts user mode with (taken by the thread). start: Mutex>, /// The environment it was started with (`spawn` passes it on). env: Mutex>, fds: Mutex, /// Locked with interrupts off only (it's read in a wait_until condition). status: Mutex>, exited: WaitQueue, /// Its own wait4 sleeps here; children wake it when they change state. pub child_wait: WaitQueue, /// Signal handlers, mask, pending signals. Locked with interrupts off. pub signals: Mutex, /// vfork: the parent waits until this child execs or exits. vfork_done: AtomicBool, } static NEXT_PID: AtomicU64 = AtomicU64::new(1); /// All processes that haven't been reaped yet, by pid. Locked with /// interrupts off only (exit_current takes it with interrupts off). Lock /// order: this table before any process's `signals`. static PROCESSES: Mutex>> = Mutex::new(BTreeMap::new()); /// vfork parents wait here. static VFORK_WAIT: WaitQueue = WaitQueue::new(); /// Everything needed to start a process besides the program itself. pub struct SpawnArgs { pub args: Vec, // argv; args[0] is the program name by convention pub env: Vec, pub stdio: [Arc; 3], pub cwd: String, pub parent: u64, /// Process group: 0 = a new group of its own. pub pgid: u64, pub trace: bool, } impl SpawnArgs { /// Defaults for a process started by the kernel: console stdio, cwd "/". pub fn kernel(name: &str) -> SpawnArgs { let console: Arc = Arc::new(Console); SpawnArgs { args: vec![String::from(name)], env: DEFAULT_ENV.iter().map(|s| String::from(*s)).collect(), stdio: [console.clone(), console.clone(), console], cwd: String::from("/"), parent: 0, pgid: 0, trace: false, } } } fn elf_errno(e: ElfError) -> Errno { match e { ElfError::OutOfMemory => Errno::ENOMEM, _ => Errno::ENOEXEC, // "Exec format error" } } /// Load the program at `path` (absolute) from the VFS and start it. pub fn spawn_path(path: &str, spawn: SpawnArgs) -> Result, Errno> { let image = fs::read_all(path)?; // ENOENT, EISDIR, EIO, ... if count() >= MAX_PROCESSES { return Err(Errno::EAGAIN); } spawn_image(path, &image, spawn).map_err(elf_errno) } /// Start ELF program `elf_image` from the kernel (console stdio, no args). pub fn spawn(name: &str, elf_image: &[u8]) -> Result, ElfError> { spawn_image(name, elf_image, SpawnArgs::kernel(name)) } /// Load `elf_image` into a new address space and start it. `path` is /// where it came from (AT_EXECFN); the process is named after its last part. pub fn spawn_image(path: &str, elf_image: &[u8], spawn: SpawnArgs) -> Result, ElfError> { let name = path.rsplit('/').next().unwrap_or(path); let mut mm = Mm::new().ok_or(ElfError::OutOfMemory)?; // (On error, dropping `mm` gives its memory back.) let args: Vec<&[u8]> = spawn.args.iter().map(|a| a.as_bytes()).collect(); let env: Vec<&[u8]> = spawn.env.iter().map(|a| a.as_bytes()).collect(); let start = load_program(&mut mm, elf_image, &args, &env, path)?; let [stdin, stdout, stderr] = spawn.stdio; let pid = NEXT_PID.fetch_add(1, Ordering::Relaxed); let process = Process { pid, parent: AtomicU64::new(spawn.parent), pgid: AtomicU64::new(if spawn.pgid == 0 { pid } else { spawn.pgid }), name: Mutex::new(String::from(name)), trace: AtomicBool::new(spawn.trace), cwd: Mutex::new(spawn.cwd), mm: Mutex::new(mm), tid: AtomicUsize::new(usize::MAX), ran_on: AtomicU32::new(0), start: Mutex::new(Some(start)), env: Mutex::new(spawn.env), fds: Mutex::new(FdTable::new([stdin, stdout, stderr])), status: Mutex::new(None), exited: WaitQueue::new(), child_wait: WaitQueue::new(), signals: Mutex::new(SignalState::new()), vfork_done: AtomicBool::new(false), }; serial_println!("[process] started pid {} ({}), entry {:#x}", pid, name, start.rip); Ok(start_thread(process)) } /// Put the process in the table and give it a thread. fn start_thread(process: Process) -> Arc { let process = Arc::new(process); let l4 = process.mm.lock().l4; without_interrupts(|| PROCESSES.lock().insert(process.pid, process.clone())); let name = process.name(); let tid = thread::spawn_with(&name, user_thread_entry, l4, Some(process.clone())); process.tid.store(tid, Ordering::Release); process } /// First code of every process thread (still ring 0, but already running /// with the process's CR3 and kernel stack): enter user mode with the /// prepared registers. fn user_thread_entry() { let process = thread::current_process().expect("process thread without process"); let frame = process.start.lock().take().expect("no start registers"); drop(process); // return_to_user never returns: don't leak the Arc crate::trap::return_to_user(frame) } /// Random bytes for AT_RANDOM (virtio-rng; the TSC if there's none). fn random_bytes(buf: &mut [u8]) { if !crate::virtio::rng::fill(buf) { let mut x = unsafe { core::arch::x86_64::_rdtsc() } | 1; for b in buf { x ^= x << 13; x ^= x >> 7; x ^= x << 17; *b = x as u8; } } } // Auxiliary vector keys (Linux's AT_* values, `man getauxval`). const AT_NULL: u64 = 0; const AT_PHDR: u64 = 3; const AT_PHENT: u64 = 4; const AT_PHNUM: u64 = 5; const AT_PAGESZ: u64 = 6; const AT_BASE: u64 = 7; const AT_FLAGS: u64 = 8; const AT_ENTRY: u64 = 9; const AT_UID: u64 = 11; const AT_EUID: u64 = 12; const AT_GID: u64 = 13; const AT_EGID: u64 = 14; const AT_PLATFORM: u64 = 15; const AT_HWCAP: u64 = 16; const AT_CLKTCK: u64 = 17; const AT_SECURE: u64 = 23; const AT_RANDOM: u64 = 25; const AT_EXECFN: u64 = 31; /// Load a program into the empty address space `mm` and build its initial /// stack the way Linux does, so a C library's `_start` finds everything /// where it expects it. Returns the registers to start with. /// /// The stack, from USER_STACK_TOP downwards: /// /// strings: execfn, environment, arguments, "x86_64", 16 random bytes, /// (a copy of the program headers if no segment contains them) /// (padding to 16 bytes) /// auxv: pairs of [key, value] ..., [AT_NULL, 0] /// envp: pointers to the environment strings ..., NULL /// argv: pointers to the argument strings ..., NULL /// argc <- rsp (16-byte aligned) pub fn load_program(mm: &mut Mm, image: &[u8], argv: &[&[u8]], envp: &[&[u8]], execfn: &str) -> Result { use crate::mm::{Kind, Vma, PROT_READ, PROT_WRITE}; let info = elf::load(image, mm)?; // The heap starts on the page after the program; the stack is a region // that fills in as it's used. mm.brk_start = info.end.next_multiple_of(4096); mm.brk = mm.brk_start; mm.insert(Vma::new(USER_STACK_TOP - USER_STACK_SIZE, USER_STACK_TOP, PROT_READ | PROT_WRITE, Kind::Stack)); let mut sp = USER_STACK_TOP; let mut push = |mm: &mut Mm, bytes: &[u8], nul: bool| -> Result { sp -= bytes.len() as u64 + nul as u64; mm.write_bytes(sp, bytes).map_err(|_| ElfError::OutOfMemory)?; if nul { mm.write_bytes(sp + bytes.len() as u64, &[0]).map_err(|_| ElfError::OutOfMemory)?; } Ok(sp) }; let execfn_ptr = push(mm, execfn.as_bytes(), true)?; let env_ptrs: Vec = envp.iter().map(|e| push(mm, e, true)).collect::>()?; let arg_ptrs: Vec = argv.iter().map(|a| push(mm, a, true)).collect::>()?; let platform = push(mm, b"x86_64", true)?; let mut random = [0u8; 16]; random_bytes(&mut random); let random_ptr = push(mm, &random, false)?; let phdr = match info.phdr { Some(addr) => addr, None => { let start = info.phoff as usize; push(mm, &image[start..start + (info.phent * info.phnum) as usize], false)? } }; let mut words: Vec = vec![arg_ptrs.len() as u64]; words.extend(&arg_ptrs); words.push(0); words.extend(&env_ptrs); words.push(0); #[rustfmt::skip] words.extend([ AT_PHDR, phdr, AT_PHENT, info.phent, AT_PHNUM, info.phnum, AT_PAGESZ, 4096, AT_BASE, 0, AT_FLAGS, 0, AT_ENTRY, info.entry, AT_UID, 0, AT_EUID, 0, AT_GID, 0, AT_EGID, 0, AT_PLATFORM, platform, AT_HWCAP, 0, AT_CLKTCK, 100, AT_SECURE, 0, AT_RANDOM, random_ptr, AT_EXECFN, execfn_ptr, AT_NULL, 0, ]); let rsp = (sp - 8 * words.len() as u64) & !0xf; let bytes: Vec = words.iter().flat_map(|w| w.to_le_bytes()).collect(); mm.write_bytes(rsp, &bytes).map_err(|_| ElfError::OutOfMemory)?; // The ABI also says: rdx = function for atexit (0 = none), others zero. Ok(TrapFrame::new_user(info.entry, rsp)) } impl Process { /// The open file behind `fd`. pub fn file(&self, fd: u64) -> Result, Errno> { self.fds.lock().get(fd) } /// Put `file` into the lowest free fd slot (like Unix). pub fn add_file(&self, file: Arc) -> Result { self.fds.lock().add(file, 0, false) } /// open(O_CLOEXEC), pipe2, fcntl(F_DUPFD...): lowest free fd >= `min`. pub fn add_file_with(&self, file: Arc, min: usize, cloexec: bool) -> Result { self.fds.lock().add(file, min, cloexec) } /// dup2/dup3: make `new` refer to the same file as `old`. pub fn dup_to(&self, old: u64, new: u64, cloexec: bool) -> Result { if new as usize >= MAX_FDS { return Err(Errno::EBADF); } let replaced = { let mut fds = self.fds.lock(); let file = fds.get(old)?; fds.set(new as usize, file, cloexec) }; drop(replaced); // closing the old file may block: after unlocking Ok(new) } pub fn cloexec(&self, fd: u64) -> Result { let fds = self.fds.lock(); fds.get(fd)?; Ok(fds.cloexec & (1 << fd) != 0) } pub fn set_cloexec(&self, fd: u64, on: bool) -> Result<(), Errno> { let mut fds = self.fds.lock(); fds.get(fd)?; fds.set_cloexec(fd as usize, on); Ok(()) } pub fn close(&self, fd: u64) -> Result<(), Errno> { let file = { let mut fds = self.fds.lock(); match fds.files.get_mut(fd as usize) { Some(slot @ Some(_)) => slot.take(), _ => return Err(Errno::EBADF), } }; drop(file); // dropping the last Arc closes the file (after unlocking) Ok(()) } pub fn status(&self) -> Option { without_interrupts(|| *self.status.lock()) } /// Make `path` absolute, relative to this process's current directory. pub fn resolve(&self, path: &str) -> Result { let cwd = self.cwd.lock().clone(); fs::normalize(&cwd, path) } pub fn name(&self) -> String { self.name.lock().clone() } pub fn parent(&self) -> u64 { self.parent.load(Ordering::Relaxed) } pub fn pgid(&self) -> u64 { self.pgid.load(Ordering::Relaxed) } pub fn tracing(&self) -> bool { self.trace.load(Ordering::Relaxed) } pub fn env(&self) -> Vec { self.env.lock().clone() } /// Wake its thread if it's blocked (it then checks for signals). pub fn wake(&self) { let tid = self.tid.load(Ordering::Acquire); if tid != usize::MAX { thread::wake(tid); } } } /// Look up a live (or not yet reaped) process. pub fn get(pid: u64) -> Option> { without_interrupts(|| PROCESSES.lock().get(&pid).cloned()) } /// How many processes are in the table (running or not yet reaped). pub fn count() -> usize { without_interrupts(|| PROCESSES.lock().len()) } /// Every process in the table. pub fn all() -> Vec> { without_interrupts(|| PROCESSES.lock().values().cloned().collect()) } /// The (not exited) members of process group `pgid`. pub fn group_members(pgid: u64) -> Vec> { without_interrupts(|| { PROCESSES.lock().values().filter(|p| p.pgid() == pgid && p.status().is_none()).cloned().collect() }) } /// End the current process (exit syscall, or killed by a signal). pub fn exit_current(status: ExitStatus) -> ! { if let Some(process) = thread::current_process() { // Close all files (a pipe's reader sees EOF once its writers are gone). let files = core::mem::take(&mut process.fds.lock().files); drop(files); crate::tty::process_exited(process.pid); let name = process.name(); match status { // Serial only: on screen the shell reports exit codes itself. ExitStatus::Exited(code) => { serial_println!("[process] pid {} ({}) exited with code {}", process.pid, name, code) } // A crash (SIGSEGV...) is worth a line on screen; a signal // somebody sent on purpose (Ctrl+C, kill) isn't. ExitStatus::Killed(sig, why) if why != signal::name(sig) => { crate::kprintln!("[process] pid {} ({}) killed: {} ({})", process.pid, name, why, signal::name(sig)) } ExitStatus::Killed(sig, _) => { serial_println!("[process] pid {} ({}) killed by {}", process.pid, name, signal::name(sig)) } } // No preemption from here on: we're about to disappear anyway. x86_64::instructions::interrupts::disable(); let removed = { // The table lock makes "set my status" and "look at my parent" // one step, so a parent exiting at the same time on another CPU // either sees our status (and reaps us) or orphans us first (and // we reap ourselves). Never both, never neither. let mut table = PROCESSES.lock(); *process.status.lock() = Some(status); let mut removed = Vec::new(); let children: Vec> = table.values().filter(|c| c.parent() == process.pid).cloned().collect(); for child in children { if child.status().is_some() { removed.extend(table.remove(&child.pid)); // exited: reap it } else { child.parent.store(ORPHAN, Ordering::Relaxed); // still running } } // Nobody will wait for us if we're an orphan, or if our parent // said so by ignoring SIGCHLD explicitly (POSIX): reap ourselves. let parent_ignores = table.get(&process.parent()).is_some_and(|p| { let action = p.signals.lock().action(signal::SIGCHLD); action.handler == signal::SIG_IGN || action.flags & signal::SA_NOCLDWAIT != 0 }); if process.parent() == ORPHAN || parent_ignores { removed.extend(table.remove(&process.pid)); } removed }; drop(removed); // (frees their memory if they're completely gone) process.exited.wake_all(); // the kernel may be blocked in wait() process.vfork_done.store(true, Ordering::Release); VFORK_WAIT.wake_all(); let code = match status { ExitStatus::Exited(_) => signal::CLD_EXITED, ExitStatus::Killed(sig, _) if signal::default_action(sig) == signal::DefaultAction::Core => signal::CLD_DUMPED, ExitStatus::Killed(..) => signal::CLD_KILLED, }; let si_status = match status { ExitStatus::Exited(c) => c as i32, ExitStatus::Killed(sig, _) => sig as i32, }; signal::notify_parent(&process, code, si_status); drop(process); } thread::exit_current() } /// Block (the calling thread) until `process` has ended and its thread is /// gone, then remove it from the process table. Used by the kernel (tests, /// the shell supervisor). Once the caller drops its own `Arc`, all of the /// process's memory is free again. pub fn wait(process: &Process) -> ExitStatus { let status = process.exited.wait_until(|| *process.status.lock()); reap(process); status } /// Wait for the (exited) process's thread to be freed, then remove the /// process from the table. fn reap(process: &Process) { // (The tid is stored right after the thread is created; on another CPU // the thread may have run and exited before that.) let tid = loop { match process.tid.load(Ordering::Acquire) { usize::MAX => thread::yield_now(), tid => break tid, } }; thread::join(tid); let removed = without_interrupts(|| PROCESSES.lock().remove(&process.pid)); drop(removed); } // wait4 options. pub const WNOHANG: u64 = 1; pub const WUNTRACED: u64 = 2; pub const WCONTINUED: u64 = 8; /// Kernel-side wait for one child (the tests use it): EAGAIN with WNOHANG /// while it runs. pub fn wait_child(parent: u64, pid: u64, flags: u64) -> Result { let child = get(pid).filter(|c| c.parent() == parent).ok_or(Errno::ECHILD)?; if flags & WNOHANG != 0 { let status = child.status().ok_or(Errno::EAGAIN)?; reap(&child); return Ok(status); } Ok(wait(&child)) } /// What wait4 found. enum Found { Event(Arc, i32, bool), // child, status word, exited (reap it) Nothing, // WNOHANG and no news } /// wait4(pid, *status, options): wait for a child to exit (or, with /// WUNTRACED / WCONTINUED, to stop / continue). `pid` > 0: that child; /// -1: any child; 0: any child in my process group; < -1: any child in /// group -pid. Returns (pid, status), or pid 0 with WNOHANG and no news. pub fn wait4(me: &Process, pid: i64, options: u64) -> Result<(u64, i32), Errno> { if options & !(WNOHANG | WUNTRACED | WCONTINUED) != 0 { return Err(Errno::EINVAL); } let matches = |c: &Process| { c.parent() == me.pid && match pid { -1 => true, 0 => c.pgid() == me.pgid(), p if p < -1 => c.pgid() == p.unsigned_abs(), p => c.pid == p as u64, } }; let found = me.child_wait.wait_until_intr(|| { let table = PROCESSES.lock(); let mut any = false; for c in table.values().filter(|c| matches(c)) { any = true; if let Some(status) = *c.status.lock() { return Some(Ok(Found::Event(c.clone(), status.wait_status(), true))); } let mut s = c.signals.lock(); match s.report { Some(signal::Report::Stopped(sig)) if options & WUNTRACED != 0 => { s.report = None; return Some(Ok(Found::Event(c.clone(), 0x7f | (sig as i32) << 8, false))); } Some(signal::Report::Continued) if options & WCONTINUED != 0 => { s.report = None; return Some(Ok(Found::Event(c.clone(), 0xffff, false))); } _ => {} } } if !any { Some(Err(Errno::ECHILD)) } else if options & WNOHANG != 0 { Some(Ok(Found::Nothing)) } else { None // sleep until a child changes state (or a signal) } })??; match found { Found::Nothing => Ok((0, 0)), Found::Event(child, status, exited) => { if exited { reap(&child); } Ok((child.pid, status)) } } } /// fork (and vfork / basic clone): a new process, a copy of `parent`, that /// starts by returning from the same syscall with rax = 0. `stack`: a new /// stack pointer for the child (clone), or 0 = the same. pub fn fork(parent: &Arc, frame: &TrapFrame, stack: u64, vfork: bool) -> Result { if count() >= MAX_PROCESSES { return Err(Errno::EAGAIN); // fork bombs stop here } // The heart of it: copy the page tables, sharing the frames // copy-on-write (mm.rs). let mm = parent.mm.lock().fork().ok_or(Errno::ENOMEM)?; let mut start = *frame; start.rax = 0; // "you're the child" if stack != 0 { start.rsp = stack; } let pid = NEXT_PID.fetch_add(1, Ordering::Relaxed); let signals = without_interrupts(|| parent.signals.lock().fork()); let child = Process { pid, parent: AtomicU64::new(parent.pid), pgid: AtomicU64::new(parent.pgid()), name: Mutex::new(parent.name()), trace: AtomicBool::new(parent.tracing()), cwd: Mutex::new(parent.cwd.lock().clone()), mm: Mutex::new(mm), tid: AtomicUsize::new(usize::MAX), ran_on: AtomicU32::new(0), start: Mutex::new(Some(start)), env: Mutex::new(parent.env()), fds: Mutex::new(parent.fds.lock().clone()), // same open files (Arc clones) status: Mutex::new(None), exited: WaitQueue::new(), child_wait: WaitQueue::new(), signals: Mutex::new(signals), vfork_done: AtomicBool::new(false), }; serial_println!("[process] pid {} forked pid {}", parent.pid, pid); let child = start_thread(child); if vfork { // The parent sleeps until the child has exec'd or exited (real // vfork shares the memory meanwhile; we just use copy-on-write). VFORK_WAIT.wait_until(|| child.vfork_done.load(Ordering::Acquire).then_some(())); } Ok(pid) } /// execve, after the arguments have been copied in: replace the current /// program. `frame` gets the new program's registers. pub fn execve(p: &Process, frame: &mut TrapFrame, path: &str, argv: &[Vec], envp: &[Vec]) -> Result<(), Errno> { let size: usize = argv.iter().chain(envp).map(|s| s.len() + 1 + 8).sum(); if size > MAX_ARGS_SIZE { return Err(Errno::E2BIG); } let image = fs::read_all(path)?; // Check the whole file *before* the point of no return: once the old // program's memory is gone, there's nobody left to report an error to. elf::check(&image).map_err(elf_errno)?; let name = path.rsplit('/').next().unwrap_or(path); let args: Vec<&[u8]> = argv.iter().map(|a| a.as_slice()).collect(); let env: Vec<&[u8]> = envp.iter().map(|a| a.as_slice()).collect(); // --- point of no return --- let loaded = { let mut mm = p.mm.lock(); mm.clear(); load_program(&mut mm, &image, &args, &env, path) }; let Ok(start) = loaded else { exit_current(ExitStatus::Killed(signal::SIGSEGV, "exec failed (out of memory)")); }; *frame = start; *p.name.lock() = String::from(name); *p.env.lock() = envp.iter().map(|e| String::from_utf8_lossy(e).into_owned()).collect(); without_interrupts(|| p.signals.lock().exec()); let closed = p.fds.lock().exec(); drop(closed); p.vfork_done.store(true, Ordering::Release); VFORK_WAIT.wake_all(); serial_println!("[process] pid {} exec {}", p.pid, path); Ok(()) } /// setpgid(pid, pgid): move process `pid` (0 = me; myself or a child of /// mine) into group `pgid` (0 = a new group named after it). The group /// must exist unless it's the new one. pub fn setpgid(me: &Process, pid: u64, pgid: u64) -> Result<(), Errno> { let pid = if pid == 0 { me.pid } else { pid }; let target = get(pid).ok_or(Errno::ESRCH)?; if target.pid != me.pid && target.parent() != me.pid { return Err(Errno::ESRCH); } let pgid = if pgid == 0 { pid } else { pgid }; // (Exited-but-not-reaped members count: a pipeline's first program may // already be done when the shell puts the second one in its group.) if pgid != pid && !without_interrupts(|| PROCESSES.lock().values().any(|p| p.pgid() == pgid)) { return Err(Errno::EPERM); } target.pgid.store(pgid, Ordering::Relaxed); Ok(()) } /// Send `sig` from the kernel to group `pgid` (Ctrl+C, Ctrl+Z). pub fn signal_group(pgid: u64, sig: u32) { let _ = signal::send_group(pgid, sig, SigInfo::from_pid(sig, 0)); }