mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
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.
50 lines
1.4 KiB
C
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;
|
|
}
|