Syscall ABI
Makar uses the Linux i386 syscall calling convention:
int 0x80
EAX = syscall number
EBX = arg0
ECX = arg1
EDX = arg2
ESI = arg3
EDI = arg4
EAX = return value
The ABI is shared verbatim between kernel and userspace (Linux-uapi style). The authoritative definitions live in:
src/kernel/include/makar_syscalls.h # the SYS_* numbers
src/kernel/include/makar_abi.h # clockids, struct timeval/timespec/stat/dirent,
# tty_cell_t, open/fcntl flags, S_IF* + DT_* bits
src/kernel/include/makar_signals.h # signal numbers
src/kernel/include/makar_keys.h # keycodes / sentinels
Both kernel/syscall.h (kernel-internal dispatch) and src/userspace/syscall.h
(the ring-3 inline wrappers) include those headers — they never re-declare
the numbers, so the two sides cannot drift. Userspace objects use generated
dependency files, so editing the ABI headers rebuilds the dependent apps.
Return Conventions
Makar mostly follows the Unix pattern:
- non-negative return values indicate success
-1or negative errno-style values indicate failure depending on the syscall- userspace libc wrappers are gradually being moved toward setting
errno
Some older Makar-specific syscalls still return a simple 0/1 or -1
without a detailed errno. Check the wrapper before assuming Linux parity.
Core Linux-Compatible Syscalls
| Number | Name | Arguments | Status |
|---|---|---|---|
| 1 | SYS_EXIT |
status |
terminates current task |
| 2 | SYS_FORK |
none | COW fork; parent gets child pid, child gets 0 |
| 3 | SYS_READ |
fd, buf, len |
fd-backed read |
| 4 | SYS_WRITE |
fd, buf, len |
fd-backed write |
| 5 | SYS_OPEN |
path, flags, mode |
mode ignored; read-only disk files are lazy (page-cached); missing file w/o O_CREAT → -ENOENT (musl ld.so walks its search path on this) |
| 6 | SYS_CLOSE |
fd |
closes and flushes |
| 10 | SYS_UNLINK |
path |
delete file |
| 11 | SYS_EXECVE |
path, argv, envp |
envp ignored |
| 12 | SYS_CHDIR |
path |
per-task cwd |
| 19 | SYS_LSEEK |
fd, offset, whence |
SEEK_SET/CUR/END |
| 20 | SYS_GETPID |
none | current task pid |
| 37 | SYS_KILL |
pid, signo |
signal delivery (signo==0 = POSIX existence test: 0 if alive, −1 if not) |
| 38 | SYS_RENAME |
old, new |
VFS rename |
| 39 | SYS_MKDIR |
path, mode |
mode ignored |
| 40 | SYS_RMDIR |
path |
remove empty directory |
| 41 | SYS_DUP |
oldfd |
duplicate to lowest free fd |
| 42 | SYS_PIPE |
int pipefd[2] |
creates read/write fds |
| 45 | SYS_BRK |
addr |
query/grow heap break (demand-paged) |
| 48 | SYS_SIGNAL |
signo, handler |
simple signal handler install |
| 54 | SYS_IOCTL |
fd, request, ... |
compatibility stub, returns -ENOTTY |
| 55 | SYS_FCNTL |
fd, cmd, arg |
F_GETFL, F_SETFL |
| 63 | SYS_DUP2 |
oldfd, newfd |
duplicate onto requested fd |
| 64 | SYS_GETPPID |
none | parent pid |
| 78 | SYS_GETTIMEOFDAY |
timeval *, tz |
tz ignored |
| 91 | SYS_MUNMAP |
addr, len |
unmap a range and release each frame (refcount-decrement; shared cache frames survive while others map them) |
| 106 | SYS_STAT |
path, stat * |
limited Linux-shaped stat |
| 108 | SYS_FSTAT |
fd, stat * |
limited Linux-shaped stat |
| 114 | SYS_WAIT4 |
pid, status *, options, rusage |
supports WNOHANG; rusage ignored |
| 119 | SYS_SIGRETURN |
internal | signal trampoline return |
| 141 | SYS_READDIR |
path, index, dirent * |
indexed Makar syscall, libc wraps it |
| 146 | SYS_WRITEV |
fd, iovec *, iovcnt |
gather-write; shares the SYS_WRITE dispatch. musl’s buffered stdio writes through this |
| 158 | SYS_YIELD |
none | scheduler yield |
| 125 | SYS_MPROTECT |
addr, len, prot |
rewrites PTE R/W/USER over the range (ld.so RELRO); no NX on i386 |
| 175 | SYS_RT_SIGPROCMASK |
Linux args | startup compatibility stub |
| 192 | SYS_MMAP2 |
addr, len, prot, flags, fd, pgoff |
anon non-fixed = demand-paged reserve; a read-only file map shares page-cache frames (pagecache_acquire — one libc.so in RAM for all mappers); writable/tmpfs/anon-fixed take a private frame (bss tail zeroed) — backs musl ld.so’s library maps |
| 240 | SYS_FUTEX |
Linux args | single-threaded compatibility stub |
| 243 | SYS_SET_THREAD_AREA |
user_desc * |
one-slot i386 TLS |
| 252 | SYS_EXIT_GROUP |
status |
same as exit |
| 258 | SYS_SET_TID_ADDRESS |
int * |
returns pid |
| 265 | SYS_CLOCK_GETTIME |
clockid, timespec * |
realtime and monotonic |
| 274 | SYS_VIDEO_CAPS |
– | returns VIDEO_CAP_* of the active display driver |
| 275 | SYS_HWCURSOR_DEFINE |
argb, (w<<16)|h, (hx<<16)|hy |
upload a HW cursor sprite |
| 276 | SYS_HWCURSOR_MOVE |
x, y |
move the HW cursor overlay |
| 277 | SYS_HWCURSOR_SHOW |
on |
show/hide the HW cursor |
| 278 | SYS_VIDEO_NAME |
buf, cap |
copy the active video backend’s name into buf |
| 279 | SYS_MAKX_SERVER |
none | display-server (gui.elf) pid, 0 if no GUI session — the $DISPLAY discovery handle for makx clients |
Makar Extension Ranges
Makar extensions provide functionality that would normally be handled by termios, ioctl, framebuffer drivers, devfs, procfs, or shell helpers on a larger Unix system.
The exact list is in src/kernel/include/makar_syscalls.h; the groups below
explain the design intent.
Terminal and Framebuffer
These support text-mode and framebuffer apps without a termios layer:
- put a character/cell at a position
- move the cursor
- clear the terminal
- query terminal size
- query framebuffer geometry
- draw framebuffer lines
- present a full-frame back buffer (
SYS_FB_PRESENT, 257) or just a sub-rect of it (SYS_FB_PRESENT_RECT, 269 — lets the compositor refresh the cursor box without re-pushing the whole frame) - set caret style
- clear using shell/default colors
Display-mutating syscalls are focus-gated where appropriate so background VTs update backing buffers without scribbling on the visible framebuffer.
Shared Pixel Surfaces
The only shared-memory primitive in the system (fork is copy-on-write and
SYS_MMAP2 is private-anon only). A surface is a kernel-owned run of physical
frames that can be mapped into more than one task at the same physical
location, so a window manager and a forked graphical child (e.g. doom.elf
windowed) can share a frame buffer: the WM composites what the child renders.
See kernel/surface.h for the lifetime model.
| Number | Name | Arguments | Returns |
|---|---|---|---|
| 259 | SYS_SURFACE_CREATE |
w, h |
surface id (>=0) holding a creator ref, or -1 |
| 262 | SYS_SURFACE_MAP |
id |
base user address, or 0/NULL on failure |
| 263 | SYS_SURFACE_INFO |
id |
(w << 16) | h, or -1 for a bad id |
| 264 | SYS_SURFACE_DESTROY |
id |
drop the creator ref; 0 ok, -1 error |
A surface stays alive while its creator reference is held or any task still has it mapped; its frames are reclaimed once neither holds. On task teardown the kernel unmaps the dying task’s surface pages before its page directory is torn down, so the shared frames are never double-freed.
IPC (synchronous message passing)
MINIX-style rendezvous: endpoints are task pids, messages are fixed 32-byte
ipc_msg_t (a type opcode + 6 data words), transfer is synchronous (a send
blocks until the destination is in a matching receive). ebx = endpoint pid (or
IPC_ANY = -1 for receive), ecx = ipc_msg_t *. See kernel/ipc.h. This is
the control channel for the makx display server/client split (pixels go over a
shared surface); docs/gui.md describes the makx protocol layered on top.
| Number | Name | Arguments | Returns |
|---|---|---|---|
| 249 | SYS_IPC_SEND |
dst, msg |
0 ok, -ESRCH/-EINVAL/-EFAULT |
| 250 | SYS_IPC_RECV |
from, out |
0 ok (blocks until a sender), or -err |
| 251 | SYS_IPC_SENDREC |
dst, msg |
atomic send-then-await-reply (RPC) |
| 267 | SYS_IPC_NBRECV |
from, out |
0 if a message was queued, else -EAGAIN (-11); never blocks |
SYS_IPC_NBRECV lets a server (the makx display server) drain queued client
requests in the same loop it polls hardware input, instead of parking in a
blocking ipc_recv.
Keyboard
Keyboard syscalls expose:
- blocking key reads
- raw keyboard mode
- focus-aware routing
Automated keyboard tests do not use a syscall. The kernel test driver injects
keycodes directly into the live keyboard path when kbtest is on the command
line.
Filesystem Convenience
Makar provides shortcut syscalls for:
- write an entire file
- list a directory into a text buffer
- delete files/directories
- rename/move paths
- disk information
- PCI information
- statfs-like rootfs usage
These are not POSIX ABI surfaces; they exist for small built-in tools and apps.
Networking
Networking currently uses a monolithic lwIP integration over the kernel
netdev layer. The public userspace surface is intentionally small:
| Syscall | Number | Purpose |
|---|---|---|
SYS_NET_INFO |
253 | render active Ethernet/lwIP state into a caller buffer |
SYS_NET_CTL |
254 | run a network control command |
SYS_WGET |
255 | fetch an http:// URL (EBX) and write the body to a VFS path (ECX); returns bytes saved, or negative on error (-(status) for a non-2xx reply) |
SYS_NET_CTL accepts these command values from src/userspace/syscall.h:
| Command | Value | Behavior |
|---|---|---|
NET_CTL_DHCP_RELEASE |
1 | release/stop DHCP and restore static QEMU slirp fallback addressing |
NET_CTL_DHCP_RENEW |
2 | restart DHCP, then fall back static if no lease arrives quickly |
NET_CTL_DNS_FLUSH |
3 | clear lwIP DNS cache and pending resolver requests |
The user-facing tool is /apps/maknetcfg.elf; see
Networking.
Session and Admin
Admin/session helpers include:
- reboot
- shutdown
- display mode changes
- foreground/background color selection
- mount/umount/mkfs/eject/install helpers
- scheduler quantum controls
- serial verbose toggle
- hostname and username queries
There is no permission model yet, so privilege checks are structural rather than user/credential based.
| Syscall | Args | Returns |
|---|---|---|
260 SYS_LOGOUT |
— | end the root login session; 0 ok, -1 |
266 SYS_LOGIN |
user, pass |
shadow_verify + set session user; 0 ok, -1 bad creds |
270 SYS_PASSWD |
old, new |
change the current session user’s password (shadow_verify old + shadow_set_password new); 0 ok, -2 wrong current password, -1 otherwise (bad args / read-only live rootfs) |
271 SYS_CAD_PENDING |
— | test-and-clear the Ctrl-Alt-Del flag; 1 if it was pressed. The GUI server polls it each frame to open its power menu |
272 SYS_PTY_WINSIZE |
fd, (cols<<16)\|rows |
publish a pty window size onto a pipe fd so a piped child’s SYS_TERM_SIZE reports it (GUI terminal → shell); 0 ok, -1 if not a pipe |
273 SYS_INSTALL_EXEC |
cmd, ptr |
stepped headless installer for the GUI front-end (mxinstall.elf). cmd: 0 begin(install_params_t*), 1 step(install_progress_t*, copies one file), 2 finish(install_params_t*), 3 list-drives(install_drive_t[4]). Returns the engine result (see kernel/installer.h). Runs preemptibly so the compositor stays live during the copy |
285 SYS_SETTIME |
(year<<16)\|(mon<<8)\|day, (hour<<16)\|(min<<8)\|sec |
set the wall clock by writing the CMOS RTC (rtc_write). Fields are range-checked (year 1970–2099, mon 1–12, day 1–31, hour ≤ 23, min/sec ≤ 59); 0 ok, -1 on any bad field (the RTC is left untouched). Because SYS_GETTIMEOFDAY/SYS_CLOCK_GETTIME read the RTC live, the new time takes effect immediately. Backs Settings → Date & Time |
286 SYS_NET_CONFIG |
const net_cfg_t * |
apply a network configuration (net_lwip_config): dhcp != 0 (re)starts DHCP and ignores the address fields; otherwise the four dotted-quad octet arrays set a static IPv4 address + netmask + gateway + DNS server on the live lwIP netif. 0 ok, -1 if the stack isn’t ready or the pointer is null. net_cfg_t is defined in makar_abi.h. Backs Settings → Network |
SYS_LOGIN backs the GUI graphical login (gui.elf’s do_login); SYS_PASSWD
backs its change-password dialog (the power menu’s “Change password…”). Both
are documented in docs/gui.md. SYS_INSTALL_EXEC backs the graphical
installer mxinstall.elf; the text installer (install builtin →
SYS_INSTALL) and the GUI share one execution engine (see docs/gui.md).
SYS_SETTIME and SYS_NET_CONFIG back the centralised Settings app
(mxsettings.elf) — its Date & Time and Network panels (see docs/gui.md).
Virtual Terminals and makmux
VT/app-tab syscalls support the userspace multiplexer:
| Syscall | Purpose |
|---|---|
SYS_VT_ENTER |
bind a task to a new VT slot |
SYS_VT_CLOSE |
close/free a VT owner pid |
SYS_VT_OPEN_REQUEST |
request another shell VT (the Alt+T route) |
SYS_VT_STATE |
return live VT state: (focused_slot << 16) \| live_mask |
SYS_VT_OPEN_APP |
queue or switch to a named app tab |
SYS_VT_TAKE_APP |
makmux drains one queued app path |
SYS_VT_SETNAME |
name the current VT/app tab |
SYS_VT_GETNAME |
read a VT/app tab name |
SYS_VT_TAKE_APP is currently number 248. It was moved off 243 so Linux i386
set_thread_area could use its standard number.
There are nine user-visible VT slots (kernel slots 0–8) plus a hidden root
console (VTTY_ROOT_SLOT). They are exposed to userspace as Linux-style
character nodes under /dev:
| Path | Slot | Notes |
|---|---|---|
/dev/tty0 |
VTTY_ROOT_SLOT |
the root console (mak.sh0), hidden from the tab strip |
/dev/tty1 … /dev/tty9 |
0 … 8 | the nine makmux VT slots (slot == ttyN − 1) |
/dev/null |
— | the bit bucket: writes discarded, reads EOF |
/dev/mouse |
— | read-only text snapshot of the pointer input chain |
These nodes are always present (independent of makmux). They are character
sinks, not block devices: reads return EOF, and a write streams into that
slot’s backing grid (vtty_write), painting the framebuffer if the VT is
focused — e.g. echo hi > /dev/tty2. No new syscall: writes ride the existing
SYS_OPEN/SYS_WRITE block-device fd path through devfs. makmux is tmux-style
— it opens one shell by default and grows more on the SYS_VT_OPEN_REQUEST
(Alt+T) route, up to nine.
TLS Details
SYS_SET_THREAD_AREA accepts a Linux-style i386 struct user_desc pointer.
Makar supports one TLS slot:
- GDT index: 6
- selector:
0x33 - writeback entry number: 6
- segment register:
%gs
The syscall programs the GDT slot, records the TLS descriptor on the current
task, and loads %gs. ISR and IRQ stubs intentionally leave %gs untouched.
The scheduler restores TLS state for TLS-active tasks.
mmap Details
SYS_MMAP2 covers the cases the dynamic linker and libc allocators need:
- anonymous, non-fixed (malloc’s big allocations): demand-paged — only reserves the range (advances a per-task bump pointer); zero-filled frames are mapped on first touch by the page-fault handler, the same lazy model Linux uses. A large mapping costs O(1) in the call rather than mapping every page.
- file-backed, read-only: pages are shared straight from the page cache
(
pagecache_acquire), so a file mapped by many processes — libc.so above all — is one physical copy in RAM. This is the Linux page-cache model and what makes dynamic linking a memory win rather than a per-process cost. - file-backed, writable / tmpfs-backed, and
MAP_FIXED: eager — a private frame per page (file region read in, bss tail zeroed), mapped at the caller’s exact address forMAP_FIXED. musl’sld.sousesMAP_FIXEDto overlay each library segment onto its whole-library reserve. - returns
MAP_FAILED((void *)-1) on unsupported requests
SYS_BRK is likewise lazy: growing the break only advances user_brk; pages in
[user_brk_base, user_brk) fault in zeroed on first touch. (Query/shrink-to
forms behave as before; the break only ever grows.)
SYS_MUNMAP clears each PTE and releases its frame (vmm_unmap_and_free, a
refcount decrement — a shared page-cache frame survives while other processes
still map it). It does not currently recycle virtual addresses: because the
mmap window is a grow-only bump arena, touching a munmap’d address below
mmap_next simply re-faults a fresh zero page rather than SIGSEGV-ing —
acceptable for the current bring-up (no address reuse).
Compatibility Stubs
These exist for hosted libc startup, especially static musl:
| Syscall | Behavior |
|---|---|
ioctl |
returns -ENOTTY |
rt_sigprocmask |
returns success |
futex |
returns success under the current single-threaded assumption |
set_tid_address |
returns pid |
exit_group |
exits current process |
Treat them as bring-up compatibility, not complete Linux behavior.