1063 lines
43 KiB
HTML
1063 lines
43 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MontaukOS — Userspace Application Development</title>
|
|
<style>
|
|
:root {
|
|
--bg: #ffffff;
|
|
--surface: #f6f8fa;
|
|
--border: #d0d7de;
|
|
--fg: #1f2328;
|
|
--fg-muted: #656d76;
|
|
--accent: #0969da;
|
|
--accent2: #8250df;
|
|
--green: #1a7f37;
|
|
--orange: #9a6700;
|
|
--red: #cf222e;
|
|
--code-bg: #f6f8fa;
|
|
--sidebar-w: 280px;
|
|
}
|
|
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html { scroll-behavior: smooth; scroll-padding-top: 2rem; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--fg);
|
|
line-height: 1.6;
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* ---- Sidebar ---- */
|
|
nav.sidebar {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: var(--sidebar-w);
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
background: var(--surface);
|
|
border-right: 1px solid var(--border);
|
|
padding: 1.5rem 1rem;
|
|
z-index: 10;
|
|
}
|
|
nav.sidebar h2 {
|
|
font-size: 1rem;
|
|
color: var(--accent);
|
|
margin-bottom: .5rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: .08em;
|
|
}
|
|
nav.sidebar ul { list-style: none; }
|
|
nav.sidebar > ul > li { margin-bottom: .15rem; }
|
|
nav.sidebar a {
|
|
display: block;
|
|
color: var(--fg-muted);
|
|
text-decoration: none;
|
|
padding: .25rem .5rem;
|
|
border-radius: 4px;
|
|
font-size: .9rem;
|
|
transition: background .15s, color .15s;
|
|
}
|
|
nav.sidebar a:hover,
|
|
nav.sidebar a:focus { background: var(--border); color: var(--fg); }
|
|
nav.sidebar ul ul { padding-left: .85rem; }
|
|
nav.sidebar ul ul a { font-size: .82rem; }
|
|
|
|
/* ---- Main content ---- */
|
|
main {
|
|
margin-left: var(--sidebar-w);
|
|
max-width: 54rem;
|
|
padding: 2.5rem 3rem 6rem;
|
|
width: 100%;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.2rem;
|
|
color: var(--accent);
|
|
border-bottom: 2px solid var(--border);
|
|
padding-bottom: .5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
h2 {
|
|
font-size: 1.6rem;
|
|
color: var(--accent2);
|
|
margin-top: 3rem;
|
|
margin-bottom: .75rem;
|
|
border-bottom: 1px solid var(--border);
|
|
padding-bottom: .35rem;
|
|
}
|
|
h3 {
|
|
font-size: 1.2rem;
|
|
color: var(--fg);
|
|
margin-top: 2rem;
|
|
margin-bottom: .5rem;
|
|
}
|
|
h4 {
|
|
font-size: 1rem;
|
|
color: var(--fg-muted);
|
|
margin-top: 1.5rem;
|
|
margin-bottom: .35rem;
|
|
}
|
|
|
|
p, li { margin-bottom: .6rem; }
|
|
ul, ol { padding-left: 1.5rem; }
|
|
|
|
a { color: var(--accent); }
|
|
|
|
code {
|
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
font-size: .88em;
|
|
background: var(--code-bg);
|
|
padding: .15em .35em;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
pre {
|
|
background: var(--code-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 1rem 1.25rem;
|
|
overflow-x: auto;
|
|
margin: .75rem 0 1.25rem;
|
|
line-height: 1.45;
|
|
}
|
|
pre code {
|
|
background: none;
|
|
padding: 0;
|
|
font-size: .85rem;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: .75rem 0 1.25rem;
|
|
font-size: .9rem;
|
|
}
|
|
th, td {
|
|
text-align: left;
|
|
padding: .55rem .75rem;
|
|
border: 1px solid var(--border);
|
|
}
|
|
th {
|
|
background: var(--surface);
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
}
|
|
tr:nth-child(even) td { background: rgba(0,0,0,.02); }
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
font-size: .72rem;
|
|
font-weight: 600;
|
|
padding: .15em .55em;
|
|
border-radius: 3px;
|
|
vertical-align: middle;
|
|
text-transform: uppercase;
|
|
letter-spacing: .04em;
|
|
}
|
|
.badge-process { background: #dafbe1; color: var(--green); }
|
|
.badge-console { background: #fff8c5; color: var(--orange); }
|
|
.badge-fileio { background: #ffebe9; color: var(--red); }
|
|
.badge-memory { background: #ddf4ff; color: var(--accent); }
|
|
.badge-time { background: #fbefff; color: var(--accent2); }
|
|
.badge-system { background: #ddf4ff; color: #0e6d5e; }
|
|
.badge-keyboard { background: #fff8c5; color: #7d6600; }
|
|
.badge-network { background: #ddf4ff; color: #0550ae; }
|
|
.badge-framebuf { background: #ffebe9; color: #953800; }
|
|
|
|
.note {
|
|
border-left: 3px solid var(--accent);
|
|
background: rgba(9,105,218,.06);
|
|
padding: .75rem 1rem;
|
|
margin: 1rem 0;
|
|
border-radius: 0 4px 4px 0;
|
|
font-size: .9rem;
|
|
}
|
|
.note strong { color: var(--accent); }
|
|
|
|
.warn {
|
|
border-left: 3px solid var(--orange);
|
|
background: rgba(154,103,0,.06);
|
|
padding: .75rem 1rem;
|
|
margin: 1rem 0;
|
|
border-radius: 0 4px 4px 0;
|
|
font-size: .9rem;
|
|
}
|
|
.warn strong { color: var(--orange); }
|
|
|
|
.sig {
|
|
color: #1a7f37;
|
|
}
|
|
|
|
.file-path {
|
|
color: var(--fg-muted);
|
|
font-size: .82rem;
|
|
font-style: italic;
|
|
}
|
|
|
|
hr {
|
|
border: none;
|
|
border-top: 1px solid var(--border);
|
|
margin: 2rem 0;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
nav.sidebar { display: none; }
|
|
main { margin-left: 0; padding: 1.5rem; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ================================================================= -->
|
|
<!-- SIDEBAR -->
|
|
<!-- ================================================================= -->
|
|
<nav class="sidebar">
|
|
<h2>MontaukOS Docs</h2>
|
|
<ul>
|
|
<li><a href="#overview">Overview</a></li>
|
|
<li><a href="#getting-started">Getting Started</a>
|
|
<ul>
|
|
<li><a href="#project-structure">Project Structure</a></li>
|
|
<li><a href="#toolchain">Toolchain</a></li>
|
|
<li><a href="#build-system">Build System</a></li>
|
|
<li><a href="#linker-script">Linker Script</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#program-anatomy">Program Anatomy</a>
|
|
<ul>
|
|
<li><a href="#entry-point">Entry Point</a></li>
|
|
<li><a href="#hello-world">Hello World</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#syscall-arch">Syscall Architecture</a>
|
|
<ul>
|
|
<li><a href="#calling-convention">Calling Convention</a></li>
|
|
<li><a href="#raw-wrappers">Raw Wrappers</a></li>
|
|
<li><a href="#typed-wrappers">Typed Wrappers</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#syscall-ref">Syscall Reference</a>
|
|
<ul>
|
|
<li><a href="#cat-process">Process</a></li>
|
|
<li><a href="#cat-console">Console I/O</a></li>
|
|
<li><a href="#cat-fileio">File I/O</a></li>
|
|
<li><a href="#cat-memory">Memory</a></li>
|
|
<li><a href="#cat-time">Timekeeping</a></li>
|
|
<li><a href="#cat-system">System</a></li>
|
|
<li><a href="#cat-keyboard">Keyboard</a></li>
|
|
<li><a href="#cat-network">Networking</a></li>
|
|
<li><a href="#cat-framebuffer">Framebuffer</a></li>
|
|
<li><a href="#cat-terminal">Terminal</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#data-structures">Data Structures</a></li>
|
|
<li><a href="#shell-walkthrough">Shell Walkthrough</a></li>
|
|
<li><a href="#adding-syscalls">Adding New Syscalls</a></li>
|
|
<li><a href="#memory-model">Memory Model</a></li>
|
|
<li><a href="#limitations">Limitations</a></li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<!-- ================================================================= -->
|
|
<!-- MAIN CONTENT -->
|
|
<!-- ================================================================= -->
|
|
<main>
|
|
|
|
<!-- ------ Overview ------ -->
|
|
<h1 id="overview">Userspace Developer's Handbook</h1>
|
|
<p>
|
|
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
|
|
<code>SYSCALL</code>/<code>SYSRET</code> mechanism.
|
|
</p>
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="getting-started">Getting Started</h2>
|
|
|
|
<h3 id="project-structure">Project Structure</h3>
|
|
<pre><code>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</code></pre>
|
|
<p>
|
|
Each subdirectory under <code>src/</code> is treated as a separate
|
|
program. The build system discovers them automatically.
|
|
</p>
|
|
|
|
<h3 id="toolchain">Toolchain</h3>
|
|
<p>
|
|
Programs are compiled with a freestanding <code>x86_64-elf</code>
|
|
cross-compiler. The build system looks for one in
|
|
<code>toolchain/local/bin/</code>; if not found, it falls back to the
|
|
host <code>g++</code>.
|
|
</p>
|
|
<p>Key compiler flags:</p>
|
|
<ul>
|
|
<li><code>-std=gnu++20</code> — C++20 with GNU extensions</li>
|
|
<li><code>-ffreestanding -nostdinc -nostdlib</code> — no hosted standard library</li>
|
|
<li><code>-fno-rtti -fno-exceptions</code> — no RTTI or C++ exceptions</li>
|
|
<li><code>-fno-PIC -mcmodel=small</code> — static absolute addressing</li>
|
|
<li><code>-mno-sse -mno-mmx -mno-red-zone</code> — kernel-safe ABI (C++ programs)</li>
|
|
</ul>
|
|
<div class="note">
|
|
<strong>SSE:</strong> The default C++ build disables SSE. Programs that need
|
|
floating-point support (e.g. C programs using <code>float</code>/<code>double</code>)
|
|
should compile with <code>-msse -msse2</code> instead. The kernel enables SSE in
|
|
CR0/CR4 at boot, so SSE instructions are safe in userspace.
|
|
</div>
|
|
|
|
<h3 id="build-system">Build System</h3>
|
|
<p>To build all programs:</p>
|
|
<pre><code>cd programs/
|
|
make # or: make -j$(nproc)</code></pre>
|
|
<p>
|
|
This produces one ELF binary per program in <code>bin/</code>
|
|
(e.g. <code>bin/hello.elf</code>, <code>bin/shell.elf</code>).
|
|
The binaries are then typically packed into the ramdisk
|
|
(<code>ramdisk.tar</code>) and loaded by the kernel at boot.
|
|
</p>
|
|
<p>To add a new program, create <code>src/<name>/main.cpp</code> and run <code>make</code>.</p>
|
|
|
|
<h3 id="linker-script">Linker Script</h3>
|
|
<p class="file-path">programs/link.ld</p>
|
|
<p>
|
|
All userspace programs are linked at virtual address
|
|
<code>0x400000</code>. The linker script defines four standard
|
|
sections:
|
|
</p>
|
|
<table>
|
|
<tr><th>Section</th><th>Contents</th><th>Alignment</th></tr>
|
|
<tr><td><code>.text</code></td><td>Executable code</td><td>— (base)</td></tr>
|
|
<tr><td><code>.rodata</code></td><td>Read-only data, string literals</td><td>4 KiB</td></tr>
|
|
<tr><td><code>.data</code></td><td>Initialized read/write data</td><td>4 KiB</td></tr>
|
|
<tr><td><code>.bss</code></td><td>Zero-initialized data</td><td>—</td></tr>
|
|
</table>
|
|
<p>
|
|
Debug frames (<code>.eh_frame</code>), notes, and comments are
|
|
discarded to keep binaries small.
|
|
</p>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="program-anatomy">Program Anatomy</h2>
|
|
|
|
<h3 id="entry-point">Entry Point: <code>_start</code></h3>
|
|
<p>
|
|
Because there is no C runtime, every program must define
|
|
<code>extern "C" void _start()</code> as its entry point. There is no
|
|
<code>main()</code>, no <code>argc</code>/<code>argv</code>, and no
|
|
<code>atexit</code> handlers.
|
|
</p>
|
|
<div class="note">
|
|
<strong>Auto-exit:</strong> If <code>_start()</code> returns normally,
|
|
the kernel's exit stub (mapped at <code>0x3FF000</code>) automatically
|
|
calls <code>SYS_EXIT(0)</code>. You can also call
|
|
<code>montauk::exit(code)</code> explicitly at any point.
|
|
</div>
|
|
|
|
<h3 id="hello-world">Hello World</h3>
|
|
<p class="file-path">programs/src/hello/main.cpp</p>
|
|
<pre><code><span style="color:#8b949e">// Minimal MontaukOS userspace program</span>
|
|
#include <montauk/syscall.h>
|
|
|
|
extern "C" void _start() {
|
|
montauk::print("Hello from userspace!\n");
|
|
}</code></pre>
|
|
<p>
|
|
Include <code><montauk/syscall.h></code> for the full typed API.
|
|
That header pulls in <code><Api/Syscall.hpp></code> for
|
|
constants and data structures.
|
|
</p>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="syscall-arch">Syscall Architecture</h2>
|
|
|
|
<h3 id="calling-convention">Calling Convention</h3>
|
|
<p>
|
|
MontaukOS uses the hardware <code>SYSCALL</code> instruction on
|
|
x86-64. The kernel sets up the required MSRs
|
|
(<code>IA32_STAR</code>, <code>IA32_LSTAR</code>,
|
|
<code>IA32_FMASK</code>) during boot.
|
|
</p>
|
|
<table>
|
|
<tr><th>Register</th><th>Purpose</th></tr>
|
|
<tr><td><code>RAX</code></td><td>Syscall number (in) / return value (out)</td></tr>
|
|
<tr><td><code>RDI</code></td><td>Argument 1</td></tr>
|
|
<tr><td><code>RSI</code></td><td>Argument 2</td></tr>
|
|
<tr><td><code>RDX</code></td><td>Argument 3</td></tr>
|
|
<tr><td><code>R10</code></td><td>Argument 4 (not RCX — <code>SYSCALL</code> clobbers it)</td></tr>
|
|
<tr><td><code>R8</code></td><td>Argument 5</td></tr>
|
|
<tr><td><code>R9</code></td><td>Argument 6</td></tr>
|
|
</table>
|
|
<p>
|
|
The <code>SYSCALL</code> instruction saves <code>RIP</code> in
|
|
<code>RCX</code> and <code>RFLAGS</code> in <code>R11</code>; both
|
|
registers are clobbered. The kernel masks <code>IF</code> on entry
|
|
(via <code>IA32_FMASK</code>) so interrupts are disabled during the
|
|
transition.
|
|
</p>
|
|
<p>
|
|
On the kernel side, <code>SyscallEntry</code> (assembly) saves
|
|
callee-saved registers and all arguments into a
|
|
<code>SyscallFrame</code>, then calls
|
|
<code>SyscallDispatch(SyscallFrame*)</code> which dispatches by
|
|
syscall number.
|
|
</p>
|
|
|
|
<h3 id="raw-wrappers">Raw Syscall Wrappers</h3>
|
|
<p class="file-path">programs/include/montauk/syscall.h</p>
|
|
<p>
|
|
Seven inline functions cover 0–6 argument syscalls:
|
|
</p>
|
|
<pre><code>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);</code></pre>
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<h3 id="typed-wrappers">Typed API Wrappers</h3>
|
|
<p>
|
|
The <code>montauk::</code> namespace provides type-safe wrappers around
|
|
the raw syscalls. These are the recommended interface for application
|
|
code. The full reference follows below.
|
|
</p>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="syscall-ref">Syscall Reference</h2>
|
|
<p>
|
|
MontaukOS v0.1.0 exposes <strong>26 syscalls</strong> (numbers
|
|
0–25), organized into 10 categories.
|
|
</p>
|
|
|
|
<!-- ---- Process ---- -->
|
|
<h3 id="cat-process"><span class="badge badge-process">Process</span> Process Management</h3>
|
|
|
|
<h4 id="sys-exit"><code>SYS_EXIT</code> (0) — Terminate the current process</h4>
|
|
<pre><code><span class="sig">[[noreturn]] void montauk::exit(int code = 0);</span></code></pre>
|
|
<p>
|
|
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.
|
|
</p>
|
|
|
|
<h4 id="sys-yield"><code>SYS_YIELD</code> (1) — Yield the CPU</h4>
|
|
<pre><code><span class="sig">void montauk::yield();</span></code></pre>
|
|
<p>
|
|
Voluntarily yields the remainder of the current time slice, allowing
|
|
the scheduler to run another ready process immediately.
|
|
</p>
|
|
|
|
<h4 id="sys-sleep-ms"><code>SYS_SLEEP_MS</code> (2) — Sleep for a duration</h4>
|
|
<pre><code><span class="sig">void montauk::sleep_ms(uint64_t ms);</span></code></pre>
|
|
<p>
|
|
Suspends the calling process for at least <code>ms</code>
|
|
milliseconds. Resolution depends on the APIC timer tick rate.
|
|
</p>
|
|
|
|
<h4 id="sys-getpid"><code>SYS_GETPID</code> (3) — Get process ID</h4>
|
|
<pre><code><span class="sig">int montauk::getpid();</span></code></pre>
|
|
<p>
|
|
Returns the PID of the calling process. Returns <code>-1</code> if
|
|
called from the idle context (should not happen in userspace).
|
|
</p>
|
|
|
|
<h4 id="sys-spawn"><code>SYS_SPAWN</code> (20) — Spawn a new process</h4>
|
|
<pre><code><span class="sig">int montauk::spawn(const char* path, const char* args = nullptr);</span></code></pre>
|
|
<p>
|
|
Loads the ELF binary at <code>path</code> (a VFS path like
|
|
<code>"0:/hello.elf"</code>) and spawns it as a new process.
|
|
Returns the new process's PID on success, or <code>-1</code> on
|
|
failure (no free slots, invalid ELF, file not found).
|
|
The optional <code>args</code> string (up to 255 characters) is
|
|
copied into the new process and can be retrieved with
|
|
<code>SYS_GETARGS</code>.
|
|
</p>
|
|
|
|
<h4 id="sys-waitpid"><code>SYS_WAITPID</code> (23) — Wait for a process to exit</h4>
|
|
<pre><code><span class="sig">void montauk::waitpid(int pid);</span></code></pre>
|
|
<p>
|
|
Blocks the calling process until the process identified by
|
|
<code>pid</code> 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. <code>run</code>
|
|
command) so that keyboard input and the terminal are not shared
|
|
simultaneously.
|
|
</p>
|
|
|
|
<h4 id="sys-getargs"><code>SYS_GETARGS</code> (25) — Get process arguments</h4>
|
|
<pre><code><span class="sig">int montauk::getargs(char* buf, uint64_t maxLen);</span></code></pre>
|
|
<p>
|
|
Copies the argument string passed to the current process (via
|
|
<code>spawn()</code>) into <code>buf</code>, writing at most
|
|
<code>maxLen - 1</code> characters plus a null terminator.
|
|
Returns the number of characters copied, or <code>-1</code> on error.
|
|
If no arguments were provided at spawn time, the buffer will be empty.
|
|
</p>
|
|
|
|
<!-- ---- Console I/O ---- -->
|
|
<h3 id="cat-console"><span class="badge badge-console">Console</span> Console I/O</h3>
|
|
|
|
<h4 id="sys-print"><code>SYS_PRINT</code> (4) — Print a string</h4>
|
|
<pre><code><span class="sig">void montauk::print(const char* text);</span></code></pre>
|
|
<p>
|
|
Writes a null-terminated string to the kernel terminal. Supports
|
|
newlines (<code>\n</code>) and standard printable ASCII.
|
|
</p>
|
|
|
|
<h4 id="sys-putchar"><code>SYS_PUTCHAR</code> (5) — Print a single character</h4>
|
|
<pre><code><span class="sig">void montauk::putchar(char c);</span></code></pre>
|
|
<p>
|
|
Writes a single character to the kernel terminal. Useful for building
|
|
output character by character (e.g. printing integers).
|
|
</p>
|
|
|
|
<!-- ---- File I/O ---- -->
|
|
<h3 id="cat-fileio"><span class="badge badge-fileio">File I/O</span> File I/O</h3>
|
|
|
|
<h4 id="sys-open"><code>SYS_OPEN</code> (6) — Open a file</h4>
|
|
<pre><code><span class="sig">int montauk::open(const char* path);</span></code></pre>
|
|
<p>
|
|
Opens a file on the VFS. Paths use the format
|
|
<code>"<device>:/<name>"</code> (e.g. <code>"0:/hello.elf"</code>
|
|
for the ramdisk). Returns a non-negative handle on success, or a
|
|
negative value on error.
|
|
</p>
|
|
|
|
<h4 id="sys-read"><code>SYS_READ</code> (7) — Read from a file</h4>
|
|
<pre><code><span class="sig">int montauk::read(int handle, uint8_t* buf, uint64_t offset, uint64_t size);</span></code></pre>
|
|
<p>
|
|
Reads up to <code>size</code> bytes from the file at the given byte
|
|
<code>offset</code> into <code>buf</code>. 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.
|
|
</p>
|
|
|
|
<h4 id="sys-getsize"><code>SYS_GETSIZE</code> (8) — Get file size</h4>
|
|
<pre><code><span class="sig">uint64_t montauk::getsize(int handle);</span></code></pre>
|
|
<p>Returns the total size (in bytes) of the file associated with <code>handle</code>.</p>
|
|
|
|
<h4 id="sys-close"><code>SYS_CLOSE</code> (9) — Close a file</h4>
|
|
<pre><code><span class="sig">void montauk::close(int handle);</span></code></pre>
|
|
<p>Closes the file handle and releases associated kernel resources.</p>
|
|
|
|
<h4 id="sys-readdir"><code>SYS_READDIR</code> (10) — List directory entries</h4>
|
|
<pre><code><span class="sig">int montauk::readdir(const char* path, const char** names, int max);</span></code></pre>
|
|
<p>
|
|
Reads up to <code>max</code> directory entries from <code>path</code>.
|
|
Entry name pointers are written into the <code>names</code> array.
|
|
The kernel allocates a user-accessible page for the string data
|
|
automatically. Returns the number of entries read, or
|
|
<code>≤ 0</code> on error/empty directory. Maximum 64 entries per call.
|
|
</p>
|
|
|
|
<!-- ---- Memory ---- -->
|
|
<h3 id="cat-memory"><span class="badge badge-memory">Memory</span> Memory Management</h3>
|
|
|
|
<p>
|
|
Memory allocation has two layers: <strong>low-level syscalls</strong>
|
|
that map pages from the kernel, and a <strong>userspace heap</strong>
|
|
that provides <code>malloc</code>/<code>mfree</code> on top.
|
|
</p>
|
|
|
|
<h4>Userspace Heap (recommended)</h4>
|
|
<p class="file-path">programs/include/montauk/heap.h</p>
|
|
<p>
|
|
Include <code><montauk/heap.h></code> for a proper free-list
|
|
allocator that runs entirely in userspace. It calls
|
|
<code>SYS_ALLOC</code> internally to obtain pages and manages
|
|
sub-page allocations with a linked free list — adapted from
|
|
the kernel's own <code>HeapAllocator</code>.
|
|
</p>
|
|
|
|
<pre><code><span class="sig">void* montauk::malloc(uint64_t size);</span></code></pre>
|
|
<p>
|
|
Allocates <code>size</code> bytes from the userspace free list.
|
|
Returns a 16-byte-aligned pointer, or <code>nullptr</code> on failure.
|
|
When the free list is exhausted, it transparently requests more pages
|
|
from the kernel via <code>SYS_ALLOC</code> (minimum 16 KiB growth).
|
|
</p>
|
|
|
|
<pre><code><span class="sig">void montauk::mfree(void* ptr);</span></code></pre>
|
|
<p>
|
|
Returns the block to the userspace free list.
|
|
<strong>No syscall is made</strong> — the memory stays mapped
|
|
and is immediately available for future <code>malloc</code> calls.
|
|
Passing <code>nullptr</code> is a safe no-op.
|
|
</p>
|
|
|
|
<pre><code><span class="sig">void* montauk::realloc(void* ptr, uint64_t size);</span></code></pre>
|
|
<p>
|
|
Resizes the allocation at <code>ptr</code> to <code>size</code> bytes.
|
|
Allocates a new block, copies the smaller of old/new sizes, and frees
|
|
the old block. If <code>ptr</code> is <code>nullptr</code>, behaves
|
|
like <code>malloc</code>.
|
|
</p>
|
|
|
|
<h4>Low-Level Page Syscalls</h4>
|
|
|
|
<h4 id="sys-alloc"><code>SYS_ALLOC</code> (11) — Map pages</h4>
|
|
<pre><code><span class="sig">void* montauk::alloc(uint64_t size);</span></code></pre>
|
|
<p>
|
|
Maps <code>size</code> bytes of zeroed physical pages into the
|
|
process's address space (starting at <code>0x40000000</code>).
|
|
The size is rounded up to the nearest page boundary (4 KiB).
|
|
Returns a pointer to the mapped region, or <code>nullptr</code> on
|
|
failure. This is the backing primitive for
|
|
<code>montauk::malloc</code> — most programs should use the heap
|
|
API instead of calling this directly.
|
|
</p>
|
|
|
|
<h4 id="sys-free"><code>SYS_FREE</code> (12) — Unmap pages (no-op)</h4>
|
|
<pre><code><span class="sig">void montauk::free(void* ptr);</span></code></pre>
|
|
<p>
|
|
Reserved for future page-level unmapping. Currently a no-op —
|
|
pages are reclaimed when the process exits. Use
|
|
<code>montauk::mfree</code> for heap allocations.
|
|
</p>
|
|
|
|
<!-- ---- Timekeeping ---- -->
|
|
<h3 id="cat-time"><span class="badge badge-time">Time</span> Timekeeping</h3>
|
|
|
|
<h4 id="sys-getticks"><code>SYS_GETTICKS</code> (13) — Get tick count</h4>
|
|
<pre><code><span class="sig">uint64_t montauk::get_ticks();</span></code></pre>
|
|
<p>
|
|
Returns the number of APIC timer ticks since boot. The tick rate
|
|
depends on the hardware and APIC timer calibration.
|
|
</p>
|
|
|
|
<h4 id="sys-getmilliseconds"><code>SYS_GETMILLISECONDS</code> (14) — Get milliseconds since boot</h4>
|
|
<pre><code><span class="sig">uint64_t montauk::get_milliseconds();</span></code></pre>
|
|
<p>
|
|
Returns wall-clock milliseconds elapsed since boot. Useful for
|
|
calculating uptime or measuring durations.
|
|
</p>
|
|
|
|
<!-- ---- System ---- -->
|
|
<h3 id="cat-system"><span class="badge badge-system">System</span> System Information</h3>
|
|
|
|
<h4 id="sys-getinfo"><code>SYS_GETINFO</code> (15) — Get OS information</h4>
|
|
<pre><code><span class="sig">void montauk::get_info(Montauk::SysInfo* info);</span></code></pre>
|
|
<p>
|
|
Fills in a <a href="#struct-sysinfo"><code>SysInfo</code></a> structure
|
|
with the OS name, version string, API version number, and maximum
|
|
process count.
|
|
</p>
|
|
|
|
<!-- ---- Keyboard ---- -->
|
|
<h3 id="cat-keyboard"><span class="badge badge-keyboard">Keyboard</span> Keyboard Input</h3>
|
|
|
|
<h4 id="sys-iskeyavailable"><code>SYS_ISKEYAVAILABLE</code> (16) — Check for pending key</h4>
|
|
<pre><code><span class="sig">bool montauk::is_key_available();</span></code></pre>
|
|
<p>
|
|
Returns <code>true</code> if a key event is available in the PS/2
|
|
keyboard buffer. Non-blocking.
|
|
</p>
|
|
|
|
<h4 id="sys-getkey"><code>SYS_GETKEY</code> (17) — Get a key event</h4>
|
|
<pre><code><span class="sig">void montauk::getkey(Montauk::KeyEvent* out);</span></code></pre>
|
|
<p>
|
|
Fills in a <a href="#struct-keyevent"><code>KeyEvent</code></a>
|
|
structure with the next keyboard event (press or release), including
|
|
scancode, ASCII translation, and modifier state (Shift, Ctrl, Alt).
|
|
</p>
|
|
|
|
<h4 id="sys-getchar"><code>SYS_GETCHAR</code> (18) — Read a character (blocking)</h4>
|
|
<pre><code><span class="sig">char montauk::getchar();</span></code></pre>
|
|
<p>
|
|
Blocks until a printable character key-press is available, then
|
|
returns the ASCII character. This is the simplest way to read
|
|
interactive text input.
|
|
</p>
|
|
|
|
<!-- ---- Networking ---- -->
|
|
<h3 id="cat-network"><span class="badge badge-network">Network</span> Networking</h3>
|
|
|
|
<h4 id="sys-ping"><code>SYS_PING</code> (19) — Send an ICMP echo request</h4>
|
|
<pre><code><span class="sig">int32_t montauk::ping(uint32_t ip, uint32_t timeoutMs = 3000);</span></code></pre>
|
|
<p>
|
|
Sends an ICMP echo request to <code>ip</code> and waits up to
|
|
<code>timeoutMs</code> milliseconds for a reply. The IP address is in
|
|
little-endian byte order (e.g. <code>10.0.2.2</code> →
|
|
<code>0x0202000A</code>).
|
|
Returns the round-trip time in milliseconds on success, or
|
|
<code>-1</code> on timeout.
|
|
</p>
|
|
|
|
<!-- ---- Framebuffer ---- -->
|
|
<h3 id="cat-framebuffer"><span class="badge badge-framebuf">Framebuffer</span> Framebuffer Access</h3>
|
|
|
|
<h4 id="sys-fbinfo"><code>SYS_FBINFO</code> (21) — Get framebuffer information</h4>
|
|
<pre><code><span class="sig">void montauk::fb_info(Montauk::FbInfo* info);</span></code></pre>
|
|
<p>
|
|
Fills in a <a href="#struct-fbinfo"><code>FbInfo</code></a> structure
|
|
with the framebuffer dimensions, pitch (bytes per scanline), and
|
|
bits per pixel. Call this before <code>SYS_FBMAP</code> to learn the
|
|
framebuffer geometry.
|
|
</p>
|
|
|
|
<h4 id="sys-fbmap"><code>SYS_FBMAP</code> (22) — Map framebuffer into process memory</h4>
|
|
<pre><code><span class="sig">void* montauk::fb_map();</span></code></pre>
|
|
<p>
|
|
Maps the physical framebuffer into the calling process's address
|
|
space at <code>0x50000000</code> and returns the user virtual address.
|
|
The mapped region covers <code>height × pitch</code> bytes. Each
|
|
pixel is a 32-bit value in <code>0xAARRGGBB</code> format (blue in
|
|
the low byte). Writing to this memory directly updates the screen.
|
|
</p>
|
|
<div class="note">
|
|
<strong>Note:</strong> After mapping, the cursor overlay is not composited
|
|
automatically. Programs that use the framebuffer take full control of
|
|
screen output for the mapped region.
|
|
</div>
|
|
|
|
<!-- ---- Terminal ---- -->
|
|
<h3 id="cat-terminal"><span class="badge badge-system">Terminal</span> Terminal</h3>
|
|
|
|
<h4 id="sys-termsize"><code>SYS_TERMSIZE</code> (24) — Get terminal dimensions</h4>
|
|
<pre><code><span class="sig">void montauk::termsize(int* cols, int* rows);</span></code></pre>
|
|
<p>
|
|
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
|
|
<code>nullptr</code> if you only need one dimension.
|
|
</p>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="data-structures">Data Structures</h2>
|
|
|
|
<h3 id="struct-sysinfo"><code>Montauk::SysInfo</code></h3>
|
|
<p class="file-path">programs/include/Api/Syscall.hpp</p>
|
|
<pre><code>struct SysInfo {
|
|
char osName[32]; <span style="color:#8b949e">// e.g. "MontaukOS"</span>
|
|
char osVersion[32]; <span style="color:#8b949e">// e.g. "0.1.0"</span>
|
|
uint32_t apiVersion; <span style="color:#8b949e">// Current: 2</span>
|
|
uint32_t maxProcesses; <span style="color:#8b949e">// Current: 16</span>
|
|
};</code></pre>
|
|
|
|
<h3 id="struct-fbinfo"><code>Montauk::FbInfo</code></h3>
|
|
<pre><code>struct FbInfo {
|
|
uint64_t width; <span style="color:#8b949e">// Framebuffer width in pixels</span>
|
|
uint64_t height; <span style="color:#8b949e">// Framebuffer height in pixels</span>
|
|
uint64_t pitch; <span style="color:#8b949e">// Bytes per scanline</span>
|
|
uint64_t bpp; <span style="color:#8b949e">// Bits per pixel (always 32)</span>
|
|
uint64_t userAddr; <span style="color:#8b949e">// Reserved (0 until mapped via SYS_FBMAP)</span>
|
|
};</code></pre>
|
|
|
|
<h3 id="struct-keyevent"><code>Montauk::KeyEvent</code></h3>
|
|
<pre><code>struct KeyEvent {
|
|
uint8_t scancode; <span style="color:#8b949e">// Raw PS/2 scancode</span>
|
|
char ascii; <span style="color:#8b949e">// Translated ASCII character (0 if non-printable)</span>
|
|
bool pressed; <span style="color:#8b949e">// true = key down, false = key up</span>
|
|
bool shift; <span style="color:#8b949e">// Shift modifier active</span>
|
|
bool ctrl; <span style="color:#8b949e">// Ctrl modifier active</span>
|
|
bool alt; <span style="color:#8b949e">// Alt modifier active</span>
|
|
};</code></pre>
|
|
|
|
<h3 id="struct-syscallframe"><code>Montauk::SyscallFrame</code> (kernel only)</h3>
|
|
<pre><code>struct SyscallFrame {
|
|
uint64_t r15, r14, r13, r12, rbp, rbx; <span style="color:#8b949e">// callee-saved</span>
|
|
uint64_t arg6, arg5, arg4, arg3, arg2, arg1;
|
|
uint64_t syscall_nr;
|
|
uint64_t user_rflags, user_rip, user_rsp;
|
|
};</code></pre>
|
|
<p>
|
|
This is the stack frame pushed by <code>SyscallEntry.asm</code> and
|
|
passed to <code>SyscallDispatch</code>. Userspace code never sees this
|
|
directly.
|
|
</p>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="shell-walkthrough">Shell Application Walkthrough</h2>
|
|
<p class="file-path">programs/src/shell/main.cpp</p>
|
|
<p>
|
|
The built-in shell is the best example of a real MontaukOS application.
|
|
It demonstrates most of the available syscalls.
|
|
</p>
|
|
|
|
<h3>Initialization</h3>
|
|
<pre><code>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(); <span style="color:#8b949e">// blocking read</span>
|
|
<span style="color:#8b949e">// ... handle input, echo, backspace ...</span>
|
|
}
|
|
}</code></pre>
|
|
<p>
|
|
The shell uses <code>montauk::getchar()</code> in a loop for blocking
|
|
character-by-character input, manually handling echo and backspace.
|
|
</p>
|
|
|
|
<h3>Shell Commands & Syscalls Used</h3>
|
|
<table>
|
|
<tr><th>Command</th><th>Description</th><th>Syscalls Used</th></tr>
|
|
<tr>
|
|
<td><code>help</code></td>
|
|
<td>Print available commands</td>
|
|
<td><code>SYS_PRINT</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>info</code></td>
|
|
<td>Show OS name, version, API version</td>
|
|
<td><code>SYS_GETINFO</code>, <code>SYS_PRINT</code>, <code>SYS_PUTCHAR</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>man <topic></code></td>
|
|
<td>Fullscreen manual page viewer</td>
|
|
<td><code>SYS_OPEN</code>, <code>SYS_GETSIZE</code>, <code>SYS_READ</code>, <code>SYS_CLOSE</code>, <code>SYS_ALLOC</code>, <code>SYS_TERMSIZE</code>, <code>SYS_GETKEY</code>, <code>SYS_PRINT</code>, <code>SYS_PUTCHAR</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>ls</code></td>
|
|
<td>List ramdisk files</td>
|
|
<td><code>SYS_READDIR</code>, <code>SYS_PRINT</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>cat <file></code></td>
|
|
<td>Display file contents in 512-byte chunks</td>
|
|
<td><code>SYS_OPEN</code>, <code>SYS_GETSIZE</code>, <code>SYS_READ</code>, <code>SYS_CLOSE</code>, <code>SYS_PRINT</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>run <file></code></td>
|
|
<td>Spawn a new process and wait for it to exit</td>
|
|
<td><code>SYS_SPAWN</code>, <code>SYS_WAITPID</code>, <code>SYS_PRINT</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>ping <ip></code></td>
|
|
<td>Send 4 ICMP echo requests</td>
|
|
<td><code>SYS_PING</code>, <code>SYS_SLEEP_MS</code>, <code>SYS_PRINT</code>, <code>SYS_PUTCHAR</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>uptime</code></td>
|
|
<td>Show uptime in minutes, seconds, ms</td>
|
|
<td><code>SYS_GETMILLISECONDS</code>, <code>SYS_PRINT</code>, <code>SYS_PUTCHAR</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>clear</code></td>
|
|
<td>Scroll past visible content</td>
|
|
<td><code>SYS_PUTCHAR</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>exit</code></td>
|
|
<td>Terminate the shell</td>
|
|
<td><code>SYS_PRINT</code>, <code>SYS_EXIT</code></td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h3>Pattern: Reading a File</h3>
|
|
<p>The shell's <code>cat</code> implementation shows the standard file-reading pattern:</p>
|
|
<pre><code><span style="color:#8b949e">// 1. Build VFS path</span>
|
|
char path[128];
|
|
<span style="color:#8b949e">// ... copy "0:/" + filename into path ...</span>
|
|
|
|
<span style="color:#8b949e">// 2. Open</span>
|
|
int handle = montauk::open(path);
|
|
if (handle < 0) { <span style="color:#8b949e">/* error */</span> return; }
|
|
|
|
<span style="color:#8b949e">// 3. Get size</span>
|
|
uint64_t size = montauk::getsize(handle);
|
|
|
|
<span style="color:#8b949e">// 4. Read in chunks</span>
|
|
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;
|
|
}
|
|
|
|
<span style="color:#8b949e">// 5. Close</span>
|
|
montauk::close(handle);</code></pre>
|
|
|
|
<h3>Pattern: IP Networking</h3>
|
|
<pre><code><span style="color:#8b949e">// Parse "10.0.2.2" into uint32_t in little-endian order</span>
|
|
uint32_t ip;
|
|
parse_ip("10.0.2.2", &ip); <span style="color:#8b949e">// ip = 0x0202000A</span>
|
|
|
|
<span style="color:#8b949e">// Send 4 pings with 1-second intervals</span>
|
|
for (int i = 0; i < 4; i++) {
|
|
int32_t rtt = montauk::ping(ip, 3000);
|
|
if (rtt < 0) { <span style="color:#8b949e">/* timeout */</span> }
|
|
else { <span style="color:#8b949e">/* reply in rtt ms */</span> }
|
|
if (i < 3) montauk::sleep_ms(1000);
|
|
}</code></pre>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="adding-syscalls">Adding New Syscalls</h2>
|
|
<p>
|
|
Adding a syscall requires changes in <strong>3 files</strong>
|
|
(kernel-side) and <strong>2 files</strong> (userspace-side). Follow
|
|
these steps:
|
|
</p>
|
|
|
|
<h3>Step 1: Assign a Syscall Number</h3>
|
|
<p>
|
|
Add a new constant in <strong>both</strong> copies of the syscall
|
|
number definitions. The numbers must match exactly.
|
|
</p>
|
|
<p class="file-path">kernel/src/Api/Syscall.hpp</p>
|
|
<p class="file-path">programs/include/Api/Syscall.hpp</p>
|
|
<pre><code>static constexpr uint64_t SYS_MYFUNC = 26; <span style="color:#8b949e">// next available number</span></code></pre>
|
|
<div class="warn">
|
|
<strong>Keep both files in sync.</strong> The kernel and userspace
|
|
headers define syscall numbers independently. If they disagree, the
|
|
wrong handler runs.
|
|
</div>
|
|
|
|
<h3>Step 2: Implement the Kernel Handler</h3>
|
|
<p class="file-path">kernel/src/Api/Syscall.cpp</p>
|
|
<p>Add a static function implementing your syscall's logic:</p>
|
|
<pre><code>static int64_t Sys_MyFunc(uint64_t arg1, const char* arg2) {
|
|
<span style="color:#8b949e">// Your kernel-side implementation here.</span>
|
|
<span style="color:#8b949e">// You have full access to kernel subsystems.</span>
|
|
return 0;
|
|
}</code></pre>
|
|
|
|
<h3>Step 3: Add the Dispatch Case</h3>
|
|
<p class="file-path">kernel/src/Api/Syscall.cpp — <code>SyscallDispatch()</code></p>
|
|
<pre><code>case SYS_MYFUNC:
|
|
return (int64_t)Sys_MyFunc(frame->arg1, (const char*)frame->arg2);</code></pre>
|
|
<p>
|
|
Arguments are accessed through <code>frame->arg1</code> through
|
|
<code>frame->arg6</code>, corresponding to RDI, RSI, RDX, R10, R8,
|
|
R9 respectively.
|
|
</p>
|
|
|
|
<h3>Step 4: Add a Typed Userspace Wrapper</h3>
|
|
<p class="file-path">programs/include/montauk/syscall.h</p>
|
|
<pre><code>inline int64_t my_func(uint64_t arg1, const char* arg2) {
|
|
return syscall2(Montauk::SYS_MYFUNC, arg1, (uint64_t)arg2);
|
|
}</code></pre>
|
|
<p>
|
|
Choose the appropriate <code>syscallN</code> variant based on the
|
|
number of arguments. Cast pointer types to <code>uint64_t</code>.
|
|
</p>
|
|
|
|
<h3>Step 5: Update the Log Message (optional)</h3>
|
|
<p class="file-path">kernel/src/Api/Syscall.cpp — <code>InitializeSyscalls()</code></p>
|
|
<p>
|
|
Update the boot log to reflect the new syscall count:
|
|
</p>
|
|
<pre><code><span style="color:#8b949e">// Change "26 syscalls" to "27 syscalls"</span>
|
|
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
|
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 27 syscalls)";</code></pre>
|
|
|
|
<h3>Complete Checklist</h3>
|
|
<table>
|
|
<tr><th>#</th><th>File</th><th>Change</th></tr>
|
|
<tr><td>1</td><td><code>kernel/src/Api/Syscall.hpp</code></td><td>Add <code>SYS_MYFUNC</code> constant (+ any new structs)</td></tr>
|
|
<tr><td>2</td><td><code>kernel/src/Api/Syscall.cpp</code></td><td>Add <code>Sys_MyFunc()</code> implementation + dispatch case</td></tr>
|
|
<tr><td>3</td><td><code>programs/include/Api/Syscall.hpp</code></td><td>Add matching <code>SYS_MYFUNC</code> constant (+ any new structs)</td></tr>
|
|
<tr><td>4</td><td><code>programs/include/montauk/syscall.h</code></td><td>Add typed wrapper in <code>montauk::</code> namespace</td></tr>
|
|
<tr><td>5</td><td><em>(optional)</em></td><td>Update syscall count in boot log</td></tr>
|
|
</table>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="memory-model">Process Memory Model</h2>
|
|
<p>
|
|
Each process gets its own PML4 page table. The kernel half
|
|
(upper 256 entries) is shared; the lower half is per-process.
|
|
</p>
|
|
<table>
|
|
<tr><th>Region</th><th>Virtual Address</th><th>Size</th><th>Purpose</th></tr>
|
|
<tr><td>Exit stub</td><td><code>0x3FF000</code></td><td>4 KiB</td><td>Auto-exit trampoline (calls <code>SYS_EXIT(0)</code> if <code>_start</code> returns)</td></tr>
|
|
<tr><td>Program code</td><td><code>0x400000</code>+</td><td>Varies</td><td>ELF <code>.text</code>, <code>.rodata</code>, <code>.data</code>, <code>.bss</code></td></tr>
|
|
<tr><td>User heap</td><td><code>0x40000000</code>+</td><td>Grows up</td><td>Page pool (<code>SYS_ALLOC</code>); managed by userspace free-list heap</td></tr>
|
|
<tr><td>Framebuffer</td><td><code>0x50000000</code>+</td><td>height × pitch</td><td>Mapped by <code>SYS_FBMAP</code>; direct pixel access (32-bit ARGB)</td></tr>
|
|
<tr><td>User stack</td><td><code>0x7FFFFEF000</code>–<code>0x7FFFFFF000</code></td><td>16 KiB (4 pages)</td><td>Grows down from <code>0x7FFFFFF000</code></td></tr>
|
|
</table>
|
|
<p>
|
|
The ELF loader maps PT_LOAD segments with user-accessible page flags.
|
|
BSS is zero-initialized automatically (pages are allocated zeroed).
|
|
</p>
|
|
|
|
<!-- ================================================================= -->
|
|
<h2 id="limitations">Current Limitations</h2>
|
|
<ul>
|
|
<li><strong>No standard library (C++)</strong> — there is no libc++ for C++ programs.
|
|
String operations, integer formatting, etc. must be written manually (see the shell for
|
|
examples). A minimal C standard library exists for C programs (used by the DOOM port) in
|
|
<code>programs/include/libc/</code> with headers like <code>stdio.h</code>,
|
|
<code>stdlib.h</code>, and <code>string.h</code>.</li>
|
|
<li><strong>No dynamic linking</strong> — all programs are statically linked.</li>
|
|
<li><strong>No argc/argv</strong> — <code>_start</code> receives no
|
|
<code>argc</code>/<code>argv</code>. Use <code>montauk::getargs()</code>
|
|
to retrieve a single argument string (max 255 chars) passed via
|
|
<code>montauk::spawn()</code>.</li>
|
|
<li><strong>No page-level <code>free()</code></strong> —
|
|
<code>SYS_FREE</code> is a no-op; pages mapped via <code>SYS_ALLOC</code>
|
|
are only reclaimed when the process exits. However, the userspace heap
|
|
(<code>montauk::malloc</code>/<code>montauk::mfree</code>) does reuse
|
|
memory via a free list.</li>
|
|
<li><strong>16 process slots</strong> — the scheduler supports a
|
|
maximum of 16 concurrent processes.</li>
|
|
<li><strong>No writable file system</strong> — the VFS is read-only
|
|
(backed by the boot ramdisk).</li>
|
|
<li><strong>Shared console</strong> — all processes write to the
|
|
same terminal; there is no per-process stdout.</li>
|
|
<li><strong>Shared keyboard</strong> — keyboard input is global;
|
|
there is no input focus or multiplexing.</li>
|
|
<li><strong>10 ms scheduling granularity</strong> — preemptive
|
|
round-robin with a fixed 10 ms time slice.</li>
|
|
</ul>
|
|
|
|
<hr>
|
|
<p style="color:var(--fg-muted); font-size:.85rem; margin-top:2rem;">
|
|
MontaukOS Documentation — Copyright © 2025-2026 Daniel Hammer
|
|
</p>
|
|
|
|
</main>
|
|
</body>
|
|
</html>
|