feat: expand user mode, add DOOM game, add manpages

This commit is contained in:
2026-02-18 15:13:53 +01:00
parent 605fbcbe42
commit 24af60d669
51 changed files with 4484 additions and 43 deletions
+74
View File
@@ -0,0 +1,74 @@
.TH FILE 2
.SH NAME
open, read, getsize, close, readdir - file I/O system calls
.SH SYNOPSIS
.BI int zenith::open(const char* path);
.BI int zenith::read(int handle, uint8_t* buf, uint64_t offset, uint64_t size);
.BI uint64_t zenith::getsize(int handle);
.BI void zenith::close(int handle);
.BI int zenith::readdir(const char* path, const char** names, int max);
.SH DESCRIPTION
ZenithOS provides a simple read-only Virtual File System (VFS)
backed by the boot ramdisk. Files are accessed via paths in the
format "<drive>:/<name>", where drive 0 is the ramdisk.
.SS open
Opens a file and returns a non-negative handle on success, or a
negative value on error (file not found, no free handles).
int h = zenith::open("0:/shell.elf");
.SS read
Reads up to 'size' bytes starting at 'offset' into 'buf'.
Returns the number of bytes actually read, or negative on error.
There is no implicit file position -- the offset is explicit on
every call.
uint8_t buf[512];
int n = zenith::read(h, buf, 0, 512);
.SS getsize
Returns the total size in bytes of the file.
uint64_t sz = zenith::getsize(h);
.SS close
Closes the file handle and frees kernel resources.
zenith::close(h);
.SS readdir
Lists entries in a directory. Up to 'max' entry names (max 64)
are written to the 'names' array. The kernel allocates a user-
accessible page for the string data automatically.
const char* entries[64];
int count = zenith::readdir("0:/", entries, 64);
.SH READING PATTERN
The standard pattern for reading a file:
int h = zenith::open("0:/myfile.txt");
uint64_t size = zenith::getsize(h);
uint8_t buf[512];
uint64_t off = 0;
while (off < size) {
uint64_t chunk = size - off;
if (chunk > 511) chunk = 511;
int n = zenith::read(h, buf, off, chunk);
if (n <= 0) break;
buf[n] = '\0';
zenith::print((const char*)buf);
off += n;
}
zenith::close(h);
.SH NOTES
The filesystem is read-only. There are no write or create calls.
All files live on the ramdisk which is loaded at boot from a
USTAR tar archive.
.SH SEE ALSO
syscalls(2), spawn(2), malloc(3)
+62
View File
@@ -0,0 +1,62 @@
.TH FRAMEBUFFER 2
.SH NAME
fb_info, fb_map - direct framebuffer access
.SH SYNOPSIS
.BI void zenith::fb_info(Zenith::FbInfo* info);
.BI void* zenith::fb_map();
.SH DESCRIPTION
These syscalls allow userspace programs to access the linear
framebuffer directly for graphical output.
.SS fb_info
Fills in an FbInfo structure with the framebuffer geometry:
Zenith::FbInfo fb;
zenith::fb_info(&fb);
// fb.width, fb.height, fb.pitch, fb.bpp
The pitch is the number of bytes per scanline (may be larger
than width * 4 due to alignment). bpp is always 32.
.SS fb_map
Maps the physical framebuffer into the process address space at
a fixed virtual address (0x50000000) and returns that address.
uint32_t* pixels = (uint32_t*)zenith::fb_map();
Each pixel is a 32-bit value in 0xAARRGGBB format (blue in the
low byte). Writing to this memory directly updates the screen.
.SH PIXEL FORMAT
Bits 31-24: Alpha (unused, typically 0xFF)
Bits 23-16: Red
Bits 15-8: Green
Bits 7-0: Blue
Example: red = 0x00FF0000, green = 0x0000FF00, blue = 0x000000FF
.SH EXAMPLE
Fill the screen with blue:
Zenith::FbInfo fb;
zenith::fb_info(&fb);
uint32_t* pixels = (uint32_t*)zenith::fb_map();
for (uint64_t y = 0; y < fb.height; y++) {
uint32_t* row = (uint32_t*)((uint8_t*)pixels + y * fb.pitch);
for (uint64_t x = 0; x < fb.width; x++) {
row[x] = 0x000000FF;
}
}
.SH NOTES
After mapping, the cursor overlay is not composited. Programs
that use the framebuffer take full control of screen output.
Only one mapping per process is supported. Calling fb_map()
multiple times returns the same address.
.SH SEE ALSO
syscalls(2), malloc(3)
+49
View File
@@ -0,0 +1,49 @@
.TH INTRO 1
.SH NAME
intro - introduction to ZenithOS userspace
.SH DESCRIPTION
ZenithOS 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.
Programs are compiled with a freestanding cross-compiler and
linked at virtual address 0x400000. There is no standard C
library for C++ programs -- all system interaction goes through
the zenith:: syscall wrappers.
.SH GETTING STARTED
To write a new program, create a directory under programs/src/
with a main.cpp file. The entry point is:
extern "C" void _start() { ... }
There is no argc/argv. Include <zenith/syscall.h> for the full
typed syscall API. Include <zenith/heap.h> for malloc/mfree.
Build with:
cd programs && make
The resulting ELF binary appears in programs/bin/.
.SH SHELL
The built-in shell is the primary way to interact with ZenithOS.
Type 'help' at the shell prompt for a list of commands. Use
'man shell' for detailed shell documentation.
.SH MAN PAGES
The following man pages are available:
intro(1) This page
shell(1) Shell commands reference
man(1) The man command itself
syscalls(2) Overview of all syscalls
spawn(2) Process spawning
file(2) File I/O syscalls
framebuffer(2) Framebuffer access
malloc(3) Memory allocation
.SH SEE ALSO
shell(1), syscalls(2), malloc(3)
+57
View File
@@ -0,0 +1,57 @@
.TH MALLOC 3
.SH NAME
malloc, mfree, realloc - userspace heap allocation
.SH SYNOPSIS
.BI void* zenith::malloc(uint64_t size);
.BI void zenith::mfree(void* ptr);
.BI void* zenith::realloc(void* ptr, uint64_t size);
.SH DESCRIPTION
The userspace heap provides dynamic memory allocation on top of
the kernel's page-mapping syscall (SYS_ALLOC). Include the
header <zenith/heap.h> to use these functions.
.SS malloc
Allocates 'size' bytes from the free list. Returns a 16-byte
aligned pointer, or nullptr on failure. When the free list is
empty, it requests more pages from the kernel via SYS_ALLOC
(minimum 16 KiB growth, initial seed of 64 KiB).
char* buf = (char*)zenith::malloc(1024);
.SS mfree
Returns the block to the userspace free list. No syscall is
made -- the memory stays mapped and is immediately reusable.
Passing nullptr is a safe no-op.
zenith::mfree(buf);
.SS realloc
Resizes the allocation 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.
buf = (char*)zenith::realloc(buf, 2048);
.SH IMPLEMENTATION
The allocator uses a linked free-list with first-fit search.
Blocks larger than needed are split. The allocation header is
16 bytes (magic + size). All allocations are 16-byte aligned.
The heap grows by requesting pages from the kernel via
SYS_ALLOC. These pages are never returned to the kernel (since
SYS_FREE is currently a no-op), but mfree makes them available
for future malloc calls within the process.
.SH LOW-LEVEL PAGE API
For large allocations or when direct page control is needed:
void* zenith::alloc(uint64_t size); // SYS_ALLOC
void zenith::free(void* ptr); // SYS_FREE (no-op)
alloc() maps zeroed pages starting at 0x40000000 and growing
upward. Size is rounded up to 4 KiB page boundaries.
.SH SEE ALSO
syscalls(2), file(2)
+47
View File
@@ -0,0 +1,47 @@
.TH MAN 1
.SH NAME
man - display manual pages
.SH SYNOPSIS
.BI man topic
.BI man section topic
.SH DESCRIPTION
The man command displays manual pages from the ramdisk in a
fullscreen pager. Pages are stored as plain text files with
simple formatting directives.
If no section is specified, sections 1, 2, and 3 are searched
in order. If a section number is given, only that section is
checked.
.SH KEY BINDINGS
.B Navigation
j, Down Arrow Scroll down one line
k, Up Arrow Scroll up one line
Space, Page Down Scroll down one page
b, Page Up Scroll up one page
g, Home Go to top
G, End Go to bottom
q Quit
.SH SECTIONS
1 User commands (shell built-ins)
2 System calls (kernel interface)
3 Library functions (userspace libraries)
.SH FILES
Man pages are stored on the ramdisk at:
0:/man/<topic>.<section>
For example, man intro reads 0:/man/intro.1
.SH EXAMPLES
man intro View the introduction
man 2 syscalls View syscall overview (section 2)
man malloc View malloc documentation
.SH SEE ALSO
intro(1), shell(1), syscalls(2)
+54
View File
@@ -0,0 +1,54 @@
.TH SHELL 1
.SH NAME
shell - ZenithOS interactive command shell
.SH DESCRIPTION
The ZenithOS shell is a simple command interpreter that runs as
the first userspace process. It provides basic file inspection,
process management, networking, and documentation access.
.SH COMMANDS
.SS help
Display a list of available commands.
.SS info
Show the OS name, version, and syscall API version number.
.SS man <topic>
Open a manual page in the fullscreen pager. See man(1).
.SS ls
List all files on the ramdisk (drive 0:/).
.SS cat <file>
Print the contents of a ramdisk file to the terminal.
Example: cat hello.elf
.SS run <file>
Spawn a new process from an ELF binary on the ramdisk and wait
for it to exit. The shell blocks until the child process
terminates.
Example: run hello.elf
.SS ping <ip>
Send 4 ICMP echo requests to the given IP address and display
round-trip times. Timeout is 3 seconds per request.
Example: ping 10.0.2.2
.SS uptime
Display the system uptime in minutes, seconds, and milliseconds.
.SS clear
Clear the terminal screen.
.SS exit
Terminate the shell process.
.SH INPUT
The shell reads input character by character using SYS_GETCHAR.
Backspace is supported. Lines are limited to 255 characters.
There is no command history or tab completion.
.SH SEE ALSO
man(1), intro(1), syscalls(2)
+69
View File
@@ -0,0 +1,69 @@
.TH SPAWN 2
.SH NAME
spawn, waitpid - create and wait for processes
.SH SYNOPSIS
.BI int zenith::spawn(const char* path, const char* args = nullptr);
.BI void zenith::waitpid(int pid);
.BI int zenith::getargs(char* buf, uint64_t maxLen);
.SH DESCRIPTION
.SS spawn
Loads the ELF64 binary at the given VFS path and creates a new
process. The path must include the drive prefix, for example:
int pid = zenith::spawn("0:/hello.elf");
An optional second argument passes a string to the child:
int pid = zenith::spawn("0:/man.elf", "intro");
The new process gets its own PML4 page table, a 16 KiB stack
(at 0x7FFFFEF000-0x7FFFFFF000), and begins executing at the
ELF entry point (_start).
Returns the new process's PID on success, or -1 on failure.
Failure occurs when there are no free process slots (max 16),
the file cannot be found, or the ELF is invalid.
.SS waitpid
Blocks the calling process until the process with the given PID
has exited. Internally, this yields the CPU in a loop:
zenith::waitpid(pid);
This is how the shell implements foreground process execution --
it spawns a child and waits for it to complete before showing
the next prompt.
.SH EXAMPLES
Spawn a program and wait for it:
int pid = zenith::spawn("0:/hello.elf");
if (pid < 0) {
zenith::print("spawn failed\n");
} else {
zenith::waitpid(pid);
zenith::print("child exited\n");
}
.SS getargs
Copies the argument string into buf (up to maxLen bytes, always
null-terminated). Returns the number of characters copied, or
-1 on error.
char args[256];
zenith::getargs(args, sizeof(args));
The argument string is set by the parent when calling spawn().
If no arguments were provided, the buffer will be empty.
.SH NOTES
The _start() entry point receives no argc/argv. Use getargs()
to retrieve the argument string passed by the parent process.
Process exit codes are not yet collected by waitpid.
.SH SEE ALSO
syscalls(2), file(2)
+137
View File
@@ -0,0 +1,137 @@
.TH SYSCALLS 2
.SH NAME
syscalls - overview of ZenithOS system calls
.SH DESCRIPTION
ZenithOS provides 26 system calls (numbers 0-25) for userspace
programs. Syscalls use the x86-64 SYSCALL instruction with the
following register convention:
RAX Syscall number (in) / return value (out)
RDI Argument 1
RSI Argument 2
RDX Argument 3
R10 Argument 4
R8 Argument 5
R9 Argument 6
Include <zenith/syscall.h> for typed wrappers in the zenith::
namespace.
.SH PROCESS MANAGEMENT
.B SYS_EXIT (0)
Terminate the calling process.
void zenith::exit(int code = 0);
.B SYS_YIELD (1)
Yield the remainder of the time slice.
void zenith::yield();
.B SYS_SLEEP_MS (2)
Sleep for at least the given number of milliseconds.
void zenith::sleep_ms(uint64_t ms);
.B SYS_GETPID (3)
Return the PID of the calling process.
int zenith::getpid();
.B SYS_SPAWN (20)
Spawn a new process from an ELF binary on the VFS.
int zenith::spawn(const char* path, const char* args = nullptr);
.B SYS_WAITPID (23)
Block until the given process has exited.
void zenith::waitpid(int pid);
.SH CONSOLE I/O
.B SYS_PRINT (4)
Write a null-terminated string to the terminal.
void zenith::print(const char* text);
.B SYS_PUTCHAR (5)
Write a single character to the terminal.
void zenith::putchar(char c);
.SH FILE I/O
.B SYS_OPEN (6)
Open a file. Returns a handle or negative on error.
int zenith::open(const char* path);
.B SYS_READ (7)
Read bytes from a file at a given offset.
int zenith::read(int h, uint8_t* buf, uint64_t off, uint64_t sz);
.B SYS_GETSIZE (8)
Get the size of an open file in bytes.
uint64_t zenith::getsize(int handle);
.B SYS_CLOSE (9)
Close a file handle.
void zenith::close(int handle);
.B SYS_READDIR (10)
List directory entries (max 64 per call).
int zenith::readdir(const char* path, const char** names, int max);
.SH MEMORY
.B SYS_ALLOC (11)
Map zeroed pages into the process address space.
void* zenith::alloc(uint64_t size);
.B SYS_FREE (12)
Reserved (currently a no-op).
void zenith::free(void* ptr);
.SH TIMEKEEPING
.B SYS_GETTICKS (13)
Get APIC timer ticks since boot.
uint64_t zenith::get_ticks();
.B SYS_GETMILLISECONDS (14)
Get milliseconds elapsed since boot.
uint64_t zenith::get_milliseconds();
.SH SYSTEM
.B SYS_GETINFO (15)
Get OS name, version, and configuration.
void zenith::get_info(Zenith::SysInfo* info);
.SH KEYBOARD
.B SYS_ISKEYAVAILABLE (16)
Check if a key event is pending (non-blocking).
bool zenith::is_key_available();
.B SYS_GETKEY (17)
Get the next key event (press or release).
void zenith::getkey(Zenith::KeyEvent* out);
.B SYS_GETCHAR (18)
Block until a printable character is typed.
char zenith::getchar();
.SH NETWORKING
.B SYS_PING (19)
Send an ICMP echo request and wait for reply.
int32_t zenith::ping(uint32_t ip, uint32_t timeoutMs);
.SH FRAMEBUFFER
.B SYS_FBINFO (21)
Get framebuffer dimensions and format.
void zenith::fb_info(Zenith::FbInfo* info);
.B SYS_FBMAP (22)
Map the framebuffer into process memory at 0x50000000.
void* zenith::fb_map();
.SH TERMINAL
.B SYS_TERMSIZE (24)
Get terminal dimensions (columns and rows).
void zenith::termsize(int* cols, int* rows);
.SH ARGUMENTS
.B SYS_GETARGS (25)
Get the argument string passed to this process at spawn time.
int zenith::getargs(char* buf, uint64_t maxLen);
.SH SEE ALSO
spawn(2), file(2), framebuffer(2), malloc(3)