Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
149 lines
5.2 KiB
C
149 lines
5.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// sigtest -- the SIGCHLD/sigsuspend contract every shell is built on.
|
|
//
|
|
// This is not one shell's quirk. It is what POSIX says to do and what zsh,
|
|
// mksh, bash and dash all do to wait for a child:
|
|
//
|
|
// sigprocmask(SIG_BLOCK, {SIGCHLD}, &old); // close the window
|
|
// pid = fork(); // child may exit at once
|
|
// while (!flag) // flag set by the handler
|
|
// sigsuspend(&old_without_SIGCHLD); // unblock and wait, atomically
|
|
// sigprocmask(SIG_SETMASK, &old, 0);
|
|
//
|
|
// Blocking first is the whole point: the child can exit *before* the parent
|
|
// reaches sigsuspend. Blocked, the signal stays pending; sigsuspend then
|
|
// unblocks and waits in one step, so a pending signal is delivered at once and
|
|
// the wait returns immediately. Get that wrong in either direction and the
|
|
// parent waits for a signal that has already happened -- which is a hang, in
|
|
// every shell, on every `$(command substitution)`.
|
|
//
|
|
// Written against the raw syscalls rather than a libc, because the thing under
|
|
// test is the kernel's half of the contract. **The user-facing sigset has
|
|
// signal N at bit N-1** (Linux's layout); the kernel converts at the boundary.
|
|
// Getting that backwards is a real bug this tree has had, so the masks below
|
|
// are spelled out rather than built by a macro.
|
|
|
|
#include "core/types.h"
|
|
#include "uapi/user.h"
|
|
#include "uapi/posix.h"
|
|
|
|
#define U_SIGCHLD_BIT (1ULL << (SIGCHLD - 1))
|
|
|
|
// rt_sigaction takes a pointer to Linux's struct sigaction, not a function
|
|
// pointer: the handler is at offset 0 and the restorer at 144. The kernel
|
|
// reads exactly those two words (SA_HANDLER_OFF / SA_RESTORER_OFF in
|
|
// kernel/sys/sys_sig.c), so a 152-byte zeroed block with the handler written
|
|
// into the first word is the whole of what it needs.
|
|
#define SA_SIZE 152
|
|
|
|
static int failures;
|
|
static volatile int handler_runs;
|
|
|
|
static void
|
|
puts_(const char *s)
|
|
{
|
|
int n = 0;
|
|
while (s[n]) n++;
|
|
write(1, (void *)s, n);
|
|
}
|
|
|
|
static void
|
|
check(const char *what, int ok)
|
|
{
|
|
puts_(ok ? "[ok] " : "[FAIL] ");
|
|
puts_(what);
|
|
puts_("\n");
|
|
if (!ok) failures++;
|
|
}
|
|
|
|
static void
|
|
on_chld(int sig)
|
|
{
|
|
(void)sig;
|
|
handler_runs++;
|
|
}
|
|
|
|
// A bounded wait. A wrong answer here is a hang, so nothing may wait for ever:
|
|
// sigsuspend returns -EINTR each time a signal is delivered, and a kernel that
|
|
// never delivers spins out instead of stopping the test dead.
|
|
#define SPINS 300
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
uint64 blocked, unblocked, old = 0;
|
|
int pid, status, i;
|
|
|
|
puts_("sigtest: the SIGCHLD/sigsuspend contract\n\n");
|
|
|
|
{
|
|
static char act[SA_SIZE];
|
|
*(uint64 *)act = (uint64)(void *)on_chld;
|
|
rt_sigaction(SIGCHLD, act, 0, 8);
|
|
}
|
|
|
|
// --- 1. blocking actually blocks --------------------------------------
|
|
blocked = U_SIGCHLD_BIT;
|
|
sigprocmask(SIG_BLOCK, (const sigset_t *)&blocked, (sigset_t *)&old);
|
|
|
|
handler_runs = 0;
|
|
pid = fork();
|
|
if (pid == 0)
|
|
exit_group(0);
|
|
|
|
// Every chance for the child to exit and for the kernel to deliver
|
|
// wrongly. If the mask is honoured, the handler has not run.
|
|
for (i = 0; i < 50; i++)
|
|
getpid();
|
|
sleep(10);
|
|
check("SIGCHLD stays pending while blocked", handler_runs == 0);
|
|
|
|
// --- 2. sigsuspend delivers what is already pending --------------------
|
|
// The case that matters, and the one that hangs: the child is already
|
|
// gone, so nothing new will ever arrive. sigsuspend has to deliver what is
|
|
// *pending*, not wait for the next one.
|
|
unblocked = ~U_SIGCHLD_BIT;
|
|
for (i = 0; i < SPINS && handler_runs == 0; i++)
|
|
rt_sigsuspend(&unblocked);
|
|
|
|
check("sigsuspend delivers a signal that was already pending",
|
|
handler_runs > 0);
|
|
check("and it did not have to spin to get it", i <= 1);
|
|
check("the handler ran exactly once", handler_runs == 1);
|
|
|
|
// --- 3. the mask survives ----------------------------------------------
|
|
{
|
|
uint64 now = 0;
|
|
sigprocmask(SIG_BLOCK, 0, (sigset_t *)&now);
|
|
check("sigsuspend restores the mask it was called with",
|
|
(now & U_SIGCHLD_BIT) != 0);
|
|
}
|
|
|
|
sigprocmask(SIG_SETMASK, (const sigset_t *)&old, 0);
|
|
|
|
// --- 4. the child is still reapable ------------------------------------
|
|
status = -1;
|
|
check("the child can still be reaped after the handler ran",
|
|
waitpid(pid, &status, 0) == pid);
|
|
check("and its status says it exited cleanly", status == 0);
|
|
|
|
// --- 5. the other order: the signal arrives while waiting ---------------
|
|
handler_runs = 0;
|
|
sigprocmask(SIG_BLOCK, (const sigset_t *)&blocked, (sigset_t *)&old);
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
sleep(30); // let the parent reach sigsuspend first
|
|
exit_group(0);
|
|
}
|
|
for (i = 0; i < SPINS && handler_runs == 0; i++)
|
|
rt_sigsuspend(&unblocked);
|
|
check("sigsuspend wakes for a signal that arrives while it waits",
|
|
handler_runs > 0);
|
|
sigprocmask(SIG_SETMASK, (const sigset_t *)&old, 0);
|
|
waitpid(pid, &status, 0);
|
|
|
|
puts_("\n");
|
|
puts_(failures ? "sigtest: FAILED\n" : "sigtest: all checks passed\n");
|
|
return failures ? 1 : 0;
|
|
}
|