8.7 KiB
System Calls
System calls (syscalls) are the bridge between your programs and the operating system's restricted core (kernel).
Status: danos currently has a placeholder M1 surface behind an
int 0x80gate —0 = exit(code),1 = ping(value),2 = write(ptr, len)(seesrc/kernel/usermode.zig, used bysbin/init.zig). It exists to prove the ring transition; the microkernel set below replaces it (viasyscall/sysret) when processes land (M3).
The Mechanism of a Syscall
A system call follows a highly orchestrated, 6-step sequence to ensure hardware safety and process security:
- Setting the Arguments: The program places a unique ID corresponding to the requested service (e.g., (sys_write)) into a specific CPU register (like RAX in x86_64) along with its required parameters.
- Executing the Trap: The program triggers a special CPU instruction, such as syscall or int 0x80. This acts as a software interrupt.
- Mode Switch: The CPU atomically flips its execution privilege from unprivileged User Mode (Ring 3) to the highly privileged Kernel Mode (Ring 0).
- Lookup and Execution: The kernel looks up the syscall ID in a dispatch table and runs the designated internal routine to perform the actual work (like fetching data from the hard drive).
- Returning the Status: The kernel places the result of the operation—or an error code—back into the RAX register.
- Return to User Mode: The kernel executes a return instruction (such as sysret), the CPU shifts back to User Mode, and your program continues executing.
Approaches
There are two approaches to system calls, stable public ABI and unstable private ABI. A public ABI uses a standard defined set of numbers. It makes it easy to guess, easy to write compilers and tools for. private ABI tend to change the numbers to obscure the numbers, preventing attackers from bypassing libc using CPU instructions directly. Private ABIs force users to use a library like libSystem, which the kernel can inject the syscall numbers. An alternative solution is to just have 1 system number but make it generic by the address to a struct in memory.
Practical minimum primitives (Monolithic kernel)
| Category | Primitive | Detail |
|---|---|---|
| lifecyle | spawn / exec | Loads a binary from storage into memory and begins execution. |
| lifecyle | exit | Terminates the current process and frees its memory back to the kernel. |
| I/O | read | Requests data from a hardware device or file descriptor into user memory. |
| I/O | write | Pushes data from user memory out to a device or file descriptor (like a screen). |
| Memory | brk / mmap | Requests the kernel to allocate more physical or virtual memory pages to the process. |
| Control | ioctl | A catch-all "escape hatch" call to send hardware-specific commands to device drivers. |
The Microkernel Minimum Set
Everything else---includingread(),write(),malloc(), andfork()---will run in user space as servers (e.g., a VFS server, a memory manager server) that threads communicate with using these three calls:
IPC_Call(endpoint, message_buffer)(Synchronous Send + Receive)- **What it does:**The calling thread sends a message block to a service endpoint and immediately blocks (sleeps) until that service processes the request and sends a reply back.
- **Why it's minimal:**CombiningSendandReceiveinto a single atomic atomic system call eliminates the need for separate tracking and prevents a massive amount of context-switching overhead. This is the foundation of high-performance microkernels likeseL4and L4.[1,2]
IPC_ReplyWait(endpoint, reply_buffer)(Respond + Wait for Next)- **What it does:**Used strictly by your background user-space servers (like your disk driver or filesystem). It sends a reply to the last client that called it, and immediately puts the server to sleep until the next request arrives.[1]
Yield()/Thread_Ctrl()- **What it does:**Allows a thread to voluntarily give up its CPU time slice, or allows a root task to spawn/kill threads.
Hardware Implementation: x86_64 vs. aarch64
Because you are targeting both platforms, you must design a cleanArchitecture Abstraction Layer (AAL). Each architecture uses completely different assembly instructions, CPU registers, and privilege levels to jump from user space (Ring 3 / EL0) to kernel space (Ring 0 / EL1).[1,2,3,4]
Here is how you will map your bare minimum system calls on both architectures:
1. x86_64 Implementation
On 64-bit Intel and AMD processors, you ignore the oldint 0x80software interrupts. Instead, you use the high-performancesyscallandsysretinstructions.[1]
- **The Trap:**The user-space program executes the
syscallinstruction.[1] - **The Registers:**The hardware automatically moves the instruction pointer, but you must pass your arguments in specific registers. A common microkernel convention mimics the System V AMD64 ABI:
rax: System Call ID (e.g.,0for IPC_Call,1for IPC_ReplyWait)rdi: Argument 1 (Endpoint ID / Destination)rsi: Argument 2 (Pointer to the message payload buffer)rdx: Argument 3 (Size of the message)[1]
- **Kernel Setup:**Your kernel must configure the Model Specific Registers (MSRs)---specifically
IA32_STARandIA32_LSTAR---during boot to point to your kernel's system call entry assembly code.[1,2]
2. aarch64 (ARM 64-bit) Implementation
On ARMv8-A and ARMv9-A architectures, privilege levels are called Exception Levels. User space runs atEL0, and your microkernel runs atEL1.[1,2,3,4]
- **The Trap:**The user-space program executes the
svc #0(Supervisor Call) instruction.[1] - **The Registers:**ARM provides a clean, plentiful register set. You typically pass your arguments in the standard parameter registers:
- **Kernel Setup:**Your kernel must set up an Exception Vector Table and write its base address to the
VBAR_EL1register. Whensvcis executed, the CPU jumps to the synchronous exception offset in that table.[1,2,3]
Managing the Payload Challenge
Because it is a microkernel, performance lives or dies by how fast yourIPC_Callcan move data from Client to Server. You have two minimal choices for handling themessage_bufferpointer:[1]
- **The Copy Method (Simplest to start):**Your kernel pauses the client, reads the data from the client's memory space, switches page tables to the server, and copies the data into the server's buffer.
- **The Shared Memory Method (Fastest):**The kernel sets up a temporary, shared virtual memory page between the client and server. The client writes to it, calls
syscall/svc, and the server reads it instantly without the kernel copying any bytes