mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
BusyBox init now boots a userspace under gex. It parses an /etc/inittab, runs a sysinit script that mounts its own /proc and /tmp, starts services in their own sessions, adopts a daemon whose parent has exited, and powers the machine off at the end. Most of what was missing was not hard, only absent. The kernel had no setsid, setpgid, getpgid or getsid; getppid returned a literal zero; mount, reboot, sync, prctl and the set*id family were unimplemented; and socket answered ENOSYS, which sends a caller looking for a fallback rather than telling it there is no networking. The structural piece is crates/gex-kernel/src/proc.rs, a process table shared by every process. Three things need it. Pids have to be unique system-wide, and each forked kernel used to carry its own counter offset from its parent's by a thousand -- fine for a shallow tree, not for an init that respawns for the life of the machine. Orphans have to be reparented to pid 1, which a parent-owned child list cannot express, because the exiting process would have to reach into init's kernel behind another thread's lock. And groups and sessions span processes by definition: kill(-pgid) asks about processes the caller may not be related to. Cross-process signals go through the table too. A sender cannot deliver -- the target's signal state is behind its own lock and it may be running, blocked in the host, or between the two -- so the sender leaves the signal pending and the target collects it at its next syscall, which is where a halt is noticed as well. reboot() from any process stops every process, rather than only the one that called it. Two bugs surfaced on the way. kill(-pgid) signalled the caller even when the caller was in another group, because the syscall restated the membership rule instead of asking the table; that turned the group-kill test into a SIGTERM to gex itself. And a forked child's /proc still pointed at its parent's state, because the VFS mount was cloned wholesale, so every child read its ancestor's pid, maps and command line from /proc/self. The native host also carried a second, staler copy of ProcSource with PPid hardcoded to zero; there is one now. tests/integration/programs/init.c is a pid 1 built for the suite: its own session, a service in another, an orphan it has to adopt, and a process group it stops with one signal. Children report through exit status rather than printing, so the transcript does not depend on scheduling. 89 stages pass, 0 fail. tests/real-programs/busybox-init.sh does the same with an init nobody wrote for gex. Also here: bench/, a CPU-time A/B harness. Wall time on this machine swings 8-15% between rounds, which is wider than most of what is being measured. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01KcidnrZ5aLqKoBJBqdjx9d
80 lines
2.8 KiB
C
80 lines
2.8 KiB
C
/* A small register VM, for the shape an interpreter has.
|
|
*
|
|
* Dispatch is a computed goto, so the hot loop is an indirect branch that
|
|
* gex cannot see the target of -- which is what Lua, CPython and every other
|
|
* bytecode interpreter look like from the translator's side, and a shape that
|
|
* defeats anything relying on statically known successors.
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
enum { OP_HALT, OP_LOADI, OP_MOV, OP_ADD, OP_SUB, OP_MUL, OP_XOR, OP_SHR,
|
|
OP_JMPNZ, OP_JMP, OP_LOAD, OP_STORE, OP_CMPLT, OP_NOPS };
|
|
|
|
struct insn { uint8_t op, a, b, c; int32_t imm; };
|
|
|
|
#define MEMSZ 4096
|
|
|
|
static uint64_t run(const struct insn *code, uint64_t iterations) {
|
|
static const void *table[OP_NOPS] = {
|
|
&&halt, &&loadi, &&mov, &&add, &&sub, &&mul, &&xor_, &&shr,
|
|
&&jmpnz, &&jmp, &&load, &&store, &&cmplt,
|
|
};
|
|
uint64_t r[16] = {0}, mem[MEMSZ] = {0};
|
|
r[15] = iterations;
|
|
const struct insn *ip = code;
|
|
#define NEXT() goto *table[(++ip)->op]
|
|
goto *table[ip->op];
|
|
halt:
|
|
return r[0] ^ r[1] ^ mem[7] ^ mem[MEMSZ - 1];
|
|
loadi:
|
|
r[ip->a] = (uint64_t)(int64_t)ip->imm; NEXT();
|
|
mov:
|
|
r[ip->a] = r[ip->b]; NEXT();
|
|
add:
|
|
r[ip->a] = r[ip->b] + r[ip->c]; NEXT();
|
|
sub:
|
|
r[ip->a] = r[ip->b] - r[ip->c]; NEXT();
|
|
mul:
|
|
r[ip->a] = r[ip->b] * r[ip->c]; NEXT();
|
|
xor_:
|
|
r[ip->a] = r[ip->b] ^ r[ip->c]; NEXT();
|
|
shr:
|
|
r[ip->a] = r[ip->b] >> (ip->imm & 63); NEXT();
|
|
load:
|
|
r[ip->a] = mem[r[ip->b] % MEMSZ]; NEXT();
|
|
store:
|
|
mem[r[ip->a] % MEMSZ] = r[ip->b]; NEXT();
|
|
cmplt:
|
|
r[ip->a] = r[ip->b] < r[ip->c]; NEXT();
|
|
jmpnz:
|
|
if (r[ip->a]) { ip = code + ip->imm; goto *table[ip->op]; }
|
|
NEXT();
|
|
jmp:
|
|
ip = code + ip->imm;
|
|
goto *table[ip->op];
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
uint64_t n = argc > 1 ? (uint64_t)atol(argv[1]) : 3000000;
|
|
/* h = mix(h, i); store; i-- ; loop while i */
|
|
static struct insn code[] = {
|
|
{OP_LOADI, 0, 0, 0, 1}, /* 0: h = 1 */
|
|
{OP_LOADI, 2, 0, 0, 31}, /* 1: k = 31 */
|
|
{OP_LOADI, 3, 0, 0, 1}, /* 2: one = 1 */
|
|
{OP_MUL, 0, 0, 2, 0}, /* 3: h *= k */
|
|
{OP_XOR, 0, 0, 15, 0}, /* 4: h ^= i */
|
|
{OP_SHR, 4, 0, 0, 13}, /* 5: t = h >> 13 */
|
|
{OP_XOR, 0, 0, 4, 0}, /* 6: h ^= t */
|
|
{OP_STORE, 0, 0, 0, 0}, /* 7: mem[h] = h */
|
|
{OP_LOAD, 5, 0, 0, 0}, /* 8: u = mem[h] */
|
|
{OP_ADD, 1, 1, 5, 0}, /* 9: acc += u */
|
|
{OP_SUB, 15, 15, 3, 0}, /* 10: i -= 1 */
|
|
{OP_JMPNZ, 15, 0, 0, 3}, /* 11: if i goto 3 */
|
|
{OP_HALT, 0, 0, 0, 0},
|
|
};
|
|
printf("%llu\n", (unsigned long long)run(code, n));
|
|
return 0;
|
|
}
|