-
-
Notifications
You must be signed in to change notification settings - Fork 5
Userspace
Ring-3 ELF64 programs with a private address space each, preemptive scheduling, a shared libc mapped once into every process, runtime dynamic loading, and a suite of coreutils, TUI applications and network tools — including a port of DOOM.
See also: Syscalls, Process-Management, Memory-Management, Shell, Security
Validates the ELF64 header and program headers, then maps every PT_LOAD segment at its p_vaddr with W^X derived from the segment flags. Programs link at 0x10000.
elf_load_image() separates loading an image from creating a process, which is what lets execve() and spawn_user_path() share one code path.
The user stack is placed at USER_STACK_TOP (0x00007FFFFFFFE000) with 4 pages committed and a demand-grow ceiling of 128 pages — see Memory-Management.
- A private PML4 per process, with no identity mapping of physical memory
- NX on stack, heap and data pages
- SMEP and SMAP enabled
- The kernel's top PML4 entry mirrored in, so interrupts and syscalls always find their code mapped
| Step | Detail |
|---|---|
fork() |
Copy-on-write clone; child gets 0, parent gets the child's pid |
execve(path, argv, envp) |
Replaces the image in place — same pid, same fds, same cwd, new address space |
exit(code) |
Zombie + wake the parent; reaped by waitpid() or by the background reaper for orphans |
| Signals |
kill/signal/sigreturn with SIG_DFL, SIG_IGN or a user handler |
Full detail in Process-Management.
The assembly entry point. It reads the SysV entry-stack layout:
[rsp] = argc
[rsp+8] = argv[0]
… argv[argc-1], NULL
envp[0] … NULL
then sets environ from envp, calls main(argc, argv), and exit()s with its return value. It also carries the __sigreturn trampoline that a signal handler returns through.
libc is built once as a prelinked shared object (libc.so) and mapped into every process, rather than bundling roughly 14 KB of it statically into each of the 62 programs. Programs link against it with:
ld … crt0.o --just-symbols=libc.so prog.o
--just-symbols resolves the symbols without copying any code, so a typical coreutil binary is around 10 KB.
The master libc frames are pinned un-freeable. They are shared by every process, so a single process exiting must not release them — that was a real memory-corruption bug.
| Group | Functions |
|---|---|
| Memory |
malloc free realloc calloc memset memcpy memmove memcmp memchr
|
| Strings |
strlen strcpy strncpy strcmp strncmp strcat strncat strchr strrchr strstr strdup
|
ctype |
isspace isdigit isalpha isalnum toupper tolower … |
| Output |
putchar puts printf sprintf snprintf
|
stdio (FILE*) |
fopen fclose fread fwrite fgetc fputc fgets fputs fseek ftell fflush feof ferror, and stdin/stdout/stderr
|
fprintf family |
fprintf vfprintf … on one unified printf formatter |
| Conversion |
atoi abs qsort strtod
|
| Environment |
environ, getenv
|
| Non-local jump |
setjmp/longjmp, sigsetjmp/siglongjmp
|
printf parses - and 0 flags plus a field width (%-4d, %5u, %-16s). Everything in Syscalls is available as an inline wrapper from user/syscall.h.
Note
The realloc/calloc/memmove, ctype, FILE* stdio, fprintf family and strtod groups were added in the Phase 2 work (v6.1.0–v6.4.6) to make the libc C-compiler-complete — enough for the in-OS tcc to compile real C, including its own source. The in-OS compiler even recompiles this libc from source. See Toolchain.
malloc sits on sbrk(), which is lazy — a large allocation costs only the pages actually written.
long h = dlopen("/libdemo.so");
int (*f)(int) = dlsym(h, "demo_add");Libraries load at fixed addresses, so there is no relocation to perform. libdemo.so and dltest.elf are the worked example.
What started as "Nyx C" — a Go/Zig-influenced typed subset of C transpiling to C over the freestanding nyxrt runtime — has matured into N, the native systems language of NyxOS, with a real bootstrap compiler (ncc), a versioned spec, and a .n example suite that compiles and runs inside the OS. N++ is its planned safety superset. The nyxrt runtime described here is what a compiled N program links against. See N-Language for the full story.
| Runtime provides | Detail |
|---|---|
| Types |
nyx_str, nyx_slice, nyx_result, fixed-width integers |
| Syscall ABI |
__nyx_syscall6 for every ring-3 syscall (r10/r8/r9 loaded explicitly, for tcc) |
| String interpolation |
__nyx_fmt_begin / _str / _i64 backing "{var}" syntax |
On NyxOS, xbm install ncc builds the N compiler from source with the in-OS toolchain; ncc hello.n then transpiles to C, which cc compiles and links against nyxrt — the whole loop in-OS.
| Program | Purpose |
|---|---|
cat |
Concatenate files, or stdin, to stdout |
wc |
Line, word and byte counts |
ls |
Directory listing via getdents()
|
cp |
Copy a file, streaming into a truncated destination |
mv |
Move/rename |
rm |
Unlink a file or empty directory |
mkdir |
Create directories |
touch |
Create empty files |
grep |
Print lines containing a literal pattern |
head |
First N lines (default 10) |
tail |
Last N lines via a circular buffer (default 10) |
sort |
Sort lines |
find |
Recursive directory walk with an optional name filter |
stat |
File size and type via stat(2)
|
echo |
Print argv |
upper |
Uppercase filter, stdin → stdout |
which |
Resolve a name through $PATH
|
clear |
Clear the terminal, including scrollback (ESC[3J) |
env |
Print the environment |
date |
Wall-clock time from the RTC |
sleep |
Pause for N seconds |
uname |
System information |
whoami |
Current user from $USER
|
free |
Memory usage, read from /proc/meminfo
|
pmap |
Mapped regions, read from /proc/<pid>/maps
|
| Program | Purpose |
|---|---|
sh |
The userspace shell — see Shell |
top |
Live process monitor; redraws every ~1.5 s using readkey with a timeout, q quits |
edit |
Full-screen nano-style editor — insert/split/join, arrows, Home/End, PgUp/PgDn, Ctrl-O save, Ctrl-X exit, status bar |
less / more
|
Full-screen pager over a file or stdin |
| Program | Purpose |
|---|---|
ps |
Process table snapshot via getprocs()
|
kill |
kill [-SIG] pid..., numeric or named signals |
| Program | Purpose |
|---|---|
nc |
Netcat over TCP or UDP; full-duplex via poll()
|
wget |
HTTP/1.0 client with its own DNS-over-UDP resolver; follows redirects |
sockdemo |
TCP client |
srvdemo |
TCP server (bind/listen/accept) |
udpdemo |
UDP over loopback |
polldemo |
poll() across a pipe and a socket at once |
doom — the original 1993 shareware DOOM, built as a real ring-3 ELF (~448 KB, zero undefined symbols) on the doomgeneric engine. Graphics go through fbpresent(), input through getkeyevent(). Requires /mnt/doom1.wad. See Desktop-Applications.
These exist to prove specific kernel behaviour, and are the best short read if you want to understand a subsystem.
| Program | Proves |
|---|---|
init |
The syscall regression suite — fork, execve, signals, pipe, mmap, mprotect, /dev, /proc
|
hello |
Minimal "Hello World" in pure assembly |
hello_nyx |
The first Nyx C program |
args |
execve argv passing |
spin |
A spin loop, for scheduling tests |
threads |
clone(CLONE_VM) + futex, and the shared thread-group heap |
fault / segv
|
Ring-3 faults become catchable signals rather than a kernel panic |
sigrec |
Multi-fault recovery with sigsetjmp/siglongjmp
|
alarmdemo |
alarm() and SIGALRM
|
stacktest |
Demand-grown stacks and the guard page |
munmaptest / mprotecttest
|
Partial munmap/mprotect split the VMA |
vmtest |
The user/kernel address boundary actually holds |
dltest |
dlopen/dlsym against libdemo.so
|
fdleak |
Fds are force-closed when a process exits without closing them |
fsx |
dup, rename, and the per-process cwd |
vfsfill |
The per-directory child cap |
bigread / pstorm / netstorm
|
Pipeline and concurrent-socket stress reproducers |
fbtest / keyevtest
|
The fullscreen framebuffer and raw key-event paths |
#include "libc.h"
int main(int argc, char** argv) {
printf("hello from pid %ld\n", getpid());
return 0;
}Add it to USER_ELFS in kernel/Makefile, add a rule following the pattern of the others, and regenerate the initramfs. See Building.
- HOWTO-Write-a-userspace-program - the procedure, step by step
- Syscall-Reference - all 61 calls in detail
- Format-Reference - ELF64 and the entry stack
- Syscalls - the full interface
- Shell - how programs are invoked
- Process-Management - scheduling, signals and threads
- System V AMD64 ABI - ELF layout and the entry stack
- ELF-64 Object File Format - Sun Microsystems
NyxOS v6.4.363 · GPL v2 · GitHub · uselessalter on Discord · nyxos@inbox.lv
NyxOS Wiki
Getting started
Kernel
Storage & network
Graphics & apps
Userspace
HOWTO
- HOWTO-Add-a-system-call
- HOWTO-Write-a-userspace-program
- HOWTO-Add-a-shell-command
- HOWTO-Add-a-GUI-application
Reference
- Syscall-Reference
- Command-Reference
- Hardware-Reference
- Format-Reference
- Kernel-Data-Structures
- Source-Tree-Reference
Project