Repository navigation
Conversation
BusyBox gates ash on !NOMMU because every subshell, pipeline member, command substitution and external command goes through fork(). The next commit replaces that fork with callback clone, so lift the Kconfig dependency and the compile-time #error.
wasm Linux has no return-twice fork or vfork. A clone without CLONE_VM copies the parent's memory, so it gives ash real fork semantics as long as the code after fork in the child is a callback rather than a second return. forkshell gains a continuation argument. Each of its five callers (here-doc writer, command substitution, subshell, pipeline member, external command) moves its "pid == 0" branch into a *_child function and passes its locals in a struct; the parent half stays inline. The child runs on a fresh 8MiB stack, matching the main process, because subshells recurse through evaltree. The parent's exception handlers are frames the child's stack does not have, so the callback installs its own. A forked child would unwind to ash_main's handler, which always calls exitshell because shlvl > 0; the callback does the same.
wasm Linux cannot interrupt userspace: the scheduler and signal delivery run only when a task enters the kernel. Hush happened to call waitpid() for each command, but ash runs a loop of builtins with no syscalls at all, so `sh -c 'while :; do :; done' &` held its CPU until a reboot and could not be killed. ash now makes one cheap syscall per evaluated node. getppid() is used because its entry polls the timer deadline (driving the tick and preemption) and its exit runs pending signals; sched_yield() would give the CPU away on every builtin regardless of the timeslice.
Upstream timeout daemonizes its watcher, which points the watcher's stdio at /dev/null. Our fork's callback-clone conversion of timeout dropped that, so the watcher kept the caller's stdout. ash execs the last command of $(...) in place and reaps it only after reading EOF, so `$(timeout 15 prog)` waited the full 15 seconds after prog exited. Hush hid this because an intermediate shell reaped prog.
tombl
added a commit
to tombl/distro
that referenced
this pull request
Oct 27, 2026
busybox is a fork, so its changes live on tombl/busybox rather than in patch files here. The four patches become the wasm-ash branch (tombl/busybox#5), stacked on vfork-child-exit (tombl/busybox#4).
This was referenced Oct 27, 2026
tombl
added a commit
to tombl/distro
that referenced
this pull request
Oct 27, 2026
busybox is a fork, so its changes live on tombl/busybox rather than in patch files here. The four patches become the wasm-ash branch (tombl/busybox#5), stacked on vfork-child-exit (tombl/busybox#4).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves distro#198's four in-tree busybox patches onto the fork as commits. Tom's rule is that each upstream project is either a fork or in-tree patches, never both, and busybox is a fork. The changes are the same as in #198's patch files; only the commit messages were adapted.
Stacked on #4 (
vfork-child-exit, 0ef30c9). The first four commits in this diff are #4's; this PR's own commits are the last four.shell/ash: allow ash without an MMU: drops the!NOMMUKconfig dependencies and the#errorin ash.c.shell/ash: fork through callback clone:forkshelltakes a continuation. Its five callers (here-doc writer,$(...), subshell, pipeline member, external command) move theirpid == 0branch into a*_childcallback. The child runs on a copy-clone with an 8MiB stack and installs its own exception handler, which callsexitshell().shell/ash: enter the kernel once per evaluated node: onegetppid()per node inevaltree. wasm Linux only schedules and delivers signals on kernel entry, so without it a builtin-only loop likewhile :; do :; done &can't be preempted or killed. This is the janky one. It's here because busybox is a fork; distro#198's body has the benchmark.timeout: detach the watcher's stdio: fixes this fork's own callback-clone conversion oftimeout, which left the watcher holding the caller's stdout. Under ash,$(timeout 15 prog)therefore waited the full 15s.Merge order
masterto 0ef30c9 (LANDING S2).masterto 7a7adb6. Don't squash or rebase, so distro's pin stays onmasterwith no repin.If this PR is squashed instead, repin #198 to the squash commit and recompute its hash.
Tested by distro#198's CI: busybox smoke/processes/networking/shell/interactive checks and the full flake check set.