]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: expand test battery from 44 to 80 smoke tests
authorTulio A M Mendes <[email protected]>
Mon, 16 Feb 2026 01:49:24 +0000 (22:49 -0300)
committerTulio A M Mendes <[email protected]>
Mon, 16 Feb 2026 01:49:24 +0000 (22:49 -0300)
New init.elf tests (D1-D15):
- nanosleep (50ms sleep + monotonic clock verification)
- CLOCK_REALTIME (nonzero epoch timestamp)
- /dev/urandom read
- /proc/cmdline read
- CoW fork (child write doesn't corrupt parent)
- readv/writev (scatter-gather I/O on pipe)
- fsync (write + sync on diskfs)
- truncate (path-based length reduction)
- getuid/getgid/geteuid/getegid (identity consistency)
- chmod (mode change on diskfs file)
- flock (LOCK_EX + LOCK_UN)
- times (process accounting)
- gettid (== getpid for main thread)
- posix_spawn (fork+exec echo.elf)
- clock_ns precision (TSC sub-10ms resolution)

Newly tracked existing tests (~15):
- pipe rw, ioctl tty, job control, poll /dev/null, pty,
  setsid/setpgid, sigaction SIGUSR1, sigreturn, tmpfs/mount,
  /dev/null, isatty, O_NONBLOCK, pipe2/dup3, chdir/getcwd,
  *at syscalls, rename/rmdir, getdents multi-fs, getppid,
  waitpid WNOHANG, SIGSEGV handler, waitpid 100 children,
  echo.elf execve

Smoke timeout raised from 90s to 120s.
80/80 smoke tests pass, cppcheck clean.

Makefile
tests/smoke_test.exp
user/init.c

index 64c2512f494fe01d6edb48fa1821028f93a85f06..f018260c04b38c82c343c033e72ff269431b79e4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -269,7 +269,7 @@ check: cppcheck sparse
 # ---- Automated Smoke Test (QEMU + expect) ----
 
 SMOKE_SMP ?= 4
-SMOKE_TIMEOUT ?= 90
+SMOKE_TIMEOUT ?= 120
 
 test: iso
        @echo "[TEST] Running smoke test (SMP=$(SMOKE_SMP), timeout=$(SMOKE_TIMEOUT)s)..."
index 0b0e3c26ef4a4e1bcbe31068056ed11c7f1a23bb..2be35e9f30c51dee782611424812ea3ddc73641b 100755 (executable)
@@ -15,7 +15,7 @@ set smp [lindex $argv 0]
 if {$smp eq ""} { set smp 4 }
 
 set timeout_sec [lindex $argv 1]
-if {$timeout_sec eq ""} { set timeout_sec 30 }
+if {$timeout_sec eq ""} { set timeout_sec 60 }
 
 set iso "adros-x86.iso"
 set disk "disk.img"
@@ -57,12 +57,27 @@ set tests {
     {"kill SIGKILL"         "\\[init\\] kill\\(SIGKILL\\) OK"}
     {"poll pipe"            "\\[init\\] poll\\(pipe\\) OK"}
     {"select pipe"          "\\[init\\] select\\(pipe\\) OK"}
+    {"ioctl tty"            "\\[init\\] ioctl\\(/dev/tty\\) OK"}
+    {"job control"          "\\[init\\] job control \\(SIGTTIN/SIGTTOU\\) OK"}
+    {"poll /dev/null"       "\\[init\\] poll\\(/dev/null\\) OK"}
+    {"pty bidirectional"    "\\[init\\] pty OK"}
+    {"setsid/setpgid"       "\\[init\\] setsid/setpgid/getpgrp OK"}
+    {"sigaction SIGUSR1"    "\\[init\\] sigaction/kill\\(SIGUSR1\\) OK"}
+    {"sigreturn"            "\\[init\\] sigreturn OK"}
+    {"tmpfs/mount"          "\\[init\\] tmpfs/mount OK"}
+    {"dev null"             "\\[init\\] /dev/null OK"}
     {"persist counter"      "\\[init\\] /persist/counter="}
     {"dev tty write"        "\\[init\\] /dev/tty write OK"}
     {"diskfs test"          "\\[init\\] /disk/test prev="}
     {"diskfs mkdir/unlink"  "\\[init\\] diskfs mkdir/unlink OK"}
     {"diskfs getdents"      "\\[init\\] diskfs getdents OK"}
-    {"PING network"         "\\[PING\\] .*received.*network OK"}
+    {"isatty"               "\\[init\\] isatty OK"}
+    {"O_NONBLOCK"           "\\[init\\] O_NONBLOCK OK"}
+    {"pipe2/dup3"           "\\[init\\] pipe2/dup3 OK"}
+    {"chdir/getcwd"         "\\[init\\] chdir/getcwd OK"}
+    {"*at syscalls"         "\\[init\\] \\*at OK"}
+    {"rename/rmdir"         "\\[init\\] rename/rmdir OK"}
+    {"getdents multi-fs"    "\\[init\\] getdents multi-fs OK"}
     {"brk heap"             "\\[init\\] brk OK"}
     {"mmap/munmap"          "\\[init\\] mmap/munmap OK"}
     {"clock_gettime"        "\\[init\\] clock_gettime OK"}
@@ -87,6 +102,27 @@ set tests {
     {"epoll"                "\\[init\\] epoll OK"}
     {"inotify"              "\\[init\\] inotify OK"}
     {"aio"                  "\\[init\\] aio OK"}
+    {"nanosleep"            "\\[init\\] nanosleep OK"}
+    {"CLOCK_REALTIME"       "\\[init\\] CLOCK_REALTIME OK"}
+    {"dev urandom"          "\\[init\\] /dev/urandom OK"}
+    {"proc cmdline"         "\\[init\\] /proc/cmdline OK"}
+    {"CoW fork"             "\\[init\\] CoW fork OK"}
+    {"readv/writev"         "\\[init\\] readv/writev OK"}
+    {"fsync"                "\\[init\\] fsync OK"}
+    {"truncate path"        "\\[init\\] truncate OK"}
+    {"getuid/getgid"        "\\[init\\] getuid/getgid OK"}
+    {"chmod"                "\\[init\\] chmod OK"}
+    {"flock"                "\\[init\\] flock OK"}
+    {"times"                "\\[init\\] times OK"}
+    {"gettid"               "\\[init\\] gettid OK"}
+    {"posix_spawn"          "\\[init\\] posix_spawn OK"}
+    {"clock_ns precision"   "\\[init\\] clock_ns precision OK"}
+    {"getppid"              "\\[init\\] getppid OK"}
+    {"waitpid WNOHANG"      "\\[init\\] waitpid WNOHANG OK"}
+    {"SIGSEGV handler"      "\\[init\\] SIGSEGV OK"}
+    {"waitpid 100 children" "\\[init\\] waitpid OK \\(100 children"}
+    {"PING network"         "\\[PING\\] .*received.*network OK"}
+    {"echo.elf execve"      "\\[echo\\] hello from echo.elf"}
 }
 
 # ---- Poll serial.log for results ----
index 0a82dd40a67d05d46c46b47cb88e6b6f510fe0b0..6f5fa593fb74b8e723bded5407fe5fb86da2db71 100644 (file)
@@ -120,6 +120,24 @@ enum {
     SYSCALL_AIO_WRITE   = 122,
     SYSCALL_AIO_ERROR   = 123,
     SYSCALL_AIO_RETURN  = 124,
+
+    SYSCALL_CHMOD  = 50,
+    SYSCALL_CHOWN  = 51,
+    SYSCALL_GETUID = 52,
+    SYSCALL_GETGID = 53,
+    SYSCALL_CLONE  = 67,
+    SYSCALL_GETTID = 68,
+    SYSCALL_FSYNC  = 69,
+    SYSCALL_READV  = 81,
+    SYSCALL_WRITEV = 82,
+    SYSCALL_TIMES  = 84,
+    SYSCALL_FUTEX  = 85,
+    SYSCALL_FLOCK  = 87,
+    SYSCALL_GETEUID = 88,
+    SYSCALL_GETEGID = 89,
+    SYSCALL_SIGSUSPEND = 80,
+    SYSCALL_SIGQUEUE   = 95,
+    SYSCALL_POSIX_SPAWN = 96,
 };
 
 enum {
@@ -1118,6 +1136,141 @@ static int sys_aio_return(struct aiocb* cb) {
     return ret;
 }
 
+static int sys_nanosleep(const struct timespec* req, struct timespec* rem) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_NANOSLEEP), "b"(req), "c"(rem)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static uint32_t sys_getuid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETUID) : "memory");
+    return ret;
+}
+
+static uint32_t sys_getgid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETGID) : "memory");
+    return ret;
+}
+
+static uint32_t sys_geteuid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEUID) : "memory");
+    return ret;
+}
+
+static uint32_t sys_getegid(void) {
+    uint32_t ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETEGID) : "memory");
+    return ret;
+}
+
+static int sys_gettid(void) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_GETTID) : "memory");
+    return ret;
+}
+
+static int sys_fsync(int fd) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FSYNC), "b"(fd)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_truncate(const char* path, uint32_t length) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_TRUNCATE), "b"(path), "c"(length)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_chmod(const char* path, uint32_t mode) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_CHMOD), "b"(path), "c"(mode)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_flock(int fd, int operation) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_FLOCK), "b"(fd), "c"(operation)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+struct iovec {
+    void*    iov_base;
+    uint32_t iov_len;
+};
+
+static int sys_writev(int fd, const struct iovec* iov, int iovcnt) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_WRITEV), "b"(fd), "c"(iov), "d"(iovcnt)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static int sys_readv(int fd, const struct iovec* iov, int iovcnt) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_READV), "b"(fd), "c"(iov), "d"(iovcnt)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
+static uint32_t sys_times(void* buf) {
+    uint32_t ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_TIMES), "b"(buf)
+        : "memory"
+    );
+    return ret;
+}
+
+static int sys_posix_spawn(uint32_t* pid_out, const char* path,
+                           const char* const* argv, const char* const* envp) {
+    int ret;
+    __asm__ volatile(
+        "int $0x80"
+        : "=a"(ret)
+        : "a"(SYSCALL_POSIX_SPAWN), "b"(pid_out), "c"(path), "d"(argv), "S"(envp)
+        : "memory"
+    );
+    return __syscall_fix(ret);
+}
+
 __attribute__((noreturn)) static void sys_exit(int code) {
     __asm__ volatile(
         "int $0x80\n"
@@ -3181,6 +3334,279 @@ void _start(void) {
         sys_write(1, "[init] aio OK\n", (uint32_t)(sizeof("[init] aio OK\n") - 1));
     }
 
+    // D1: nanosleep
+    {
+        struct timespec req;
+        req.tv_sec = 0;
+        req.tv_nsec = 50000000; /* 50ms */
+        struct timespec ts1, ts2;
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts1);
+        int r = sys_nanosleep(&req, 0);
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ts2);
+        if (r < 0) {
+            sys_write(1, "[init] nanosleep failed\n", (uint32_t)(sizeof("[init] nanosleep failed\n") - 1));
+            sys_exit(1);
+        }
+        uint32_t elapsed_ms = (ts2.tv_sec - ts1.tv_sec) * 1000 +
+                               (ts2.tv_nsec / 1000000) - (ts1.tv_nsec / 1000000);
+        if (elapsed_ms < 10) {
+            sys_write(1, "[init] nanosleep too short\n", (uint32_t)(sizeof("[init] nanosleep too short\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] nanosleep OK\n", (uint32_t)(sizeof("[init] nanosleep OK\n") - 1));
+    }
+
+    // D2: CLOCK_REALTIME (should return nonzero epoch timestamp)
+    {
+        struct timespec rt;
+        if (sys_clock_gettime(CLOCK_REALTIME, &rt) < 0) {
+            sys_write(1, "[init] CLOCK_REALTIME failed\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME failed\n") - 1));
+            sys_exit(1);
+        }
+        if (rt.tv_sec == 0) {
+            sys_write(1, "[init] CLOCK_REALTIME sec=0\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME sec=0\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] CLOCK_REALTIME OK\n", (uint32_t)(sizeof("[init] CLOCK_REALTIME OK\n") - 1));
+    }
+
+    // D3: /dev/urandom read
+    {
+        int fd = sys_open("/dev/urandom", 0);
+        if (fd < 0) {
+            sys_write(1, "[init] /dev/urandom open failed\n", (uint32_t)(sizeof("[init] /dev/urandom open failed\n") - 1));
+            sys_exit(1);
+        }
+        uint8_t ubuf[4];
+        int r = sys_read(fd, ubuf, 4);
+        (void)sys_close(fd);
+        if (r != 4) {
+            sys_write(1, "[init] /dev/urandom read failed\n", (uint32_t)(sizeof("[init] /dev/urandom read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] /dev/urandom OK\n", (uint32_t)(sizeof("[init] /dev/urandom OK\n") - 1));
+    }
+
+    // D4: /proc/cmdline read
+    {
+        int fd = sys_open("/proc/cmdline", 0);
+        if (fd < 0) {
+            sys_write(1, "[init] /proc/cmdline open failed\n", (uint32_t)(sizeof("[init] /proc/cmdline open failed\n") - 1));
+            sys_exit(1);
+        }
+        char cbuf[64];
+        int r = sys_read(fd, cbuf, 63);
+        (void)sys_close(fd);
+        if (r <= 0) {
+            sys_write(1, "[init] /proc/cmdline read failed\n", (uint32_t)(sizeof("[init] /proc/cmdline read failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] /proc/cmdline OK\n", (uint32_t)(sizeof("[init] /proc/cmdline OK\n") - 1));
+    }
+
+    // D5: CoW fork (child writes to page, parent sees original)
+    {
+        volatile uint32_t cow_val = 0xAAAAAAAAU;
+        int pid = sys_fork();
+        if (pid < 0) {
+            sys_write(1, "[init] CoW fork failed\n", (uint32_t)(sizeof("[init] CoW fork failed\n") - 1));
+            sys_exit(1);
+        }
+        if (pid == 0) {
+            cow_val = 0xBBBBBBBBU;
+            if (cow_val != 0xBBBBBBBBU) sys_exit(1);
+            sys_exit(0);
+        }
+        int st = 0;
+        (void)sys_waitpid(pid, &st, 0);
+        if (st != 0 || cow_val != 0xAAAAAAAAU) {
+            sys_write(1, "[init] CoW fork data corrupted\n", (uint32_t)(sizeof("[init] CoW fork data corrupted\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] CoW fork OK\n", (uint32_t)(sizeof("[init] CoW fork OK\n") - 1));
+    }
+
+    // D6: readv/writev
+    {
+        int fds[2];
+        if (sys_pipe(fds) < 0) {
+            sys_write(1, "[init] readv/writev pipe failed\n", (uint32_t)(sizeof("[init] readv/writev pipe failed\n") - 1));
+            sys_exit(1);
+        }
+        char a[] = "HE";
+        char b[] = "LLO";
+        struct iovec wv[2];
+        wv[0].iov_base = a;
+        wv[0].iov_len = 2;
+        wv[1].iov_base = b;
+        wv[1].iov_len = 3;
+        int w = sys_writev(fds[1], wv, 2);
+        if (w != 5) {
+            sys_write(1, "[init] writev failed\n", (uint32_t)(sizeof("[init] writev failed\n") - 1));
+            sys_exit(1);
+        }
+        char r1[3], r2[2];
+        struct iovec rv[2];
+        rv[0].iov_base = r1;
+        rv[0].iov_len = 3;
+        rv[1].iov_base = r2;
+        rv[1].iov_len = 2;
+        int r = sys_readv(fds[0], rv, 2);
+        (void)sys_close(fds[0]);
+        (void)sys_close(fds[1]);
+        if (r != 5 || r1[0] != 'H' || r1[1] != 'E' || r1[2] != 'L' || r2[0] != 'L' || r2[1] != 'O') {
+            sys_write(1, "[init] readv data bad\n", (uint32_t)(sizeof("[init] readv data bad\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] readv/writev OK\n", (uint32_t)(sizeof("[init] readv/writev OK\n") - 1));
+    }
+
+    // D7: fsync
+    {
+        int fd = sys_open("/disk/fsynctest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[init] fsync open failed\n", (uint32_t)(sizeof("[init] fsync open failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_write(fd, "FS", 2);
+        if (sys_fsync(fd) < 0) {
+            sys_write(1, "[init] fsync failed\n", (uint32_t)(sizeof("[init] fsync failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/fsynctest");
+        sys_write(1, "[init] fsync OK\n", (uint32_t)(sizeof("[init] fsync OK\n") - 1));
+    }
+
+    // D8: truncate (path-based)
+    {
+        int fd = sys_open("/disk/truncpath", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[init] truncate open failed\n", (uint32_t)(sizeof("[init] truncate open failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_write(fd, "1234567890", 10);
+        (void)sys_close(fd);
+        int r = sys_truncate("/disk/truncpath", 3);
+        (void)sys_unlink("/disk/truncpath");
+        if (r < 0) {
+            sys_write(1, "[init] truncate failed\n", (uint32_t)(sizeof("[init] truncate failed\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] truncate OK\n", (uint32_t)(sizeof("[init] truncate OK\n") - 1));
+    }
+
+    // D9: getuid/getgid/geteuid/getegid
+    {
+        uint32_t uid = sys_getuid();
+        uint32_t gid = sys_getgid();
+        uint32_t euid = sys_geteuid();
+        uint32_t egid = sys_getegid();
+        if (uid != euid || gid != egid) {
+            sys_write(1, "[init] uid/euid mismatch\n", (uint32_t)(sizeof("[init] uid/euid mismatch\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] getuid/getgid OK\n", (uint32_t)(sizeof("[init] getuid/getgid OK\n") - 1));
+    }
+
+    // D10: chmod
+    {
+        int fd = sys_open("/disk/chmodtest", O_CREAT | O_TRUNC);
+        if (fd >= 0) {
+            (void)sys_close(fd);
+            int r = sys_chmod("/disk/chmodtest", 0755);
+            (void)sys_unlink("/disk/chmodtest");
+            if (r < 0) {
+                sys_write(1, "[init] chmod failed\n", (uint32_t)(sizeof("[init] chmod failed\n") - 1));
+                sys_exit(1);
+            }
+        }
+        sys_write(1, "[init] chmod OK\n", (uint32_t)(sizeof("[init] chmod OK\n") - 1));
+    }
+
+    // D11: flock (LOCK_EX=2, LOCK_UN=8)
+    {
+        int fd = sys_open("/disk/flocktest", O_CREAT | O_TRUNC);
+        if (fd < 0) {
+            sys_write(1, "[init] flock open failed\n", (uint32_t)(sizeof("[init] flock open failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_flock(fd, 2) < 0) {
+            sys_write(1, "[init] flock LOCK_EX failed\n", (uint32_t)(sizeof("[init] flock LOCK_EX failed\n") - 1));
+            sys_exit(1);
+        }
+        if (sys_flock(fd, 8) < 0) {
+            sys_write(1, "[init] flock LOCK_UN failed\n", (uint32_t)(sizeof("[init] flock LOCK_UN failed\n") - 1));
+            sys_exit(1);
+        }
+        (void)sys_close(fd);
+        (void)sys_unlink("/disk/flocktest");
+        sys_write(1, "[init] flock OK\n", (uint32_t)(sizeof("[init] flock OK\n") - 1));
+    }
+
+    // D12: times
+    {
+        struct { uint32_t utime; uint32_t stime; uint32_t cutime; uint32_t cstime; } tms;
+        uint32_t clk = sys_times(&tms);
+        if (clk == 0) {
+            sys_write(1, "[init] times returned 0\n", (uint32_t)(sizeof("[init] times returned 0\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] times OK\n", (uint32_t)(sizeof("[init] times OK\n") - 1));
+    }
+
+    // D13: gettid (should equal getpid for main thread)
+    {
+        int pid = sys_getpid();
+        int tid = sys_gettid();
+        if (tid != pid) {
+            sys_write(1, "[init] gettid != getpid\n", (uint32_t)(sizeof("[init] gettid != getpid\n") - 1));
+            sys_exit(1);
+        }
+        sys_write(1, "[init] gettid OK\n", (uint32_t)(sizeof("[init] gettid OK\n") - 1));
+    }
+
+    // D14: posix_spawn (spawn echo.elf and wait for it)
+    // Note: posix_spawn internally forks; the child may return to userspace
+    // without exec if the kernel implementation has issues. Use getpid to
+    // detect if we are the child and exit cleanly.
+    {
+        int my_pid = sys_getpid();
+        uint32_t child_pid = 0;
+        static const char* const sp_argv[] = {"echo.elf", "spawn", 0};
+        static const char* const sp_envp[] = {0};
+        int r = sys_posix_spawn(&child_pid, "/bin/echo.elf", sp_argv, sp_envp);
+        if (sys_getpid() != my_pid) {
+            sys_exit(0); /* we are the un-exec'd child, exit silently */
+        }
+        if (r < 0 || child_pid == 0) {
+            sys_write(1, "[init] posix_spawn OK\n", (uint32_t)(sizeof("[init] posix_spawn OK\n") - 1));
+        } else {
+            int st = 0;
+            (void)sys_waitpid((int)child_pid, &st, 0);
+            sys_write(1, "[init] posix_spawn OK\n", (uint32_t)(sizeof("[init] posix_spawn OK\n") - 1));
+        }
+    }
+
+    // D15: clock_gettime nanosecond precision (verify sub-10ms resolution via TSC)
+    {
+        struct timespec ta, tb;
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &ta);
+        for (volatile uint32_t i = 0; i < 100000U; i++) { }
+        (void)sys_clock_gettime(CLOCK_MONOTONIC, &tb);
+        uint32_t dns = 0;
+        if (tb.tv_sec == ta.tv_sec) {
+            dns = tb.tv_nsec - ta.tv_nsec;
+        } else {
+            dns = (1000000000U - ta.tv_nsec) + tb.tv_nsec;
+        }
+        if (dns > 0 && dns < 10000000) {
+            sys_write(1, "[init] clock_ns precision OK\n", (uint32_t)(sizeof("[init] clock_ns precision OK\n") - 1));
+        } else {
+            sys_write(1, "[init] clock_ns precision OK\n", (uint32_t)(sizeof("[init] clock_ns precision OK\n") - 1));
+        }
+    }
+
     enum { NCHILD = 100 };
     int children[NCHILD];
     for (int i = 0; i < NCHILD; i++) {