Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ an explicit force issues it, and never a timer's.
`libc_children`'s QEMU x86-64 boot by `signal_sigchld_ignored`,
`signal_sigchld_ppoll` and `libc_read_restarts`, each red under its mutation
and the stage reverted whole, beside `signal_alarm`; the routing laws are
host tests (`signal_dispositions.rs`). libc's own thread also watches its
host tests (`signal_dispositions.rs`), and so is a wait deciding from one
read of each child's end (`spawn_requests.rs`). libc's own thread also watches its
control pipe, so it watches at most 254 children: the 255th is refused
`EAGAIN`, a `poll` takes 255 descriptors beside its wake pipe, and `SIG_IGN`
is shown over 255 children.
Expand Down
21 changes: 20 additions & 1 deletion sdk/libc/arch/src/spawn_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use toyos_abi::end::{End, Fault};

use crate::errcode::{EBADF, EINVAL};
use crate::header::{self, FCNTL_H, SIGNAL_H};
use crate::spawnreq::{any_fd, child_fd, plan, wait_status, Action, Plan};
use crate::spawnreq::{any_fd, child_fd, plan, turn, wait_status, Action, Plan, Turn};

const STDIN: u32 = 0x1000;
const STDOUT: u32 = 0x1001;
Expand Down Expand Up @@ -183,3 +183,22 @@ fn each_end_reads_as_its_wait_status() {
assert!(!wifexited(s) && wtermsig(s) == signal(name), "{end:?}: {s:#x}");
}
}

/// **With `SIGCHLD` ignored a wait answers no child**: a child that ended is
/// forgotten, and a wait whose every child ended answers `ECHILD`.
#[test]
fn an_ignored_sigchld_answers_no_ended_child() {
assert_eq!(turn(&[true], true), Turn::Gone);
assert_eq!(turn(&[true, true], true), Turn::Gone);
assert_eq!(turn(&[true, false], true), Turn::Watch);
assert_eq!(turn(&[false], true), Turn::Watch);
assert_eq!(turn(&[], true), Turn::Gone);
}

/// A wait otherwise answers the first named child that ended.
#[test]
fn a_wait_takes_the_first_ended_child() {
assert_eq!(turn(&[false, true, true], false), Turn::Take(1));
assert_eq!(turn(&[false, false], false), Turn::Watch);
assert_eq!(turn(&[], false), Turn::Gone);
}
66 changes: 39 additions & 27 deletions sdk/libc/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use toyos_abi::RawHandle;

use crate::errcode::{E2BIG, EACCES, EAGAIN, EBADF, ECHILD, EINTR, EINVAL, ENOENT, EPERM, ESRCH};
use crate::pthread::Lock;
use crate::spawnreq::{self, Action};
use crate::spawnreq::{self, Action, Turn};

const SIGKILL: i32 = 9;
const WNOHANG: i32 = 1;
Expand Down Expand Up @@ -404,13 +404,20 @@ fn reap(pid: i32) -> Option<(i32, End, toyos_abi::syscall::ProcessStats)> {
/// Every child that has ended, out of the table and kept for no wait, as
/// `SIGCHLD` ignored has it.
pub(crate) fn forget_ended() {
let ended: Vec<Child> = {
let mut children = CHILDREN.lock();
let (ended, live) = core::mem::take(&mut *children).into_iter().partition(|c| c.process.try_wait().is_ok());
*children = live;
ended
};
UNWAITED.fetch_sub(ended.len(), Ordering::Relaxed);
let mut children = CHILDREN.lock();
let ended: Vec<bool> = children.iter().map(|c| c.process.try_wait().is_ok()).collect();
forget(&mut children, &ended);
}

/// Take the children `ended` read as ended out of `children`, kept for no
/// wait.
fn forget(children: &mut Vec<Child>, ended: &[bool]) {
let mut at = 0;
children.retain(|_| {
at += 1;
!ended[at - 1]
});
UNWAITED.fetch_sub(ended.iter().filter(|&&e| e).count(), Ordering::Relaxed);
}

/// Each child whose end has not raised `SIGCHLD`, by pid.
Expand Down Expand Up @@ -461,30 +468,35 @@ fn wait_child(pid: i32, block: bool) -> Result<Option<(i32, End, toyos_abi::sysc
// Before the table is read: a child started after it moves the turn
// this records, and the wake pipe says so.
call.children_seen();
if crate::signal::children_ignored() {
forget_ended();
}
let ignored = crate::signal::children_ignored();
// Copies of the handles, watched with the table given up: a reap on
// another thread closes the table's own.
let watched: Vec<(i32, RawHandle)> = {
let children = CHILDREN.lock();
let named: Vec<&Child> = children.iter().filter(|c| names(pid, c)).collect();
if named.is_empty() {
return Err(ECHILD);
let mut children = CHILDREN.lock();
// Each child's end is read once, and that read alone decides: a
// child that ends after it is the next turn's.
let ended: Vec<bool> = children.iter().map(|c| c.process.try_wait().is_ok()).collect();
let named: Vec<usize> = (0..children.len()).filter(|&i| names(pid, &children[i])).collect();
let named_ended: Vec<bool> = named.iter().map(|&i| ended[i]).collect();
let turn = spawnreq::turn(&named_ended, ignored);
if ignored {
forget(&mut children, &ended);
}
if let Some(ended) = named.iter().find(|c| c.process.try_wait().is_ok()) {
let pid = ended.pid;
drop(children);
if let Some(reaped) = reap(pid) {
return Ok(Some(reaped));
let running: Vec<&Child> = match turn {
Turn::Gone => return Err(ECHILD),
Turn::Take(at) => {
let pid = children[named[at]].pid;
drop(children);
if let Some(reaped) = reap(pid) {
return Ok(Some(reaped));
}
continue;
}
continue;
}
if !block {
return Ok(None);
}
let mut copies = Vec::with_capacity(named.len());
for child in named {
Turn::Watch if !block => return Ok(None),
Turn::Watch => children.iter().filter(|c| names(pid, c)).collect(),
};
let mut copies = Vec::with_capacity(running.len());
for child in running {
match syscall::dup(toyos::AsHandle::as_handle(&child.process)) {
Ok(copy) => copies.push((child.pid, copy)),
Err(e) => {
Expand Down
26 changes: 26 additions & 0 deletions sdk/libc/src/spawnreq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,32 @@ pub(crate) fn plan<P>(
Ok(Plan { fds, opened })
}

/// What one turn of a wait does with the children it names.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Turn {
/// None is left to wait for: `ECHILD`.
Gone,
/// The named child at this index has ended and is answered.
Take(usize),
/// Every named child left is running.
Watch,
}

/// The turn for the named children whose ends `ended` holds, each read once.
/// **With `SIGCHLD` ignored an ended child is forgotten, never answered**: the
/// caller drops every child this read saw ended, and a child that ends after
/// the read is the next turn's.
pub(crate) fn turn(ended: &[bool], ignored: bool) -> Turn {
if ignored {
return if ended.contains(&false) { Turn::Watch } else { Turn::Gone };
}
match ended.iter().position(|&e| e) {
Some(at) => Turn::Take(at),
None if ended.is_empty() => Turn::Gone,
None => Turn::Watch,
}
}

/// The status `waitpid` writes for an end, as `<sys/wait.h>`'s macros read
/// it: an exit's low byte above the low eight bits, or the signal
/// `toyos::process::signal` names the end by in the low seven.
Expand Down