Userspace Runtime
Userspace programs are native ELF binaries under src/usr/, linked at 0x400000 by src/usr/user.ld, and staged into the root filesystem as /bin/<name>.elf.
crt0 and Program Startup
src/usr/libc/crt0.asm:
- Aligns the stack to 16 bytes and clears
rbp. - Runs
__init_arrayglobal constructors. - Calls
main()with no arguments (no argc/argv/envp or aux vector is delivered yet). - Calls
exit(retval); a hang loop catches returns.
It also exports __sigret (SYS_SIGRETURN trampoline), which libc installs as the signal restorer, and __thread_exit_shim: a thread whose entry function returns falls into it, and it moves the return value (rax) to the first argument, realigns the stack, and tail-jumps to pthread_exit — so a plain return cleans up exactly like an explicit exit.
libc Subset
src/usr/libc/ is a freestanding C/C++ subset:
- Syscalls: inline wrappers
syscall0..syscall6using the System V syscall ABI (number inrax, argsrdi/rsi/rdx/r10/r8/r9). Wrappers exist for the whole syscall surface — files, processes, memory, display, events, sound, shm, network, extended calls (futex/threads/epoll/memfd/mprotect). Time helpers:get_ticks/get_uptime/get_tsc_freq,sleep_ms(a realSYS_SLEEP_MSsleep), andsleep_until_ticksfor deadline-paced animation loops. - string:
strlen/strcpy/strcat/strncpy/strncat/strcmp/strncmp/strchr/strrchr/strstr/strtok(single global strtok state),memset/memcpy/memmovewith SSE2 16-byte aligned-store fast paths (forward and backward),memcmp,itoa(base 2-36, INT64_MIN-safe). - stdio:
printf/sprintf/snprintf/vsnprintfinto a 4 KiB stack buffer with raw fd writes — supports%s %d %i %u %o %x %X %p %c,-/0flags, widths,l/ll. No floats, noFILE*, no buffering. - stdlib: a region allocator over
SYS_MMAP— 64 KiB regions with magic-validated block lists, first-fit + split, coalescing free, fully-free regions unmapped, dedicated blocks above 32 KiB;callocwith overflow checks;reallocwith in-place growth;atoi; a simple LCGrand. - socket: POSIX-shaped wrappers over the network syscalls plus byte-order helpers.
- log: leveled
[sec.mmm] scope [mark] messagelogging to stdout. - config_utils:
key=valueconfig readers/writers used forSYSTEM.CFGand wallpaper settings, includingcfg_write_text_file_atomic(write-temp-then-rename; FAT32 rename refuses to overwrite, so the destination is unlinked first). - math: freestanding
sqrt/sin/cos/tan/fabs/fmod(plusfvariants) — bit-seeded Newton sqrt (machine epsilon across the full double range) and range-reduced Taylor trig, so apps never hand-roll math. - cxx.cpp: global
new/deleteover malloc, a__cxa_pure_virtualfault handler, and the static-object runtime glue (__cxa_guard_acquire/release/abort,__cxa_atexit/__cxa_finalize/__dso_handle— single-threaded fast-path guards; static destructors are not run at exit because process teardown reclaims everything). - vec.h / str.h: header-only C++ containers for apps.
Vec<T>is a growable array for trivially-copyable elements (realloc + memmove growth;push/pop/insert/remove/resize/reserve, checkedat(), move-only ownership) that bounds-checks indexed access in debug builds and returns false on allocation failure instead of aborting.Stringis a growable NUL-terminated buffer (assign/append/equals, always-validc_str()). They replace fixed-size row/entry arrays so lists are no longer silently truncated.
Threads (pthread)
src/usr/libc/pthread.h/pthread.c implement a pthread-shaped API over the thread syscalls. pthread_t is the thread's pid; functions return 0 on success or the raw negative errno on failure (no errno in userspace). Every synchronization object is a single 32-bit word with a valid all-zero initial state (PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, PTHREAD_RWLOCK_INITIALIZER; PTHREAD_ONCE_INIT = 0), and pthread_mutex_init/pthread_cond_init/pthread_rwlock_init zero the word (attr ignored). See Processes — Threads for the kernel side.
- Lifecycle:
pthread_createmmaps one 132 KiB anonymous RW mapping per thread — 128 KiB of usable stack with a one-pagePROT_NONEguard below it — writes the shim address at the stack top, and passes the whole range as the thread's recorded stack so the kernel unmaps it at exit;attris ignored.pthread_joinis awaitpidon the tid (-10when the target is not a joinable child); the status travels through the 32-bit exit channel, so onlyint32_t-ranged values round-trip.pthread_exitnever returns, and a plainreturnfrom the entry function reaches it through the shim. Unlikeexit(), it exits only the calling thread:pthread_exitfrom the main thread leaves sibling threads running.pthread_detachwrapsSYS_THREAD_DETACH;pthread_selfreads the TCB (see Thread-Local Storage). - Mutex: one 32-bit word (0 unlocked, 1 locked, 2 locked-with-waiters), so a zero-initialized object is a valid unlocked mutex.
pthread_mutex_lockfutex-waits on the word under contention;pthread_mutex_unlockof an unheld mutex returns-1and leaves the state unchanged;pthread_mutex_trylockreturns-16(EBUSY). - Condvar: a sequence counter.
pthread_cond_waitrecords the counter, releases the mutex, and futex-waits on the recorded value; spurious wakeups are possible, so the caller owns the predicate loop.pthread_cond_timedwaittakes a relative timeout in ms (0 = forever):-110(ETIMEDOUT) on expiry,-28propagated when the kernel's 16-slot timed-wait table is full and the wait never started.pthread_cond_signalbumps the counter and wakes one waiter;pthread_cond_broadcastbumps it and wakes every waiter — the futex has no requeue, so each re-acquires the mutex through its own contended path. - Once:
pthread_once_twalks 0 (not run) → 1 (initializer running) → 2 (done); concurrent callers futex-wait on the running state. - Rwlock: one 32-bit word — active reader count in bits 0-29, writer-held bit 30, writer-waiting bit 31; a waiting writer blocks new readers, so a reader stream cannot starve it.
pthread_rwlock_rdlockandpthread_rwlock_wrlockfutex-wait on the word when blocked;pthread_rwlock_tryrdlockreturns-16(EBUSY) when a writer holds or wants the lock,pthread_rwlock_trywrlockwhen readers are active or a writer holds it;pthread_rwlock_unlockof an idle rwlock returns-1, and unlocking a writer-held rwlock you do not own is undetectable — the one-word design carries no owner tracking. - The raw wrappers remain in
unistd.h(thread_createwith a caller-managed stack,futex,futex_wait_timeout) andsys/mman.h(mmap/munmap/mprotect,MAP_FAILED).
Limits: no attribute surface (fixed 128 KiB stacks), no pthread_cancel, no recursive or error-checking mutexes. TLS limits are listed with Thread-Local Storage.
Thread-Local Storage
__thread variables work — static TLS only. Apps are built -fno-pie and statically linked, so the compiler emits initial-exec/local-exec codegen (a load of fs:0 plus a link-time negative offset), and there is no dynamic TLS (__tls_get_addr, DTV) because there is no dynamic linking.
The kernel maps one region per thread (see Processes — Thread-Local Storage for the lifecycle):
- Layout: the static TLS block —
PT_TLSp_memszbytes placed so the whole padded block (memsz rounded up top_align, exactly as the linker'sTPOFFoffsets round it) ends at the thread pointer; variables sit at negativefsoffsets (an align-4 image's 4-byte block lands atfs_base - 4). The alignment gap, whenp_memszis not a multiple ofp_align, sits directly below the 16-byteUniTcb(include/uapi/tcb.h), zero, and the TCB sits above the block. The thread pointer (the fs base) is the TCB's address:fs:0holdsself, the TCB's own address, andfs:8holdstid, the thread's kernel pid. - Every thread has a TCB — an image with no
__threadvariables still gets a TCB-only mapping — sofs:0is valid in every user thread. - Threads start from the image's initializers: the
PT_TLStemplate is the.tdatafile image plus a zero.tbsstail. Only the file-backedp_fileszprefix ([p_vaddr, p_vaddr + p_filesz)) is ever read back — the exec path copies it out of the image buffer, thread creates walk the live user space — and the tail is zeroed, never read. Values written by one thread are never visible to another; a thread's values survive fork (the block is COW-shared with the child). - The user linker script (
src/usr/user.ld) places the template as.tdatathen.tbss, before.bss. ld and lld do not advance the location counter past.tbss, so the following.bssstarts at the same VA and overlaps it — the standard ELF TLS layout, harmless here because those overlapped.tbssVAs are never read. The NOBITS.bsstail is what extends the RWPT_LOAD'sp_memszover the.tbssVAs; a script placing.tbsslast leaves them outside everyPT_LOAD, and a.tdataending on a page boundary then fails every thread create with-EFAULT. AnASSERTpins the covered order. Do not let lld place the TLS sections itself as orphans — it once aliased.dataover.tbssat the same VAs. - A present but malformed
PT_TLS(p_filesz over p_memsz, template bytes outside the image, a vaddr leaving the user half, or a non-power-of-two p_align) refuses the exec with a serial line naming the field — absence degrades to the TCB-only install, malformation is a broken image and never silently strips the app's__threadstorage. pthread_selfloadsfs:0and returns the TCBtidfield — no syscall — valid in every thread by the always-TCB invariant.
Limits: no pthread_key_* (thread-specific data) and no per-thread errno — both deliberately absent until a consumer exists; the raw-negative-error convention covers callers today.
libgui
src/usr/libgui/ is an immediate-mode GUI toolkit — no retained widget tree, no internal event loop:
- Surfaces:
Surfacewraps a BGRA buffer with width/height/pitch and an optional display-buffer handle. - Primitives: pixels, rectangles, rounded rectangles (8x8 supersampled corner masks), circles, alpha blits,
gui_fill_rect_blend(alpha-aware fill for dimming scrims). - Pixel core (
gui_pixops): SSE2 row primitives for XRGB8888 —pix_copy_row,pix_fill_row, premultiplied src-over blend rows (out = src + dst*(255-src_a)/255,/255via thex+128+((x+128)>>8)>>8approximation, bit-exact with the scalargui_blend_premultipliedreference). Cached stores only (targets are read back by the next pass).gui_blit_alphauses the blend row on non-overlapping spans and falls back to the directional scalar loop for in-buffer overlap;gui_fill_rectuses the fill row. A randomized self-test (gui_pixops_self_test) diffs SIMD against scalar; the WM runs it every boot in debug builds and behindSYSTEM_FLAG_WM_PIXEL_SELFTESTotherwise. - App widgets: header/nav/list/toggle/slider/segmented-control/text-field/button helpers, popup menus, cards/badges/metrics, boxed-list group containers (
gui_draw_boxed_container/gui_draw_boxed_divider), and the icon glyph set (gui_draw_glyph,GuiGlyphKind) — the building blocks the apps compose. Glyphs are SVG-authored white silhouettes rasterized to.uoicassets under/usr/share/glyphs/;gui_draw_glyphcaches each kind/size and tints the silhouette to the requested foreground. Buttons have a pressed variant (gui_app_draw_button_ex) with centered labels; text fields reveal the text tail while focused so the caret stays visible past the field width. - Shared chrome: one drop-shadow recipe for every floating surface (
gui_draw_panel_shadow: popup menus, dialogs, shell overlays), one modal dialog (gui_dialog_layout+gui_draw_dialog: scrim from theoverlay_scrimpalette token, content-derived width, optional text field, Cancel/Confirm footer), one scrollbar (gui_draw_scrollbar+gui_scrollbar_w/gui_scrollbar_min_thumb), and one outline shared with windows: the crisp 1 px translucent hairline the WM paints around every window (gui_window_hairline_color), withgui_draw_window_frame(opaque body fill + hairline) for solid panels andgui_draw_window_ring(hairline only) for surfaces that paint their own body, like the dock's glass. Popup menus, dialogs, WM overlays and the dock all draw it, so every floating edge on the desktop is pixel-identical to a window frame. - Unified headerbar: windows have no titlebar strip — the top
gui_headerbar_h()rows of the client are the headerbar band, where the WM overlays the traffic lights at the left (gui_traffic_lights_w()is the cluster's right edge, the x apps start their own headerbar content at).gui_app_beginfills the canvas with the app background and returns the layout withheader_rectset to the band andbody_rectto the content region below; there is no divider under the band. Controls an app places in the band are published withgui_window_set_header_input(canvas-coordinate rects,WINDOW_HEADER_INPUT_MAX= 8) so the WM forwards clicks and hovers to them instead of treating the band as the window's drag zone; rects are re-published each frame and cleared to zero on window registration. See Window manager — Window Chrome and Unified Headerbar. - Design tokens: a fixed spacing scale (
gui_space_0_5/1/1_5/2/3/4= 4/8/12/16/24/32), radius scale (gui_radius_xs..xl= 4/6/10/12/12), and derived metrics (gui_app_row_h,gui_app_row_tall_h,gui_app_nav_h,gui_app_row_gap,gui_app_control_h, card-header/badge/dialog-button sizes) so every app and shell surface shares one rhythm. Windows, popups, dialogs and WM overlays share thexlradius (card headers sit at radius-1 inside the 1 px hairline); the dock rounds at the app-icon ratio instead — 25% of its height, the same proportion as the.uoicicon corners — so the panel reads as one shape with the icons it carries. - Theming: dark/light palettes synced from the shared registry, UI scaling helpers.
- Fonts (
.uof): atlas-based bitmap fonts with per-size loading, UTF-8 decoding, 26.6 fixed-point advances with GPOS kerning, subpixel glyph placement for UI fonts (mono stays on an integer grid), measurement and clipped text drawing, built-in bitmap fallback. See UOF Font Format. - Images:
.uoicicon,.uocucursor, and.uowpwallpaper loaders with entry selection by size/scale/variant, including a QOI decoder and scaled-cover blitting. - Window protocol: registration, resize handling, damage commits — see Window manager.
- System integration:
gui_set_window_titleupdates the window title live (the WM samples it each commit; it feeds app-identity matching, the window context menu and launchers);gui_notifyposts a toast through the registry (notify_generation) that the WM displays like its own notifications.gui_window_title_matchesis the shared app-identity test for registry window slots: a window matches its app on an exact title or adetail - Appsuffix, so apps that rename their window (Files, Latitude) stay discoverable by the dock, menubar, and WM launchers. - App menus: a focused app builds a
MenuModellocally (gui_menu_model_reset/add_menu/add_item/add_separator) and publishes it withgui_menu_publish(registry slot guarded by an odd/even seq protocol); the menubar renders it and dispatches activated item IDs through the window entry, which the app consumes withgui_menu_take_command. App item IDs must stay belowMENU_CMD_APP_MAX(0xE000): the menubar reserves that range and above for its own window-operation (0xE001+), system (MENU_CMD_RESERVED_BASE=0xF000+) and window-list (0x10000+) commands, andgui_menu_model_add_itemrejects IDs in it.gui_clipboard_copy/gui_clipboard_pastemove text through the registry clipboard (4095-byte cap, seq-stable reads), shared by all apps. - Frame waits:
gui_wait_frame/gui_poll_framewrap display event waits for FLIP_COMPLETE/VBLANK.
libapp
src/usr/libapp/ is the application runtime and widget kit that every graphical app is built on. It owns the window, the double-buffered canvas, the event loop, resize/theme/menu/focus plumbing, and damage publishing, so an app supplies only drawing and interaction logic.
- Runtime (
app.h): an app fills anAppConfig(title, size, min size,WIN_FLAG_*, pacing, callbacks) and callsapp_run. The runtime registers the window, allocates a private backbuffer canvas, and loops.on_draw(canvas)paints a whole frame into the canvas; the app marks what changed withapp_invalidate/app_invalidate_alland the runtime copies only those rects to the window and publishes damage.on_eventreceives input and lifecycle events (close is handled by the runtime as a clean exit; resize arrives after the window and canvas were re-synced; scroll arrives after invalidation).on_menugets menubar commands,on_menusrebuilds the model at startup and on focus gain (callapp_publish_menusto refresh otherwise),on_settingsfires after a registry settings-generation change and theme re-sync, andon_idleruns each iteration for periodic work. - Pacing:
frame_ticks == 0runs event-driven —on_drawonly when something was invalidated, sleepingidle_ms(default 16) when idle.frame_ticks > 0runs continuous —on_drawevery frame, paced withsleep_until_ticksand catch-up (app_set_frame_ticksadjusts the interval live). - Scrollable content:
app_set_content_sizegrows the window backing and canvas so the whole content is retained and the WM scrolls it (app_scroll_x/yread the offset); call it at the start of a frame, before drawing into the grown area. It also clamps the WM-owned scroll offset to the new content bounds in the same store-fence group as the content size, so the app's draw and the compositor agree on the offset on the very frame a folder shrinks while scrolled (otherwise sticky panels draw off-canvas for a frame). The grown canvas is pre-filled with the app background before the previous rows are preserved, so uncovered pixels never reach the backing as garbage when the viewport scrolls into them. - Manual mode: apps with an extra event source use
app_create/app_pump/app_commit/app_destroyinstead ofapp_runto interleave GUI plumbing with their own loop — the terminal does this around its epoll shell pipe and incremental renderer, withon_drawleft unset so it keeps drawing to the window directly. - Widgets (
widgets.h): stateful controls over the immediate-mode libgui primitives. Each owns its hover/press/focus/value state;*_eventreturnsWIDGET_*bitmasks (WIDGET_CHANGED= invalidate the rect) and*_drawrenders. Provided: buttons (release-to-apply with drag-away cancel, optional fire-on-down), toggle rows, sliders (live drag withgui_app_slider_track_recthit-testing and a release-to-persist click;widget_slider_draw_exoverrides the right-aligned readout with caller text, e.g. a position instead of a percent), segmented controls, text fields (caller-owned buffer, Enter/Escape, click-outside blur), popup menus (viewport-clamped placement), modal dialogs (confirm/cancel/dismiss with an optional field), help overlays, a scroll view (wheel + draggable vertical/horizontal thumbs, track page-jumps,widget_scroll_view_reveal_yfor keyboard nav), and a shared rect hit-test helper. - Standard menus and shortcuts (
app.h):app_menus_add_editpublishes a uniform Edit menu (Undo/Redo/Cut/Copy/Paste/Delete/Select All) with accelerators and per-entry enable flags under sharedAPP_CMD_*IDs;app_menus_add_helppublishes the standard Help menu (an optional tips entry plus the reserved About uniOS item);app_edit_shortcutmaps a Ctrl+letterEVT_KEY_DOWNto itsAPP_CMD_*. Apps with non-standard bindings (the terminal uses Ctrl+X for Copy because Ctrl+C is SIGINT) keep their own menu. - Settings (
app.h):app_setting_load_int/app_setting_save_intpersist per-app toggles in/data/APPS.CFG, kept separate fromSYSTEM.CFGso app writes never race WM/Preferences persistence of system settings.
Apps that bypass libapp still poll kernel events directly with poll_event/wait_event (SYS_GET_EVENT).
libmedia
src/usr/libmedia/ is a freestanding media codec library with no libc file I/O: callers load a whole file into memory and decode or probe from the buffer.
- API (
media_image.h):media_image_decode(data, size, out)dispatches on magic bytes,media_image_scaledoes bilinear resize,media_image_freereleases the result. Output is straight-alpha ARGB8888 (A<<24 | R<<16 | G<<8 | B). - Audio (
media_audio.h):media_audio_probe(data, size, file_size, out)validates a RIFF/WAV header window — PCM format code, 16-bit, 1-2 channels, 8000-48000 Hz — and returns the payload as absolute file offset and byte count (data_start/data_sizeinstruct media_audio_info). Parsing reads stay within the firstsizebytes of the buffer while the payload bounds check usesfile_size, so a streaming caller can probe a header window smaller than the file;outis untouched on failure. The shellplaycommand uses it. - Codecs: PNG (zlib/DEFLATE, all color types, tRNS, 1/2/4/8/16-bit), baseline JPEG (Huffman, fixed-point IDCT, bilinear JFIF chroma upsampling, BT.601 full-range conversion), GIF (bounded LZW, interlace, transparency as alpha-zero palette color), BMP (24/32-bit BI_RGB), QOI.
- Limits:
MEDIA_MAX_DIMENSION16384,MEDIA_MAX_PIXELS16 Mi pixels (64 MiB ARGB). Progressive JPEG and Adam7 PNG are rejected; allocations go through libcmalloc. - The image codecs are verified by a host-side harness against PIL references and hand-crafted malformed inputs; the WAV probe is verified by the
wavprobethreadtest scenario (see Testing).
Building Apps
Per meson.build:
crt0is assembled fromcrt0.asm(NASM elf64).libc,libgui,libapp, andlibmediaare static libraries built freestanding (-fno-exceptions -fno-rtti -mno-red-zone, no stack protector, no PIE).- The
app_sourcesmap lists each app directory; sources are globbed at setup time. Apps linkcrt0 + libc(shell,init,threadtest) orcrt0 + libc + libapp + libgui(everything else) with--whole-archivethroughld.lldanduser.ld.libmediais linked after--no-whole-archivefor apps that need it (currentlyfiles,imageviewer,shell, andthreadtest), so only referenced codec objects are pulled in. - Each app ELF is staged as
/bin/<name>.elfand packed intounifs.img.
Current apps: shell, init, wm, menubar, dock, files, terminal, latitude, preferences, clock, calendar, calculator, imageviewer, threadtest (a debug boot self-test; see Testing). Adding an app requires a map entry (see Building and running).