Files
velzie 4d2a46b50e fork and wait4
A clone without CLONE_THREAD is a process, and a process needs a second
address space. It gets one: a fresh shared memory with the parent's
mapped regions copied into it. No copy-on-write, because wasm gives the
host no way to fault on a write, so the cost is proportional to what the
parent has mapped rather than to what the child touches.

What the child does share is everything Linux says it should. Descriptors
are Arc handles, so parent and child hold the same open file
descriptions, offsets included, while the tables are separate. Signal
dispositions and the blocked mask are inherited; the pending set is not.
Futex queues are per-address-space, so the child gets its own.

The child reuses every module the parent has translated. Same program,
same addresses, and a translated module imports the memory it acts on
rather than owning it -- so instantiating one against a different address
space is exactly what that import is for.

Two bugs on the way, both worth recording. The store's watermark into the
shared module registry was advanced by hand after a translation, which is
wrong whenever another thread appended in between: the store then
reinstalls everything from the stale index, and one store ended up with
ten thousand instances of the same modules. Publishing to the registry
and letting the replay path do the installing removes the second way to
get there.

And the child was copying guest memory only up to RESERVED_END, described
in my own comment as covering the dispatch directory. The directory
*begins* at RESERVED_END. The child therefore started with an all-zero
directory, every page resolved to the miss stub, and the first branch
after the fork translated the same block forever.

wait4 is a blocking op like futex and nanosleep, so it waits with the
kernel unlocked. WNOHANG, a specific pid or -1, ECHILD when there is
nothing left, and the exit code in bits 8..16 of the status word.

fork.c: memory isolation across the fork, exit status through waitpid,
four children collected in completion order, and ECHILD after. Identical
to QEMU, ten runs. Suite is 43.
2026-08-05 09:42:25 -04:00

50 lines
1.4 KiB
C

/* fork, exit status, and wait4. */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
static long shared_before_fork = 11;
int main(void) {
/* A child sees the parent's memory as it was, and its writes stay its own. */
static char buf[64];
strcpy(buf, "set by parent");
shared_before_fork = 42;
pid_t p = fork();
if (p < 0) { printf("fork failed\n"); return 1; }
if (p == 0) {
printf("child sees %ld and '%s'\n", shared_before_fork, buf);
shared_before_fork = 99;
strcpy(buf, "set by child");
printf("child now %ld\n", shared_before_fork);
fflush(stdout);
_exit(3);
}
int st = 0;
pid_t w = waitpid(p, &st, 0);
printf("waited ok=%d exited=%d status=%d\n", w == p, WIFEXITED(st), WEXITSTATUS(st));
printf("parent still %ld and '%s'\n", shared_before_fork, buf);
/* Several children, collected in whatever order they finish. */
int total = 0;
for (int i = 0; i < 4; i++) {
pid_t c = fork();
if (c == 0) _exit(i + 1);
}
for (int i = 0; i < 4; i++) {
int s2 = 0;
pid_t c = wait(&s2);
if (c > 0 && WIFEXITED(s2)) total += WEXITSTATUS(s2);
}
printf("collected total %d\n", total);
/* No children left. */
int s3 = 0;
printf("wait with no children: %d\n", wait(&s3));
printf("done\n");
return 0;
}