An educational x86_64 operating system built from scratch.
- Custom Two-Stage Bootloader (no GRUB dependency)
- Stage 1: 512-byte MBR that loads Stage 2
- Stage 2: Enables A20, detects memory via E820, transitions to 64-bit long mode
- 64-bit Kernel: Written in C with x86_64 assembly
- Higher-Half Kernel: Runs at virtual address
0xFFFFFFFF80000000 - VGA Text Mode: 80x25 text output with 16 colors and
kprintf()
- Physical Memory Manager (PMM): Bitmap-based page allocator
- Virtual Memory Manager (VMM): 4-level paging (PML4), higher-half kernel mapping
- Kernel Heap:
kmalloc()/kfree()with block coalescing
- Interrupt Descriptor Table (IDT): 256 64-bit interrupt gates
- GDT with TSS: Task State Segment with IST1 for double fault protection
- Exception Handlers: Divide error, invalid opcode, double fault, GPF, page fault
- 8259A PIC Driver: Remaps IRQs 0-15 to vectors 32-47, spurious IRQ handling
- PIT Timer: 100Hz system clock (10ms resolution)
- PS/2 Keyboard Driver: Scancode set 1, circular input buffer, modifier key tracking
- Process Control Block (PCB): Full process state tracking (PID, state, stack, scheduling info)
- Process States: UNUSED, READY, RUNNING, BLOCKED, TERMINATED
- Kernel Stack: 8KB per-process kernel stack with 16-byte alignment
- Context Switching: Assembly-based register save/restore with TSS.RSP0 updates
- Round-Robin Scheduler: Preemptive scheduling with 100ms time slices
- Process API:
process_create(),process_exit(),process_yield(),process_block()/unblock() - Timer Integration: PIT IRQ0 triggers scheduler tick for preemption
- SYSCALL/SYSRET: Fast system call mechanism via x86_64 MSRs (STAR, LSTAR, SFMASK)
- System Calls: 6 core syscalls (exit, write, read, yield, getpid, sleep)
- User Processes: Separate address space per process via PML4 page tables
- User Stack: 64KB per-process stack at
0x7FFFFFFFE000(grows down) - User Code: Loaded at
0x400000with read-only permissions - Ring 3 Execution: User mode entry via IRETQ with proper GDT segments
- User Library: Minimal libc with syscall wrappers (
puts(),print_int(), etc.)
- Virtual File System (VFS): Abstraction layer for filesystem operations
- RAMFS: In-memory filesystem (4MB capacity, 256 max files)
- Superblock, inode table, data blocks
- Direct block allocation (12 blocks per file, 48KB max)
- Directory support with
.and..entries
- File Descriptors: Per-process FD table (16 per process)
- Path Resolution: Absolute and relative path handling with normalization
- Interactive Shell: Command-line interface with 8 built-in commands
- File Syscalls: open, close, lseek, stat, fstat, readdir, getcwd, chdir
- Cross-compiler:
x86_64-elf-gcc,x86_64-elf-ld - Assembler: NASM
- Emulator: QEMU (
qemu-system-x86_64)
# Build the OS image
make
# Run in QEMU
make run
# Debug with GDB
make debug
# Run with QEMU monitor
make monitor
# Clean build artifacts
make clean
# Show build configuration
make info
# Debug builds (verbose logging)
make DEBUG=1 # Enable all debug output
make DEBUG_SCHED=1 # Scheduler debug only
make DEBUG_VMM=1 # VMM debug only
make DEBUG_USER=1 # User mode debug onlychanux/
├── boot/
│ ├── stage1/mbr.asm # Stage 1 MBR bootloader (512 bytes)
│ ├── stage2/loader.asm # Stage 2 bootloader (A20, E820, long mode)
│ └── include/boot.inc # Shared boot constants
├── kernel/
│ ├── arch/x86_64/
│ │ ├── boot.asm # Kernel entry point
│ │ ├── gdt.c # GDT with TSS and user segments
│ │ ├── idt.asm # ISR/IRQ stubs, IDT loading
│ │ ├── context.asm # Context switch assembly
│ │ ├── syscall.asm # SYSCALL/SYSRET entry point
│ │ └── user_entry.asm # User mode entry (IRETQ)
│ ├── interrupts/
│ │ ├── idt.c # IDT initialization
│ │ ├── isr.c # Exception handlers
│ │ └── irq.c # Hardware IRQ dispatcher
│ ├── drivers/
│ │ ├── vga/vga.c # VGA text mode driver
│ │ ├── pic/pic.c # 8259A PIC driver
│ │ ├── pit/pit.c # 8254 PIT timer
│ │ └── keyboard/keyboard.c # PS/2 keyboard driver
│ ├── mm/
│ │ ├── pmm.c # Physical memory manager
│ │ ├── vmm.c # Virtual memory manager
│ │ └── heap.c # Kernel heap allocator
│ ├── proc/
│ │ ├── process.c # Process management (PCB, create/exit)
│ │ └── sched.c # Round-robin scheduler
│ ├── fs/ # File system
│ │ ├── vfs.c # Virtual File System layer
│ │ ├── ramfs.c # RAM filesystem implementation
│ │ ├── file.c # File descriptor management
│ │ └── path.c # Path utilities
│ ├── syscall/
│ │ ├── syscall.c # System call dispatcher
│ │ ├── sys_process.c # Process control syscalls
│ │ ├── sys_io.c # I/O syscalls (read/write)
│ │ └── sys_fs.c # File system syscalls
│ ├── user/
│ │ └── user_process.c # User process creation
│ ├── lib/
│ │ └── string.c # String utilities (memset, memcpy, etc.)
│ ├── include/ # Kernel headers
│ │ └── fs/ # VFS, RAMFS, file headers
│ └── kernel.c # Main kernel entry
├── user/ # User-space programs
│ ├── include/syscall.h # User syscall API
│ ├── lib/
│ │ ├── syscall.asm # Raw SYSCALL invocation
│ │ └── libc.c # Minimal C library
│ ├── init/
│ │ ├── start.asm # User program entry point
│ │ └── init.c # First user program (demo)
│ ├── shell/ # Interactive shell
│ │ ├── start.asm # Shell entry point
│ │ └── shell.c # Shell implementation
│ └── linker.ld # User program linker script
├── scripts/
│ └── linker.ld # Kernel linker script
└── Makefile # Build system
- Phase 1: Bootloader and minimal kernel
- Phase 2: Memory management (PMM, VMM, heap)
- Phase 3: Interrupts and I/O (IDT, GDT with TSS, PIC, PIT, keyboard)
- Phase 4: Process management (PCB, round-robin scheduler, context switching)
- Phase 5: User mode and system calls (SYSCALL/SYSRET, 6 syscalls, user processes)
- Phase 6: File system and shell (VFS, RAMFS, interactive shell)
- Phase 7: Extended filesystem (file deletion, mkdir, permissions)
BIOS → MBR (Stage 1) → Stage 2 → Protected Mode → Long Mode → kernel_main()
512 bytes 16KB 32-bit 64-bit
kernel_main():
└→ VGA init → Memory init → GDT/TSS → IDT → PIC/PIT → SYSCALL init → RAMFS init → Shell load → sched_start()
(PMM/VMM/Heap) (MSRs) (create /bin) (from RAMFS) (never returns)
Timer Interrupt (IRQ0, 100Hz)
│
▼
sched_tick()
│
├── Decrement time_slice
│
└── time_slice == 0?
│
▼
schedule()
│
├── Pick next from run queue (round-robin)
├── Update TSS.RSP0 for new process
└── context_switch() → Switch stacks → New process runs
Virtual Address Space:
User Space (Ring 3):
0x0000000000400000 User code (USER_CODE_BASE)
0x00007FFFFFFFE000 User stack top (64KB, grows down)
Kernel Space (Ring 0, Higher-Half):
0xFFFFFFFF80000000 - 0xFFFFFFFF80FFFFFF Kernel code/data (16MB)
0xFFFFFFFF81000000 - 0xFFFFFFFF81FFFFFF Kernel heap (16MB)
Physical Memory:
0x00000000 - 0x000FFFFF Real mode area (1MB)
0x00100000 - 0x001FFFFF Kernel physical location (1MB+)
| Number | Name | Signature |
|---|---|---|
| 0 | exit | void exit(int code) |
| 1 | write | ssize_t write(int fd, const void* buf, len) |
| 2 | read | ssize_t read(int fd, void* buf, len) |
| 3 | yield | int yield(void) |
| 4 | getpid | pid_t getpid(void) |
| 5 | sleep | int sleep(uint64_t ms) |
| 6 | open | int open(const char* path, int flags) |
| 7 | close | int close(int fd) |
| 8 | lseek | off_t lseek(int fd, off_t offset, int whence) |
| 9 | stat | int stat(const char* path, struct stat* buf) |
| 10 | fstat | int fstat(int fd, struct stat* buf) |
| 11 | readdir | int readdir(int fd, struct dirent* ent, int idx) |
| 12 | getcwd | int getcwd(char* buf, size_t size) |
| 13 | chdir | int chdir(const char* path) |
Calling Convention: RAX=syscall#, RDI/RSI/RDX/R10/R8/R9=args
Return Value: RAX (negative = error)
| Command | Description |
|---|---|
help |
List available commands |
echo [args] |
Print arguments to stdout |
cat <file> |
Display file contents |
ls [dir] |
List directory contents |
pwd |
Print working directory |
cd <dir> |
Change current directory |
clear |
Clear the screen |
exit |
Exit shell (halt system) |
Vectors 0-31: CPU Exceptions (Divide error, Page fault, etc.)
Vectors 32-47: Hardware IRQs (PIC remapped)
IRQ0 (32): PIT Timer (100Hz)
IRQ1 (33): PS/2 Keyboard
After boot, the kernel:
- Initializes VGA driver and displays banner
- Detects and displays memory map from E820
- Initializes PMM, VMM, and kernel heap
- Loads GDT with TSS and user segments (Ring 0 + Ring 3)
- Sets up IDT with exception handlers
- Remaps PIC and enables timer/keyboard IRQs
- Initializes SYSCALL/SYSRET mechanism (MSR configuration)
- Initializes RAMFS and creates filesystem structure
- Creates
/bindirectory and demo files (/hello.txt,/README) - Loads interactive shell from embedded binary
- Starts round-robin scheduler (preemptive multitasking)
The OS boots into an interactive shell where users can navigate the filesystem:
================================================================================
Welcome to Chanux OS!
A simple educational OS kernel
================================================================================
chanux:/$ help
Available commands:
help - Show this help message
echo - Print arguments
cat - Display file contents
ls - List directory
pwd - Print working directory
cd - Change directory
clear - Clear screen
exit - Exit shell
chanux:/$ ls
.
..
bin
hello.txt
README
chanux:/$ cat hello.txt
Hello from Chanux OS!
chanux:/$ cd bin
chanux:/bin$ pwd
/bin
chanux:/bin$
The shell demonstrates:
- File system navigation (ls, cd, pwd)
- File reading (cat)
- Path resolution (absolute and relative paths)
- Per-process current working directory
- All 14 system calls working together
RAMFS Layout (4MB total):
Block 0: Superblock (magic, counts, allocation bitmaps)
Blocks 1-8: Inode table (256 inodes × 128 bytes each)
Blocks 9-1023: Data blocks (4KB each, ~4MB storage)
Inode Structure (128 bytes):
├── Type (file=1, directory=2)
├── Size, permissions (rwx), link count
├── Timestamps (created, modified, accessed)
├── 12 direct block pointers (48KB max per file)
└── Owner UID/GID
VFS Layer:
├── Vnode table (cached inodes with reference counting)
├── File table (open files with position tracking)
└── Per-process FD table (16 descriptors per process)
Educational project - feel free to learn from and modify this code.