From be91cc1f1c40fb88a679ab9f47dda271f2fbddfc Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 28 Feb 2026 15:11:03 +0100 Subject: [PATCH] fix: remove redundant/test docs file --- docs/userspace.html | 1062 ------------------------------------------- 1 file changed, 1062 deletions(-) delete mode 100644 docs/userspace.html diff --git a/docs/userspace.html b/docs/userspace.html deleted file mode 100644 index 0b57510..0000000 --- a/docs/userspace.html +++ /dev/null @@ -1,1062 +0,0 @@ - - - - - -MontaukOS — Userspace Application Development - - - - - - - - - - - - -
- - -

Userspace Developer's Handbook

-

- MontaukOS is a hobbyist 64-bit operating system written in C++20. - Userspace programs run in Ring 3, are loaded as static ELF64 - binaries, and communicate with the kernel through the x86-64 - SYSCALL/SYSRET mechanism. -

-

- This document covers everything you need to write, build, and run - userspace applications — from a minimal "Hello World" to using - every available syscall — as well as how to extend the kernel - with new syscalls. -

- - -

Getting Started

- -

Project Structure

-
programs/
-├── GNUmakefile          # Build system (C++ programs)
-├── link.ld              # Linker script (base address 0x400000)
-├── include/
-│   ├── Api/
-│   │   └── Syscall.hpp      # Syscall numbers & data structures
-│   ├── montauk/
-│   │   ├── syscall.h        # Inline asm wrappers & typed API
-│   │   └── heap.h           # Userspace heap allocator (malloc/mfree/realloc)
-│   └── libc/                # Minimal C standard library headers
-│       ├── stdio.h          # printf, FILE I/O
-│       ├── stdlib.h         # malloc, free, atoi, exit
-│       ├── string.h         # memcpy, strlen, strcmp, etc.
-│       └── ...              # ctype.h, errno.h, assert.h, etc.
-├── src/
-│   ├── hello/
-│   │   └── main.cpp         # Hello world example
-│   ├── shell/
-│   │   └── main.cpp         # Interactive shell
-│   ├── man/
-│   │   └── main.cpp         # Manual page viewer
-│   └── doom/
-│       ├── Makefile             # DOOM build system
-│       ├── doomgeneric_montauk.c # MontaukOS platform layer
-│       └── libc.c               # C library implementation
-├── bin/                     # Compiled .elf binaries
-└── obj/                     # Intermediate object files
-

- Each subdirectory under src/ is treated as a separate - program. The build system discovers them automatically. -

- -

Toolchain

-

- Programs are compiled with a freestanding x86_64-elf - cross-compiler. The build system looks for one in - toolchain/local/bin/; if not found, it falls back to the - host g++. -

-

Key compiler flags:

- -
- SSE: The default C++ build disables SSE. Programs that need - floating-point support (e.g. C programs using float/double) - should compile with -msse -msse2 instead. The kernel enables SSE in - CR0/CR4 at boot, so SSE instructions are safe in userspace. -
- -

Build System

-

To build all programs:

-
cd programs/
-make          # or: make -j$(nproc)
-

- This produces one ELF binary per program in bin/ - (e.g. bin/hello.elf, bin/shell.elf). - The binaries are then typically packed into the ramdisk - (ramdisk.tar) and loaded by the kernel at boot. -

-

To add a new program, create src/<name>/main.cpp and run make.

- -

Linker Script

-

programs/link.ld

-

- All userspace programs are linked at virtual address - 0x400000. The linker script defines four standard - sections: -

- - - - - - -
SectionContentsAlignment
.textExecutable code— (base)
.rodataRead-only data, string literals4 KiB
.dataInitialized read/write data4 KiB
.bssZero-initialized data
-

- Debug frames (.eh_frame), notes, and comments are - discarded to keep binaries small. -

- - -

Program Anatomy

- -

Entry Point: _start

-

- Because there is no C runtime, every program must define - extern "C" void _start() as its entry point. There is no - main(), no argc/argv, and no - atexit handlers. -

-
- Auto-exit: If _start() returns normally, - the kernel's exit stub (mapped at 0x3FF000) automatically - calls SYS_EXIT(0). You can also call - montauk::exit(code) explicitly at any point. -
- -

Hello World

-

programs/src/hello/main.cpp

-
// Minimal MontaukOS userspace program
-#include <montauk/syscall.h>
-
-extern "C" void _start() {
-    montauk::print("Hello from userspace!\n");
-}
-

- Include <montauk/syscall.h> for the full typed API. - That header pulls in <Api/Syscall.hpp> for - constants and data structures. -

- - -

Syscall Architecture

- -

Calling Convention

-

- MontaukOS uses the hardware SYSCALL instruction on - x86-64. The kernel sets up the required MSRs - (IA32_STAR, IA32_LSTAR, - IA32_FMASK) during boot. -

- - - - - - - - - -
RegisterPurpose
RAXSyscall number (in) / return value (out)
RDIArgument 1
RSIArgument 2
RDXArgument 3
R10Argument 4 (not RCX — SYSCALL clobbers it)
R8Argument 5
R9Argument 6
-

- The SYSCALL instruction saves RIP in - RCX and RFLAGS in R11; both - registers are clobbered. The kernel masks IF on entry - (via IA32_FMASK) so interrupts are disabled during the - transition. -

-

- On the kernel side, SyscallEntry (assembly) saves - callee-saved registers and all arguments into a - SyscallFrame, then calls - SyscallDispatch(SyscallFrame*) which dispatches by - syscall number. -

- -

Raw Syscall Wrappers

-

programs/include/montauk/syscall.h

-

- Seven inline functions cover 0–6 argument syscalls: -

-
int64_t syscall0(uint64_t nr);
-int64_t syscall1(uint64_t nr, uint64_t a1);
-int64_t syscall2(uint64_t nr, uint64_t a1, uint64_t a2);
-int64_t syscall3(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3);
-int64_t syscall4(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4);
-int64_t syscall5(uint64_t nr, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5);
-int64_t syscall6(uint64_t nr, uint64_t a1, ..., uint64_t a6);
-

- Each wrapper uses inline assembly with explicit register moves and a - full clobber list to ensure correctness. You can use these directly if - you need a syscall not yet covered by the typed API. -

- -

Typed API Wrappers

-

- The montauk:: namespace provides type-safe wrappers around - the raw syscalls. These are the recommended interface for application - code. The full reference follows below. -

- - -

Syscall Reference

-

- MontaukOS v0.1.0 exposes 26 syscalls (numbers - 0–25), organized into 10 categories. -

- - -

Process Process Management

- -

SYS_EXIT (0) — Terminate the current process

-
[[noreturn]] void montauk::exit(int code = 0);
-

- Terminates the calling process. The exit code is currently unused by - the kernel but reserved for future wait/status support. - Control never returns to the caller. -

- -

SYS_YIELD (1) — Yield the CPU

-
void montauk::yield();
-

- Voluntarily yields the remainder of the current time slice, allowing - the scheduler to run another ready process immediately. -

- -

SYS_SLEEP_MS (2) — Sleep for a duration

-
void montauk::sleep_ms(uint64_t ms);
-

- Suspends the calling process for at least ms - milliseconds. Resolution depends on the APIC timer tick rate. -

- -

SYS_GETPID (3) — Get process ID

-
int montauk::getpid();
-

- Returns the PID of the calling process. Returns -1 if - called from the idle context (should not happen in userspace). -

- -

SYS_SPAWN (20) — Spawn a new process

-
int montauk::spawn(const char* path, const char* args = nullptr);
-

- Loads the ELF binary at path (a VFS path like - "0:/hello.elf") and spawns it as a new process. - Returns the new process's PID on success, or -1 on - failure (no free slots, invalid ELF, file not found). - The optional args string (up to 255 characters) is - copied into the new process and can be retrieved with - SYS_GETARGS. -

- -

SYS_WAITPID (23) — Wait for a process to exit

-
void montauk::waitpid(int pid);
-

- Blocks the calling process until the process identified by - pid has exited. Internally yields the CPU in a loop - until the target process is no longer alive. This is used by the - shell to wait for foreground processes (e.g. run - command) so that keyboard input and the terminal are not shared - simultaneously. -

- -

SYS_GETARGS (25) — Get process arguments

-
int montauk::getargs(char* buf, uint64_t maxLen);
-

- Copies the argument string passed to the current process (via - spawn()) into buf, writing at most - maxLen - 1 characters plus a null terminator. - Returns the number of characters copied, or -1 on error. - If no arguments were provided at spawn time, the buffer will be empty. -

- - -

Console Console I/O

- -

SYS_PRINT (4) — Print a string

-
void montauk::print(const char* text);
-

- Writes a null-terminated string to the kernel terminal. Supports - newlines (\n) and standard printable ASCII. -

- -

SYS_PUTCHAR (5) — Print a single character

-
void montauk::putchar(char c);
-

- Writes a single character to the kernel terminal. Useful for building - output character by character (e.g. printing integers). -

- - -

File I/O File I/O

- -

SYS_OPEN (6) — Open a file

-
int montauk::open(const char* path);
-

- Opens a file on the VFS. Paths use the format - "<device>:/<name>" (e.g. "0:/hello.elf" - for the ramdisk). Returns a non-negative handle on success, or a - negative value on error. -

- -

SYS_READ (7) — Read from a file

-
int montauk::read(int handle, uint8_t* buf, uint64_t offset, uint64_t size);
-

- Reads up to size bytes from the file at the given byte - offset into buf. Returns the number of bytes - actually read, or a negative value on error. Does not maintain an - implicit file position — the offset is explicit on every call. -

- -

SYS_GETSIZE (8) — Get file size

-
uint64_t montauk::getsize(int handle);
-

Returns the total size (in bytes) of the file associated with handle.

- -

SYS_CLOSE (9) — Close a file

-
void montauk::close(int handle);
-

Closes the file handle and releases associated kernel resources.

- -

SYS_READDIR (10) — List directory entries

-
int montauk::readdir(const char* path, const char** names, int max);
-

- Reads up to max directory entries from path. - Entry name pointers are written into the names array. - The kernel allocates a user-accessible page for the string data - automatically. Returns the number of entries read, or - ≤ 0 on error/empty directory. Maximum 64 entries per call. -

- - -

Memory Memory Management

- -

- Memory allocation has two layers: low-level syscalls - that map pages from the kernel, and a userspace heap - that provides malloc/mfree on top. -

- -

Userspace Heap (recommended)

-

programs/include/montauk/heap.h

-

- Include <montauk/heap.h> for a proper free-list - allocator that runs entirely in userspace. It calls - SYS_ALLOC internally to obtain pages and manages - sub-page allocations with a linked free list — adapted from - the kernel's own HeapAllocator. -

- -
void* montauk::malloc(uint64_t size);
-

- Allocates size bytes from the userspace free list. - Returns a 16-byte-aligned pointer, or nullptr on failure. - When the free list is exhausted, it transparently requests more pages - from the kernel via SYS_ALLOC (minimum 16 KiB growth). -

- -
void montauk::mfree(void* ptr);
-

- Returns the block to the userspace free list. - No syscall is made — the memory stays mapped - and is immediately available for future malloc calls. - Passing nullptr is a safe no-op. -

- -
void* montauk::realloc(void* ptr, uint64_t size);
-

- Resizes the allocation at ptr to size bytes. - Allocates a new block, copies the smaller of old/new sizes, and frees - the old block. If ptr is nullptr, behaves - like malloc. -

- -

Low-Level Page Syscalls

- -

SYS_ALLOC (11) — Map pages

-
void* montauk::alloc(uint64_t size);
-

- Maps size bytes of zeroed physical pages into the - process's address space (starting at 0x40000000). - The size is rounded up to the nearest page boundary (4 KiB). - Returns a pointer to the mapped region, or nullptr on - failure. This is the backing primitive for - montauk::malloc — most programs should use the heap - API instead of calling this directly. -

- -

SYS_FREE (12) — Unmap pages (no-op)

-
void montauk::free(void* ptr);
-

- Reserved for future page-level unmapping. Currently a no-op — - pages are reclaimed when the process exits. Use - montauk::mfree for heap allocations. -

- - -

Time Timekeeping

- -

SYS_GETTICKS (13) — Get tick count

-
uint64_t montauk::get_ticks();
-

- Returns the number of APIC timer ticks since boot. The tick rate - depends on the hardware and APIC timer calibration. -

- -

SYS_GETMILLISECONDS (14) — Get milliseconds since boot

-
uint64_t montauk::get_milliseconds();
-

- Returns wall-clock milliseconds elapsed since boot. Useful for - calculating uptime or measuring durations. -

- - -

System System Information

- -

SYS_GETINFO (15) — Get OS information

-
void montauk::get_info(Montauk::SysInfo* info);
-

- Fills in a SysInfo structure - with the OS name, version string, API version number, and maximum - process count. -

- - -

Keyboard Keyboard Input

- -

SYS_ISKEYAVAILABLE (16) — Check for pending key

-
bool montauk::is_key_available();
-

- Returns true if a key event is available in the PS/2 - keyboard buffer. Non-blocking. -

- -

SYS_GETKEY (17) — Get a key event

-
void montauk::getkey(Montauk::KeyEvent* out);
-

- Fills in a KeyEvent - structure with the next keyboard event (press or release), including - scancode, ASCII translation, and modifier state (Shift, Ctrl, Alt). -

- -

SYS_GETCHAR (18) — Read a character (blocking)

-
char montauk::getchar();
-

- Blocks until a printable character key-press is available, then - returns the ASCII character. This is the simplest way to read - interactive text input. -

- - -

Network Networking

- -

SYS_PING (19) — Send an ICMP echo request

-
int32_t montauk::ping(uint32_t ip, uint32_t timeoutMs = 3000);
-

- Sends an ICMP echo request to ip and waits up to - timeoutMs milliseconds for a reply. The IP address is in - little-endian byte order (e.g. 10.0.2.2 → - 0x0202000A). - Returns the round-trip time in milliseconds on success, or - -1 on timeout. -

- - -

Framebuffer Framebuffer Access

- -

SYS_FBINFO (21) — Get framebuffer information

-
void montauk::fb_info(Montauk::FbInfo* info);
-

- Fills in a FbInfo structure - with the framebuffer dimensions, pitch (bytes per scanline), and - bits per pixel. Call this before SYS_FBMAP to learn the - framebuffer geometry. -

- -

SYS_FBMAP (22) — Map framebuffer into process memory

-
void* montauk::fb_map();
-

- Maps the physical framebuffer into the calling process's address - space at 0x50000000 and returns the user virtual address. - The mapped region covers height × pitch bytes. Each - pixel is a 32-bit value in 0xAARRGGBB format (blue in - the low byte). Writing to this memory directly updates the screen. -

-
- Note: After mapping, the cursor overlay is not composited - automatically. Programs that use the framebuffer take full control of - screen output for the mapped region. -
- - -

Terminal Terminal

- -

SYS_TERMSIZE (24) — Get terminal dimensions

-
void montauk::termsize(int* cols, int* rows);
-

- Returns the current terminal dimensions (character grid) via the - two output pointers. Columns are packed in the low 32 bits and - rows in the high 32 bits of the raw return value; the typed - wrapper unpacks them for you. Either pointer may be - nullptr if you only need one dimension. -

- - -

Data Structures

- -

Montauk::SysInfo

-

programs/include/Api/Syscall.hpp

-
struct SysInfo {
-    char     osName[32];       // e.g. "MontaukOS"
-    char     osVersion[32];    // e.g. "0.1.0"
-    uint32_t apiVersion;       // Current: 2
-    uint32_t maxProcesses;     // Current: 16
-};
- -

Montauk::FbInfo

-
struct FbInfo {
-    uint64_t width;       // Framebuffer width in pixels
-    uint64_t height;      // Framebuffer height in pixels
-    uint64_t pitch;       // Bytes per scanline
-    uint64_t bpp;         // Bits per pixel (always 32)
-    uint64_t userAddr;    // Reserved (0 until mapped via SYS_FBMAP)
-};
- -

Montauk::KeyEvent

-
struct KeyEvent {
-    uint8_t scancode;   // Raw PS/2 scancode
-    char    ascii;      // Translated ASCII character (0 if non-printable)
-    bool    pressed;    // true = key down, false = key up
-    bool    shift;      // Shift modifier active
-    bool    ctrl;       // Ctrl modifier active
-    bool    alt;        // Alt modifier active
-};
- -

Montauk::SyscallFrame (kernel only)

-
struct SyscallFrame {
-    uint64_t r15, r14, r13, r12, rbp, rbx;   // callee-saved
-    uint64_t arg6, arg5, arg4, arg3, arg2, arg1;
-    uint64_t syscall_nr;
-    uint64_t user_rflags, user_rip, user_rsp;
-};
-

- This is the stack frame pushed by SyscallEntry.asm and - passed to SyscallDispatch. Userspace code never sees this - directly. -

- - -

Shell Application Walkthrough

-

programs/src/shell/main.cpp

-

- The built-in shell is the best example of a real MontaukOS application. - It demonstrates most of the available syscalls. -

- -

Initialization

-
extern "C" void _start() {
-    montauk::print("\n  MontaukOS Shell v0.1\n");
-    montauk::print("  Type 'help' for available commands.\n\n");
-
-    char line[256];
-    int pos = 0;
-    prompt();
-
-    while (true) {
-        char c = montauk::getchar();       // blocking read
-        // ... handle input, echo, backspace ...
-    }
-}
-

- The shell uses montauk::getchar() in a loop for blocking - character-by-character input, manually handling echo and backspace. -

- -

Shell Commands & Syscalls Used

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CommandDescriptionSyscalls Used
helpPrint available commandsSYS_PRINT
infoShow OS name, version, API versionSYS_GETINFO, SYS_PRINT, SYS_PUTCHAR
man <topic>Fullscreen manual page viewerSYS_OPEN, SYS_GETSIZE, SYS_READ, SYS_CLOSE, SYS_ALLOC, SYS_TERMSIZE, SYS_GETKEY, SYS_PRINT, SYS_PUTCHAR
lsList ramdisk filesSYS_READDIR, SYS_PRINT
cat <file>Display file contents in 512-byte chunksSYS_OPEN, SYS_GETSIZE, SYS_READ, SYS_CLOSE, SYS_PRINT
run <file>Spawn a new process and wait for it to exitSYS_SPAWN, SYS_WAITPID, SYS_PRINT
ping <ip>Send 4 ICMP echo requestsSYS_PING, SYS_SLEEP_MS, SYS_PRINT, SYS_PUTCHAR
uptimeShow uptime in minutes, seconds, msSYS_GETMILLISECONDS, SYS_PRINT, SYS_PUTCHAR
clearScroll past visible contentSYS_PUTCHAR
exitTerminate the shellSYS_PRINT, SYS_EXIT
- -

Pattern: Reading a File

-

The shell's cat implementation shows the standard file-reading pattern:

-
// 1. Build VFS path
-char path[128];
-// ... copy "0:/" + filename into path ...
-
-// 2. Open
-int handle = montauk::open(path);
-if (handle < 0) { /* error */ return; }
-
-// 3. Get size
-uint64_t size = montauk::getsize(handle);
-
-// 4. Read in chunks
-uint8_t buf[512];
-uint64_t offset = 0;
-while (offset < size) {
-    uint64_t chunk = size - offset;
-    if (chunk > sizeof(buf) - 1) chunk = sizeof(buf) - 1;
-    int bytesRead = montauk::read(handle, buf, offset, chunk);
-    if (bytesRead <= 0) break;
-    buf[bytesRead] = '\0';
-    montauk::print((const char*)buf);
-    offset += bytesRead;
-}
-
-// 5. Close
-montauk::close(handle);
- -

Pattern: IP Networking

-
// Parse "10.0.2.2" into uint32_t in little-endian order
-uint32_t ip;
-parse_ip("10.0.2.2", &ip);  // ip = 0x0202000A
-
-// Send 4 pings with 1-second intervals
-for (int i = 0; i < 4; i++) {
-    int32_t rtt = montauk::ping(ip, 3000);
-    if (rtt < 0) { /* timeout */ }
-    else { /* reply in rtt ms */ }
-    if (i < 3) montauk::sleep_ms(1000);
-}
- - -

Adding New Syscalls

-

- Adding a syscall requires changes in 3 files - (kernel-side) and 2 files (userspace-side). Follow - these steps: -

- -

Step 1: Assign a Syscall Number

-

- Add a new constant in both copies of the syscall - number definitions. The numbers must match exactly. -

-

kernel/src/Api/Syscall.hpp

-

programs/include/Api/Syscall.hpp

-
static constexpr uint64_t SYS_MYFUNC = 26;  // next available number
-
- Keep both files in sync. The kernel and userspace - headers define syscall numbers independently. If they disagree, the - wrong handler runs. -
- -

Step 2: Implement the Kernel Handler

-

kernel/src/Api/Syscall.cpp

-

Add a static function implementing your syscall's logic:

-
static int64_t Sys_MyFunc(uint64_t arg1, const char* arg2) {
-    // Your kernel-side implementation here.
-    // You have full access to kernel subsystems.
-    return 0;
-}
- -

Step 3: Add the Dispatch Case

-

kernel/src/Api/Syscall.cpp — SyscallDispatch()

-
case SYS_MYFUNC:
-    return (int64_t)Sys_MyFunc(frame->arg1, (const char*)frame->arg2);
-

- Arguments are accessed through frame->arg1 through - frame->arg6, corresponding to RDI, RSI, RDX, R10, R8, - R9 respectively. -

- -

Step 4: Add a Typed Userspace Wrapper

-

programs/include/montauk/syscall.h

-
inline int64_t my_func(uint64_t arg1, const char* arg2) {
-    return syscall2(Montauk::SYS_MYFUNC, arg1, (uint64_t)arg2);
-}
-

- Choose the appropriate syscallN variant based on the - number of arguments. Cast pointer types to uint64_t. -

- -

Step 5: Update the Log Message (optional)

-

kernel/src/Api/Syscall.cpp — InitializeSyscalls()

-

- Update the boot log to reflect the new syscall count: -

-
// Change "26 syscalls" to "27 syscalls"
-Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
-    << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 27 syscalls)";
- -

Complete Checklist

- - - - - - - -
#FileChange
1kernel/src/Api/Syscall.hppAdd SYS_MYFUNC constant (+ any new structs)
2kernel/src/Api/Syscall.cppAdd Sys_MyFunc() implementation + dispatch case
3programs/include/Api/Syscall.hppAdd matching SYS_MYFUNC constant (+ any new structs)
4programs/include/montauk/syscall.hAdd typed wrapper in montauk:: namespace
5(optional)Update syscall count in boot log
- - -

Process Memory Model

-

- Each process gets its own PML4 page table. The kernel half - (upper 256 entries) is shared; the lower half is per-process. -

- - - - - - - -
RegionVirtual AddressSizePurpose
Exit stub0x3FF0004 KiBAuto-exit trampoline (calls SYS_EXIT(0) if _start returns)
Program code0x400000+VariesELF .text, .rodata, .data, .bss
User heap0x40000000+Grows upPage pool (SYS_ALLOC); managed by userspace free-list heap
Framebuffer0x50000000+height × pitchMapped by SYS_FBMAP; direct pixel access (32-bit ARGB)
User stack0x7FFFFEF0000x7FFFFFF00016 KiB (4 pages)Grows down from 0x7FFFFFF000
-

- The ELF loader maps PT_LOAD segments with user-accessible page flags. - BSS is zero-initialized automatically (pages are allocated zeroed). -

- - -

Current Limitations

- - -
-

- MontaukOS Documentation — Copyright © 2025-2026 Daniel Hammer -

- -
- -