Userspace

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:

  1. Aligns the stack to 16 bytes and clears rbp.
  2. Runs __init_array global constructors.
  3. Calls main() with no arguments (no argc/argv/envp or aux vector is delivered yet).
  4. 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..syscall6 using the System V syscall ABI (number in rax, args rdi/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 real SYS_SLEEP_MS sleep), and sleep_until_ticks for deadline-paced animation loops.
  • string: strlen/strcpy/strcat/strncpy/strncat/strcmp/strncmp/strchr/strrchr/strstr/strtok (single global strtok state), memset/memcpy/memmove with SSE2 16-byte aligned-store fast paths (forward and backward), memcmp, itoa (base 2-36, INT64_MIN-safe).
  • stdio: printf/sprintf/snprintf/vsnprintf into a 4 KiB stack buffer with raw fd writes — supports %s %d %i %u %o %x %X %p %c, -/0 flags, widths, l/ll. No floats, no FILE*, 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; calloc with overflow checks; realloc with in-place growth; atoi; a simple LCG rand.
  • socket: POSIX-shaped wrappers over the network syscalls plus byte-order helpers.
  • log: leveled [sec.mmm] scope [mark] message logging to stdout.
  • config_utils: key=value config readers/writers used for SYSTEM.CFG and wallpaper settings, including cfg_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 (plus f variants) — 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/delete over malloc, a __cxa_pure_virtual fault 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, checked at(), move-only ownership) that bounds-checks indexed access in debug builds and returns false on allocation failure instead of aborting. String is a growable NUL-terminated buffer (assign/append/equals, always-valid c_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_create mmaps one 132 KiB anonymous RW mapping per thread — 128 KiB of usable stack with a one-page PROT_NONE guard 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; attr is ignored. pthread_join is a waitpid on the tid (-10 when the target is not a joinable child); the status travels through the 32-bit exit channel, so only int32_t-ranged values round-trip. pthread_exit never returns, and a plain return from the entry function reaches it through the shim. Unlike exit(), it exits only the calling thread: pthread_exit from the main thread leaves sibling threads running. pthread_detach wraps SYS_THREAD_DETACH; pthread_self reads 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_lock futex-waits on the word under contention; pthread_mutex_unlock of an unheld mutex returns -1 and leaves the state unchanged; pthread_mutex_trylock returns -16 (EBUSY).
  • Condvar: a sequence counter. pthread_cond_wait records 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_timedwait takes a relative timeout in ms (0 = forever): -110 (ETIMEDOUT) on expiry, -28 propagated when the kernel's 16-slot timed-wait table is full and the wait never started. pthread_cond_signal bumps the counter and wakes one waiter; pthread_cond_broadcast bumps it and wakes every waiter — the futex has no requeue, so each re-acquires the mutex through its own contended path.
  • Once: pthread_once_t walks 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_rdlock and pthread_rwlock_wrlock futex-wait on the word when blocked; pthread_rwlock_tryrdlock returns -16 (EBUSY) when a writer holds or wants the lock, pthread_rwlock_trywrlock when readers are active or a writer holds it; pthread_rwlock_unlock of 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_create with a caller-managed stack, futex, futex_wait_timeout) and sys/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_TLS p_memsz bytes placed so the whole padded block (memsz rounded up to p_align, exactly as the linker's TPOFF offsets round it) ends at the thread pointer; variables sit at negative fs offsets (an align-4 image's 4-byte block lands at fs_base - 4). The alignment gap, when p_memsz is not a multiple of p_align, sits directly below the 16-byte UniTcb (include/uapi/tcb.h), zero, and the TCB sits above the block. The thread pointer (the fs base) is the TCB's address: fs:0 holds self, the TCB's own address, and fs:8 holds tid, the thread's kernel pid.
  • Every thread has a TCB — an image with no __thread variables still gets a TCB-only mapping — so fs:0 is valid in every user thread.
  • Threads start from the image's initializers: the PT_TLS template is the .tdata file image plus a zero .tbss tail. Only the file-backed p_filesz prefix ([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 .tdata then .tbss, before .bss. ld and lld do not advance the location counter past .tbss, so the following .bss starts at the same VA and overlaps it — the standard ELF TLS layout, harmless here because those overlapped .tbss VAs are never read. The NOBITS .bss tail is what extends the RW PT_LOAD's p_memsz over the .tbss VAs; a script placing .tbss last leaves them outside every PT_LOAD, and a .tdata ending on a page boundary then fails every thread create with -EFAULT. An ASSERT pins the covered order. Do not let lld place the TLS sections itself as orphans — it once aliased .data over .tbss at 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 __thread storage.
  • pthread_self loads fs:0 and returns the TCB tid field — 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: Surface wraps 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, /255 via the x+128+((x+128)>>8)>>8 approximation, bit-exact with the scalar gui_blend_premultiplied reference). Cached stores only (targets are read back by the next pass). gui_blit_alpha uses the blend row on non-overlapping spans and falls back to the directional scalar loop for in-buffer overlap; gui_fill_rect uses 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 behind SYSTEM_FLAG_WM_PIXEL_SELFTEST otherwise.
  • 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 .uoic assets under /usr/share/glyphs/; gui_draw_glyph caches 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 the overlay_scrim palette 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), with gui_draw_window_frame (opaque body fill + hairline) for solid panels and gui_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_begin fills the canvas with the app background and returns the layout with header_rect set to the band and body_rect to the content region below; there is no divider under the band. Controls an app places in the band are published with gui_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 the xl radius (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 .uoic icon 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: .uoic icon, .uocu cursor, and .uowp wallpaper 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_title updates the window title live (the WM samples it each commit; it feeds app-identity matching, the window context menu and launchers); gui_notify posts a toast through the registry (notify_generation) that the WM displays like its own notifications. gui_window_title_matches is the shared app-identity test for registry window slots: a window matches its app on an exact title or a detail - App suffix, so apps that rename their window (Files, Latitude) stay discoverable by the dock, menubar, and WM launchers.
  • App menus: a focused app builds a MenuModel locally (gui_menu_model_reset/add_menu/add_item/add_separator) and publishes it with gui_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 with gui_menu_take_command. App item IDs must stay below MENU_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, and gui_menu_model_add_item rejects IDs in it. gui_clipboard_copy/gui_clipboard_paste move text through the registry clipboard (4095-byte cap, seq-stable reads), shared by all apps.
  • Frame waits: gui_wait_frame/gui_poll_frame wrap 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 an AppConfig (title, size, min size, WIN_FLAG_*, pacing, callbacks) and calls app_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 with app_invalidate/app_invalidate_all and the runtime copies only those rects to the window and publishes damage. on_event receives 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_menu gets menubar commands, on_menus rebuilds the model at startup and on focus gain (call app_publish_menus to refresh otherwise), on_settings fires after a registry settings-generation change and theme re-sync, and on_idle runs each iteration for periodic work.
  • Pacing: frame_ticks == 0 runs event-driven — on_draw only when something was invalidated, sleeping idle_ms (default 16) when idle. frame_ticks > 0 runs continuous — on_draw every frame, paced with sleep_until_ticks and catch-up (app_set_frame_ticks adjusts the interval live).
  • Scrollable content: app_set_content_size grows the window backing and canvas so the whole content is retained and the WM scrolls it (app_scroll_x/y read 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_destroy instead of app_run to interleave GUI plumbing with their own loop — the terminal does this around its epoll shell pipe and incremental renderer, with on_draw left 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; *_event returns WIDGET_* bitmasks (WIDGET_CHANGED = invalidate the rect) and *_draw renders. Provided: buttons (release-to-apply with drag-away cancel, optional fire-on-down), toggle rows, sliders (live drag with gui_app_slider_track_rect hit-testing and a release-to-persist click; widget_slider_draw_ex overrides 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_y for keyboard nav), and a shared rect hit-test helper.
  • Standard menus and shortcuts (app.h): app_menus_add_edit publishes a uniform Edit menu (Undo/Redo/Cut/Copy/Paste/Delete/Select All) with accelerators and per-entry enable flags under shared APP_CMD_* IDs; app_menus_add_help publishes the standard Help menu (an optional tips entry plus the reserved About uniOS item); app_edit_shortcut maps a Ctrl+letter EVT_KEY_DOWN to its APP_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_int persist per-app toggles in /data/APPS.CFG, kept separate from SYSTEM.CFG so 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_scale does bilinear resize, media_image_free releases 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_size in struct media_audio_info). Parsing reads stay within the first size bytes of the buffer while the payload bounds check uses file_size, so a streaming caller can probe a header window smaller than the file; out is untouched on failure. The shell play command 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_DIMENSION 16384, MEDIA_MAX_PIXELS 16 Mi pixels (64 MiB ARGB). Progressive JPEG and Adam7 PNG are rejected; allocations go through libc malloc.
  • 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 wavprobe threadtest scenario (see Testing).

Building Apps

Per meson.build:

  • crt0 is assembled from crt0.asm (NASM elf64).
  • libc, libgui, libapp, and libmedia are static libraries built freestanding (-fno-exceptions -fno-rtti -mno-red-zone, no stack protector, no PIE).
  • The app_sources map lists each app directory; sources are globbed at setup time. Apps link crt0 + libc (shell, init, threadtest) or crt0 + libc + libapp + libgui (everything else) with --whole-archive through ld.lld and user.ld. libmedia is linked after --no-whole-archive for apps that need it (currently files, imageviewer, shell, and threadtest), so only referenced codec objects are pulled in.
  • Each app ELF is staged as /bin/<name>.elf and packed into unifs.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).