TinyCoreOS¶
Applies to version: TinyCoreOS Pre-Alpha 5 2.0
A graphical operating system built from scratch in C++20 and x86-64 assembly.
Current milestone: M44 - Preferences System
Next: M45 - Shell Window (CLI)
Prebuilt images for VMWare: https://tinycoreos.com/build/.
Video: Development trailers and demo clips.
Trailers: YouTube Trailer 1 | YouTube Trailer 2 | YouTube Trailer 3
About me: Adrian Pucek. Contact: [enable JavaScript]
Index¶
Build and run
Daily usage
- CoreShell
- Not Commander (
nc) - Networking
- HTTP Server (
httpd) - FTP Server (
ftpd) - Disk workflow (
format/mount)
Reference
- Project layout
- Architecture overview
- Supported drivers
- Toolchain
- Milestones
- PHP migration plan
- Roadmap
- Historical notes
First steps (5 minutes)¶
After make run and boot/login in TinyCoreOS:
help
ipconfig
resolve github.com
ls /disk
date
time
nc
Tip: if DNS/network is not up yet, run netinit and retry resolve.
Latest CoreDesk updates¶
- Screen menu TinyCoreOS → Run Program now opens an in-window run dialog (no external
run_dialog.elfdependency). - The run dialog executes shell commands with arguments (PATH lookup +
execve), e.g.notepad.elf /tmp/readme.txt. - Power-style actions now use confirmation dialogs: Quit CoreDesk, Restart, Shut Down.
- In Apps → Tools, a new Terminal entry launches
sh(fallback:busybox sh). - Apps → System no longer contains
Power(power actions remain in the TinyCoreOS menu).
Latest CoreShell updates¶
- USB stage-2 detection (wait + probe + MSC/HID) now runs asynchronously after
STI; boot reacheslogin:without waiting on the USB settle delay. - During login, USB stage-2 logs are batched and printed as one block, then
login:/Password:is repainted once (nologin: [INFO] ...interleave). - Login caret repaint was hardened for async boot logs to avoid stray
_artifacts at line start. find <path> [name]now supports wildcard match (*,?) inname; when no wildcard is present it keeps substring matching.copy/cpnow support wildcard source expansion (*,?) in the last path component.greppath operands now support wildcards; added-s(sort output) and-u(unique output).catnow supports path wildcards plus-s/-uline output options.tailnow supports wildcard paths in non-follow mode;tail -frequires exactly one resolved file.zipinput arguments now support file glob expansion (*,?).rmnow supports wildcard operands (*,?) with existing-r/-f/-vbehavior.catremains interruptible withCtrl+C;tail -fremains a live append view untilCtrl+C.verprints product codename + version from release macros (TCOS_PRODUCT_CODENAME,TCOS_PRODUCT_VERSION).
Milestones¶
| ID | Name | Status | Key deliverables |
|---|---|---|---|
| M0 | Build Environment | ✅ Done | MSYS2 + x86_64-elf toolchain, Limine, QEMU |
| M1 | Bootloader | ✅ Done | 32-bit → 64-bit long mode, GDT, paging, SSE |
| M2 | Kernel Basics | ✅ Done | IDT, PIC, PIT (100 Hz), PS/2 keyboard, VGA console |
| M3 | Memory Manager | ✅ Done | PMM (bitmap + alloc_n_frames), kernel heap, operator new/delete |
| M4 | Virtual Memory + kstd | ✅ Done | VMM (4-level page tables), #PF handler, kstd::Vector<T> |
| M5 | Preemptive Scheduler | ✅ Done | Kernel threads, round-robin, switch_context in ASM |
| M6 | User Space + Syscalls | ✅ Done | Ring 3, TSS, SYSCALL/SYSRET, user-space task |
| M7a | VFS + RamFS | ✅ Done | VFS abstraction layer, RamFS backend, 8 syscalls, FdTable per task |
| M7b | ATA driver + TinyFS | ✅ Done | ATA PIO driver, custom TinyFS, dual-disk support, CoreShell first boot |
| M8 | Graphical Framebuffer | ✅ Done | VESA/VBE framebuffer, embedded font, desktop chrome, dual-mode console |
| M8+ | Extended Shell & Services | ✅ Done | TCP stack, HTTP/1.1 server (v2), background jobs, startup scripts, security P1-P4 |
| M9 | CodeDesk (Window Manager) | ✅ Done | PS/2 mouse, double buffering, overlapping windows, compositor, drag/focus/resize |
| M10 | ELF Loader + Process Isolation | ✅ Done | ELF64 loader, 2MiB→4KiB page splitting, exec shell command, Linux ABI syscalls |
| M11 | POSIX File I/O Syscalls | ✅ Done | open/close/read/write/stat/lseek/getdents64, openat, linux ABI structs |
| M12 | Process Environment | ✅ Done | per-task CWD, getcwd/chdir, uname, readlink, openat with relative paths |
| M13 | Network Syscalls | ✅ Done | socket/bind/listen/accept/connect/sendto/recvfrom, active TCP client, UDP |
| M14 | Threading | ✅ Done | clone(56) creates a thread with its own stack, futex(202) FUTEX_WAIT/WAKE, TLS via FS_BASE (CLONE_SETTLS) |
| M15 | Signals | ✅ Done | rt_sigaction(13), rt_sigprocmask(14), rt_sigreturn(15), kill(62), sigframe + trampoline |
| M16 | I/O Multiplexing | ✅ Done | select(23), poll(7), pipe(22)/pipe2(293), O_NONBLOCK, fcntl F_GETFL/F_SETFL |
| M17 | Device Nodes | ✅ Done | /dev/null, /dev/zero, /dev/tty, /dev/fb0 (mmap), /proc/meminfo |
| M18 | First Real Application | ✅ Done | Graceful ring-3 #PF/#UD (task kill instead of panic); shell PATH search; user-space ELF framework |
| M19 | CodeDesk Syscall Bridge | ✅ Done | SYS_WM_CREATE/DESTROY/MAP_FB/FLUSH/POLL_EVENT/SET_TITLE (500-505); PMM-backed window pixel buffers; libtwm.h header-only API |
| M20 | Graphical File Manager | ✅ Done | musl ELF: folder browser in a CodeDesk window, mouse + keyboard navigation, icons, scrollbar |
| M21 | CoreShell & F-key UI | ✅ Done | Dynamic F-key bar (Files/Net/Monitor sub-menus), kill, copy, CPU idle hinting, CodeDesk dirty flag, embedded README help |
| M22 | Userspace Runtime Fixes | ✅ Done | Relative-path CWD resolution for all path syscalls, LinuxDirent64 packed ABI fix, FSGSBASE CPUID guard, syscall arg ABI fix, compositor yield, CWD inheritance |
| M23 | TTF Font System | ✅ Done | stb_truetype renderer, antialiased glyph cache, runtime font switching (font command), fonts/ directory, make fonts-to-fat |
| M24 | Process Management | ✅ Done | Per-process CR3 (isolated address spaces), fork(), execve(), waitpid(), getppid, setpgid/getpgid, setsid/getsid, per-task brk/mmap arenas |
| M25 | TTY termios Layer | ✅ Done | struct Termios (Linux ABI), ioctl dispatch: TCGETS/TCSETS/TCSETSW/TCSETSF, TIOCGWINSZ/TIOCSWINSZ, TIOCGPGRP/TIOCSPGRP; canonical + raw mode reads; isatty() returns 1 |
| M26 | File System Group | ✅ Done | Real symlinks (S_IFLNK) + hard links in VFS/RamFS with resolve_no_follow(); extended /proc (cpuinfo, version, uptime, stat, self/status, /proc/self → symlink); /sys skeleton; /run; SYS_SYMLINK/LINK/READLINK; SYS_FLOCK/MOUNT/UMOUNT2; inotify stubs; SYS_FCNTL F_GETLK/SETLK |
| M27 | Missing Syscalls | ✅ Done | nanosleep (yield-based), mremap (grow-in-place), clock_gettime (real PIT ticks), file-backed mmap, setuid/setgid/setfsuid (VFS uid update), getrusage (zero struct), times (returns ticks), getresuid/getresgid |
| M28 | Init & User-space | ✅ Done | PID 1 init task (zombie-reaper + orphan adoption via exit_current), full FHS layout (/etc /home /lib /root /usr /var), Linux-format /etc/passwd /etc/shadow /etc/group /etc/hostname /etc/hosts /etc/os-release, make busybox target |
| M29 | Hardware Drivers | ✅ Done | AHCI/SATA (DMA, PCI BAR5 MMIO, IDENTIFY, LBA48), NVMe (Admin + I/O queues, Identify NS), LAPIC + I/O APIC (ACPI MADT), USB HID boot-protocol keyboard, blkdev expanded to 16 devices |
| M30 | Network & TCP/IP | ✅ Done | DHCP client (DISCOVER→OFFER→REQUEST→ACK), software loopback (127.0.0.1), /etc/hosts lookup before DNS, AF_UNIX domain sockets (bind/connect/accept + ring-buffer pairs), dhcp shell command, netinit auto-DHCP |
| M31 | Shell I/O Plumbing | ✅ Done | dup/dup2/dup3 + File::refcount, pread64/pwrite64, truncate/ftruncate (ramfs grow/shrink), rename/renameat/renameat2, getpgrp, FD_MAX→64, stdin/stdout respect dup2 redirections |
| M32 | PTY | ✅ Done | /dev/ptmx (open allocates master), /dev/pts/N (slave), bidirectional 4 KiB ring buffers, N_TTY line discipline (canonical/raw, echo, ICRNL, ONLCR), TIOCGPTN/TIOCSPTLCK/TIOCSCTTY ioctls, TCGETS/TCSETS per-PTY termios |
| M33 | Busybox on Disk | ✅ Done | Full argc/argv/envp in ELF loader (Linux ABI stack layout); SYS_EXECVE parses user-space argv[]; bb-install (TFTP + chmod 755 + ~60 symlinks); PATH extended; argv[0] override for busybox dispatch; interactive sh prompt: TIOCGPGRP returns caller pgid, SIGTTIN/SIGTTOU ignored, fd 0/1/2 pre-installed as /dev/tty, F_DUPFD_CLOEXEC respects min-fd; make run guards build/busybox; Python no longer required; fork fix enter_userspace_fork restores RBX/RBP/R12-R15; writev(20) + readv(19) + sched_yield(24); ANSI escape parser + \r in console; getdents64 debug removed |
| M34 | CoreShell Enhancements | ✅ Done | Stream redirection > / >> (console hook, VFS write); pipe operator \| (multi-stage, double-buffer, grep/cat/tail pipe-aware); built-in grep (-i -n -r -l); kernel logging (klog); env-var help; help coloring |
| M35 | CoreShell CLI Completeness | ✅ Done | Built-in rm [-r][-f], rmdir, touch, echo [-n], mv (alias rename), cp (alias copy), ln [-s] (alias link/symlink), uptime, source/., alias/unalias; alias expansion in dispatch |
| M36 | SSH Client & Server | ✅ Done | sshd (port 22, worker pool via tcp_srv, up to 5 concurrent sessions) + ssh [user@]host [cmd] incl. ssh localhost; crypto: curve25519-sha256 KEX, chacha20-poly1305@openssh.com cipher, ssh-ed25519 host key (standard Ed25519 with SHA-512 via Monocypher building blocks + own SHA-256/SHA-512); password auth aligned with passwd/shadow behavior |
| M37 | AC97 Audio | ✅ Done | Intel ICH AC97 PCI driver; DMA ring buffer (32 × 4 KiB, 128 KiB); /dev/dsp; beep [freq] [ms], play <wav/pcm>, volume [0-100]; -device AC97 QEMU flag |
| M38 | NC File Manager | ✅ Done | Not Commander dual-panel text file manager (nc): copy/move/rename/mkdir/delete (recursive + marked), sort by name/date/size (s key), Ctrl+O shell-suspend/resume mini-shell, Shortcuts menu (This PC / User / Desktop / Docs / Music / Downloads / Pictures / Videos / Root), FTP client (ftpc), Insert=mark, Del=delete, soft cursor in input dialogs, F2 refresh, bare-name rename-in-place, exit sets shell CWD, pre-login F-key bar hidden |
| M39 | Widget Toolkit (Intuition) | ✅ Done | libui widgets in production apps: buttons, labels, text/input patterns, list/grid views, dialog-style layouts used across CoreDesk/Control Panel |
| M40 | Screen & Palette System | ✅ Done | Backdrop/wallpaper handling, palette reload, runtime cursor switching (arrow/wait), runtime mode switch (twm_set_mode) from Control Panel |
| M41 | Icon System | ✅ Done | Themed desktop icons loaded from disk (PNG/BMP), category/fallback resolution, transparent icon rendering, robust icon metadata parsing |
| M42 | Workbench Desktop | ✅ Done | CoreDesk desktop with icon grid, drawer launching, taskbar/sysbar stats, context menus, My stuff dynamic submenu, minimized redraw path |
| M43 | Menu System | ✅ Done | Global top screen menu with multi-level submenus (up to deep nesting), disabled/check/radio items, Win/Ctrl+Esc activation, mouse+keyboard navigation |
| M44 | Preferences System | ✅ Done | Unified in-window Control Panel applets (ScreenMode/Palette/Font/Sound/Input/Time/Startup), persistent prefs in /disk/disk0/prefs/, Default Apps mapping UI |
| M45 | Shell Window (CLI) | 🔲 Plan | PTY terminal window, scrollback, mouse copy/paste, dynamic resizing |
| M46 | Dock / Application Bar | 🔲 Plan | Application dock (AmiDock), icons with tooltips, auto-hide |
| M47 | Multimedia Viewer | 🔲 Plan | MultiView (PNG/BMP/WAV), stb_image, zoom, waveform audio preview |
| M48 | GUI Text Editor | 🔲 Plan | Syntax highlighting, undo/redo, Find & Replace, tabs |
| M49 | Visual Theming | 🔲 Plan | Window skins (Classic/AmigaOS4/Dark), animations, wallpapers, .theme format |
| M50 | Notifications & Clipboard | 🔲 Plan | Notification banner, system clipboard, SYS_CLIPBOARD_COPY/PASTE |
Prerequisites¶
| Tool | Version | Purpose |
|---|---|---|
| MSYS2 | latest | Shell environment on Windows |
| x86_64-elf GCC toolchain | 13.x | Freestanding cross-compiler |
| NASM | 2.x | Assembler |
| QEMU | latest | Emulator (qemu-system-x86_64) |
| xorriso | latest | ISO creation |
| Git | 2.x | Fetching Limine bootloader |
The toolchain is installed natively under MSYS2 - no Docker required.
Seemsys2-env.shfor the one-time setup script.
Quick start¶
# 1. Enter MSYS2 and load the toolchain path (once per session)
source ./msys2-env.sh
# 2. Download Limine bootloader binaries (once)
make limine-get
# 3. Download Monocypher crypto library (once, required for SSH)
make monocypher-get
# 4. Build kernel ELF + bootable ISO
make
# 5. Run in QEMU (disk images created automatically)
make run
VMware Workstation (recommended VM profile)¶
TinyCoreOS works in VMware Workstation when the VM uses devices covered by current drivers.
- Firmware: BIOS or UEFI
- Disk controller: SATA (AHCI) or NVMe
- Network adapter: Intel E1000 / E1000E (recommended), or AMD PCnet
- Audio: HD Audio (Azalia) or AC97
- Graphics: VMware SVGA (framebuffer path works)
Notes:
- VMXNET3 is visible in lspci, but not yet supported by the network stack.
- Legacy SCSI controllers (BusLogic / LSI Logic Parallel) are not supported yet - use SATA/NVMe.
- VMware AHCI may expose many implemented-but-empty SATA ports; TinyCoreOS now suppresses those empty-port lines in boot output.
VMware debug logging setup (qemu-debug equivalent)¶
Use a virtual serial port mapped to a host file. This gives you a persistent boot/runtime log similar to QEMU -serial.
- Power off the VM.
- Open VM Settings -> Add... -> Serial Port.
- Select Output to file and choose a host path, e.g.
C:\temp\tinycoreos-vmware-serial.log. - Enable Connect at power on.
- Boot the VM and inspect the log file on the host.
Notes:
- TinyCoreOS writes boot/kernel logs to COM1, so this captures early boot too.
- VMware also writes hypervisor/device diagnostics to vmware.log in the VM directory.
- If the serial log file stays empty, verify COM1 is present and connected.
PowerShell live tail (host):
Get-Content "C:\temp\tinycoreos-vmware-serial.log" -Wait
Debug targets¶
make debug # QEMU paused, GDB on port 1234
make debug-boot # QEMU with -no-reboot -d int,cpu_reset
make serial # QEMU output to stdout, no window
Supported drivers¶
TinyCoreOS currently targets virtualized hardware profiles (QEMU / VirtualBox / VMware Workstation).
Network¶
- Intel E1000 family (
e1000):8086:100e,8086:100f,8086:10d3 - AMD PCnet (
pcnet):1022:2000,1022:2001 - DHCP, DNS, TCP/UDP stack, loopback (
127.0.0.1) are supported on top of these NICs.
Not supported yet:
- VMware VMXNET3 (15ad:07b0)
- VirtIO-Net
Storage¶
- ATA PIO (
ata) - AHCI SATA (
ahci) - NVMe (
nvme) - USB Mass Storage (SCSI transparent subset used by common flash drives)
Not supported yet:
- Legacy SCSI VM controllers (BusLogic / LSI Parallel)
- VirtIO-Blk / VirtIO-SCSI drivers
Audio¶
- Intel AC97 (
8086:2415) viaac97 - Intel HD Audio / Azalia (
hda)
Display and input¶
- Framebuffer console + desktop compositor path
- PS/2 keyboard and mouse
- USB keyboard (HID boot protocol)
- VMware SVGA works via framebuffer path (no dedicated SVGA II acceleration driver yet)
PCI ID -> driver module¶
| PCI ID | Device (short) | Driver module |
|---|---|---|
| 8086:100e | Intel 82540EM (E1000) | e1000 |
| 8086:100f | Intel 82545EM (E1000) | e1000 |
| 8086:10d3 | Intel 82574L (E1000E profile) | e1000 |
| 1022:2000 | AMD PCnet-FAST III | pcnet |
| 1022:2001 | AMD PCnet (variant) | pcnet |
| 8086:2922 | Intel ICH9 AHCI SATA | ahci |
| 8086:2415 | Intel ICH AC97 Audio | ac97 |
| 8086:2668 | Intel HD Audio (ICH6/ICH9) | hda |
| 15ad:07b0 | VMware VMXNET3 | not supported |
| 15ad:07c0 | VMware PVSCSI | not supported |
| 104b:1040 | BusLogic SCSI | not supported |
| 1000:0030 | LSI Logic Parallel SCSI | not supported |
Recommended VM profiles¶
- QEMU:
e1000+ AHCI/NVMe + AC97 or HDA - VirtualBox: Intel PRO/1000 (
e1000) or PCnet + AHCI + HDA/AC97 - VMware Workstation: E1000/E1000E + SATA/NVMe + HD Audio
SMP diagnostics (dev)¶
When smpap is enabled in iso/limine.cfg, use these runtime checks:
cat /proc/smp_sched
smpkick 5
smpkick ap 1 5
cat /proc/smp_sched
Interpretation:
- resched_ipi_sent should increase after smpkick.
- resched_ipi_hits / resched_ipi_hits_cpuN confirm IPI delivery/handling per CPU.
- schedule_attempts / schedule_switches track scheduler activity under SMP tests.
- syscall_bkl_taken / syscall_bkl_bypassed show how much syscall traffic still goes
through the global BKL versus lock-free whitelist paths.
- syscall_bkl_taken_* highlights the hottest still-locked syscall families
(read, ioctl, futex, openat, close, wm_poll_event, wm_flush,
write, wm_create/destroy/map_fb, clipboard, readv/writev,
wm_get_size/wm_set_cursor/wm_move, file metadata paths (open/stat/fstat/lstat/getdents/access),
plus memory/time/path buckets (brk/mmap/munmap/clock_gettime/getcwd),
and additional scheduler/signal-fd buckets (poll/select/fcntl/rt_sigprocmask/set_tid_addr/waitpid),
plus process/UI text buckets (execve/clone/fork/kill/wm_set_title/font_draw_str/font_draw_to/shellcore_exec),
plus fd/plumbing buckets (lseek/pread64/pwrite64/dup/dup2/dup3/pipe/pipe2),
plus socket/net buckets (socket/connect/accept/accept4/sendto/recvfrom/bind/listen/shutdown
and net_dns_resolve/net_tcp_connect/net_tcp_connected/net_tcp_send/net_tcp_recv/net_tcp_close/net_poll),
plus WM/fclip extension buckets (wm_set_style/wm_set_desktop/wm_get_desktop/wm_win_desktop/wm_win_sticky
wm_set_mode/wm_set_cursor_bmp/wm_cursor_frame/fclip_begin/fclip_add/fclip_count/fclip_op/fclip_get/fclip_clear/fclip_gen),
plus WM menu/popup buckets (wm_menu_set/wm_menu_poll/wm_popup_show/wm_popup_poll/wm_get_focused/wm_raise),
plus WM/control buckets (wm_list_windows/power_off/reboot),
and other).
- syscall_bkl_taken_read_stdin / syscall_bkl_taken_read_fd and
syscall_bkl_taken_write_console / syscall_bkl_taken_write_fd split
read/write into fallback TTY/console paths versus fd-backed file paths.
- syscall_bkl_taken_waitpid_nohang / syscall_bkl_taken_waitpid_blocking
split wait family pressure into polling (WNOHANG) versus blocking waits.
- syscall_bkl_taken_mmap_anon / syscall_bkl_taken_mmap_file /
syscall_bkl_taken_mmap_fb0 split mmap pressure into anonymous, generic
file-backed, and framebuffer (/dev/fb0) mappings.
- syscall_bypass_* fields split lock-free traffic by syscall family
(sched_yield, nanosleep, socket name queries, kill(0,0), plus hot read-only
paths: getpid, gettid, getppid, getpgrp, getuid, geteuid,
get_ticks, brk, mmap(MAP_ANONYMOUS), waitpid(WNOHANG), fd-backed read/write, access, close, open (safe read-only subset),
wm_get_screen_size, wm_set_cursor, wm_set_style, wm_win_sticky, wm_get_focused, wm_menu_poll, wm_popup_poll, wm_list_windows,
wm_flush, wm_poll_event,
getdents,
font_measure, font_draw_to, clipboard_count, clipboard_gen, and other).
Current SMP checkpoint (incremental, stable):
- AP bring-up (smpap) is stable for 2 CPU QEMU runs.
- Reschedule IPI (smpkick) is wired and observable via /proc/smp_sched.
- Syscall path uses a conservative, argument-gated BKL bypass policy.
- sched_yield(24) and nanosleep(35) are now lock-free in syscall entry
(scheduler still applies its own SMP safety path internally).
- wm_poll_event(504) is now lock-free at syscall entry, backed by dedicated
WM internal locking for wid/event-ring critical sections.
- WM bridge now has dedicated internal locking for wid-table and event-ring
critical sections (prep step for safely reducing BKL pressure on WM poll path).
- FdTable operations (alloc/get/release/replace) are now internally
protected by a per-table spinlock as groundwork for future VFS-path BKL reductions.
- vfs::File now includes a dedicated per-file spinlock and atomic refcount
helpers, with vfs::read/write/readdir/ftruncate serialized per open-file instance.
- vfs::pread/pwrite now provide positional I/O with lock-scoped offset restore,
and mmap file-backed copying uses the same per-file lock domain.
- read(0) keyboard fallback and write(1/2) direct-console fallback remain under
BKL, but fd-backed read/write paths now bypass BKL and are tracked via
syscall_bypass_read_fd / syscall_bypass_write_fd.
- waitpid(61) now bypasses BKL for WNOHANG probes only; blocking waits stay
under BKL and telemetry exposes both sides via
syscall_bkl_taken_waitpid_nohang / syscall_bkl_taken_waitpid_blocking
and syscall_bypass_waitpid_nohang.
- wm_set_cursor(520) now bypasses BKL and is tracked via
syscall_bypass_wm_set_cursor; cursor mode updates use atomic loads/stores
in WM to avoid races with compositor-side hover updates.
- wm_set_style(530) and wm_win_sticky(534) now bypass BKL, backed by
existing wid-table locking in WM and tracked via
syscall_bypass_wm_set_style / syscall_bypass_wm_win_sticky.
- brk(12) now bypasses BKL; heap growth still synchronizes through PMM/VMM
internal locks, and lock-free traffic is tracked via syscall_bypass_brk.
- mmap(9) now bypasses BKL for MAP_ANONYMOUS mappings; file-backed mmap
paths remain under BKL and are split into mmap_file / mmap_fb0 telemetry.
- A dedicated VFS gate lock now protects syscall-level VFS metadata paths
(open/close/stat/lstat/fstat/access/getdents/openat) and enables lock-free
getdents dispatch without relying on the global BKL.
- close(3) now runs lock-free at syscall entry, guarded by the same VFS gate
+ per-task FdTable locking.
- open(2) now bypasses BKL only for a safe subset (O_RDONLY without
O_CREAT/O_TRUNC), keeping mutating opens under BKL.
- New telemetry counters help guide the next whitelist steps with runtime data.
- syscall_bkl_taken_other is now partially decomposed into dedicated buckets
(brk, mmap, munmap, clock_gettime, getcwd, wm_move) to narrow the
next lock-free candidate with less guesswork.
- Decomposition was extended with poll/select/fcntl/rt_sigprocmask/set_tid_addr/waitpid
to identify high-frequency non-VFS pressure still hidden under other.
- Decomposition was further extended with execve/clone/fork/kill/wm_set_title/font_draw_str/font_draw_to/shellcore_exec
to expose process-launch/UI-rendering overhead currently folded into other.
- Decomposition now also covers lseek/pread64/pwrite64/dup/dup2/dup3/pipe/pipe2
to isolate file-offset/descriptor plumbing pressure from residual other.
- Decomposition now also covers socket/net syscall families
(socket/connect/accept/accept4/sendto/recvfrom/bind/listen/shutdown
and net_dns_resolve/net_tcp_connect/net_tcp_connected/net_tcp_send/net_tcp_recv/net_tcp_close/net_poll)
to isolate network-path pressure from residual other.
- Decomposition now also covers WM extension (530-537) and file-clipboard (550-556)
syscall families to isolate desktop-control/file-clipboard pressure from residual other.
- Clipboard history/file-clipboard paths now use dedicated internal locking in WM,
enabling safe lock-free bypass for read-only clipboard_count/clipboard_gen.
- font_draw_to(516) now bypasses BKL (pure draw into caller-provided pixel
buffer), with dedicated syscall_bypass_font_draw_to telemetry.
- wm_get_focused(542) now bypasses BKL (read-only focused-window query),
backed by WM wid-table locking and dedicated syscall_bypass_wm_get_focused
telemetry.
- wm_menu_poll(539) and wm_popup_poll(541) now bypass BKL; their pending-id
handoff paths use atomic set/poll in WM to avoid races with compositor updates.
- wm_list_windows(546) now bypasses BKL, backed by wid-table locking in
wm::list_windows_wid() and dedicated syscall_bypass_wm_list_windows telemetry.
- Decomposition now also covers WM menu/popup extension syscall families
(wm_menu_set/wm_menu_poll/wm_popup_show/wm_popup_poll/wm_get_focused/wm_raise)
to isolate remaining desktop-control pressure from residual other.
- Decomposition also tracks wm_list_windows(546) and power control syscalls
(power_off(544), reboot(545)) to reduce residual other in desktop shell runs.
Project layout¶
TinyCoreOS/
├── iso/
│ └── limine.cfg # Limine bootloader config (Multiboot2)
├── src/
│ ├── kernel/
│ │ ├── boot.asm # 32→64-bit long mode, SSE, identity-map 0-1 GiB + 3-4 GiB
│ │ ├── linker.ld # Kernel linked at 1 MiB, exports _kernel_end
│ │ ├── main.cpp # kernel_main: full init sequence + idle loop
│ │ ├── console.{hpp,cpp} # Dual-mode console: VGA text (fallback) + framebuffer
│ │ ├── panic.{hpp,cpp} # kernel::panic + PANIC / ASSERT macros
│ │ ├── new.cpp # operator new/delete -> heap::alloc/free
│ │ ├── multiboot2.h # Multiboot2 tag structures (incl. framebuffer tag)
│ │ ├── io.hpp # inb / outb port I/O
│ │ ├── gdt/ # 64-bit GDT + TSS descriptor, ltr
│ │ ├── idt/ # IDT, isr_stubs.asm, interrupt dispatch
│ │ ├── pic/ # 8259A PIC remap (IRQ0-15 -> vec 32-47)
│ │ ├── vfs/
│ │ │ ├── vfs.{hpp,cpp} # VFS abstraction (Inode+uid, File, Superblock, FdTable, chmod/chown/rename)
│ │ │ └── ramfs.{hpp,cpp} # RamFS backend (heap-linked lists, setattr)
│ │ ├── fs/
│ │ │ ├── tinyfs.{hpp,cpp}# TinyFS v4: 4KiB-block FS, dynamic layout, file-level dedupe, double indirect, mtime, version
│ │ │ └── fat32.{hpp,cpp} # FAT32 driver (LFN, mtime, file version counter via nt_res)
│ │ ├── syscall/
│ │ │ ├── syscall.{hpp,cpp} # MSR init, syscall_dispatch (SYS_EXIT..SYS_STAT)
│ │ │ └── syscall_entry.asm # SYSCALL/SYSRET entry point + enter_userspace
│ │ ├── userspace/
│ │ │ └── user_init.asm # Demo ring-3 program (position-independent)
│ │ ├── readme_data.asm # Embeds README.md as .rodata blob → /tmp/readme.md at boot
│ │ ├── drivers/
│ │ │ ├── ata.{hpp,cpp} # ATA PIO driver (28-bit LBA, primary bus, polling, 2 drives)
│ │ │ ├── fb.{hpp,cpp} # Linear framebuffer driver (put_pixel, fill_rect, scroll_region_up)
│ │ │ ├── timer.{hpp,cpp} # PIT 100 Hz, ticks(), calls sched::tick
│ │ │ ├── keyboard.{hpp,cpp} # PS/2 scancode -> ASCII, Page Up/Down, arrow keys, Tab, F-keys
│ │ │ └── ac97.{hpp,cpp} # Intel ICH AC97 audio: PCI scan, DMA ring, /dev/dsp, beep/play/volume
│ │ ├── gfx/
│ │ │ ├── font.{hpp,cpp} # Embedded 8×8→8×16 bitmap font (ASCII 0x20-0x7E)
│ │ │ ├── canvas.{hpp,cpp}# Desktop chrome: title bar, live sysinfo, dynamic F-key bar (set_fkey_bar/reset_fkey_bar)
│ │ │ └── wm.{hpp,cpp} # Window manager: compositor, drag/focus/resize, wid-based syscall API, dirty-flag rendering
│ │ ├── net/
│ │ │ ├── e1000.{hpp,cpp} # Intel 82540EM NIC driver (MMIO, TX/RX descriptor rings, polling)
│ │ │ ├── net.{hpp,cpp} # Network stack: Ethernet II, ARP, IPv4, ICMP, UDP, DNS, TCP
│ │ │ ├── dns.{hpp,cpp} # DNS A-record client (UDP port 53)
│ │ │ ├── httpd.{hpp,cpp} # HTTP/1.1 server (v2; static files, dir listing, runs as bg task)
│ │ │ ├── ftpd.{hpp,cpp} # FTP server (plaintext, PASV, ACL + auth backends)
│ │ │ └── ssh.{hpp,cpp} # SSH2 server + client (curve25519-sha256 + chacha20-poly1305 + ed25519)
│ │ ├── shell/
│ │ │ └── shell.{hpp,cpp} # Interactive kernel shell (70+ commands, history, tab completion,
│ │ │ # background jobs, startup scripts, users/groups, chmod/chown,
│ │ │ # aliases, stream redirection, pipes, NC file manager, dynamic F-key bar)
│ │ ├── zip/
│ │ │ └── zip.{hpp,cpp} # ZIP reader/writer (Store + Deflate via miniz_tinfl)
│ │ ├── mm/
│ │ │ ├── pmm.{hpp,cpp} # Physical Memory Manager (bitmap + alloc_n_frames)
│ │ │ ├── heap.{hpp,cpp} # Kernel heap (first-fit free list, realloc, C malloc/free wrappers)
│ │ │ └── vmm.{hpp,cpp} # Virtual Memory Manager (page-table walker, map/unmap/get_phys)
│ │ └── sched/
│ │ ├── task.hpp # struct Task (RSP, privilege, FdTable, ring-list)
│ │ ├── scheduler.{hpp,cpp} # Round-robin, create_task/create_user_task, yield, kill_task, hint_idle
│ │ └── context.asm # switch_context (save/restore 6 callee-saved regs)
│ ├── kstd/
│ │ ├── utility.hpp # kstd::move, kstd::forward
│ │ ├── placement_new.hpp # operator new(size_t, void*) for freestanding
│ │ ├── unique_ptr.hpp # kstd::unique_ptr<T>
│ │ ├── vector.hpp # kstd::Vector<T> - heap-backed dynamic array
│ │ └── cstring.hpp # strlen, strcmp, strncmp, strncpy, strrchr, memcpy, memset, memmove
│ └── vendor/
│ ├── miniz/
│ │ ├── miniz_tinfl.{h,c} # Deflate decompressor (richgel999/miniz)
│ │ ├── miniz_common.h
│ │ └── compat/ # Freestanding shim headers (stdlib, string, assert, …)
│ ├── stb/
│ │ ├── stb_truetype.h # stb_truetype (nothings.org) — TTF rasterizer
│ │ ├── stb_truetype_impl.c # Single-TU implementation (compiled as C)
│ │ └── compat/ # Freestanding shim headers
│ └── monocypher/
│ └── monocypher.{h,c} # Monocypher 4.0.2 — X25519, ChaCha20, Poly1305, EdDSA (download: make monocypher-get)
├── userspace/
│ ├── libtwm.h # Header-only user-space WM API (M19) - no libc required
│ └── filemanager.c # Graphical file manager (M20) - musl ELF
├── tools/limine/ # Limine bootloader binaries (git clone, gitignored)
├── msys2-env.sh
├── Makefile
└── README.md
Architecture overview¶
Boot sequence¶
BIOS/UEFI
└── Limine (Multiboot2)
├── reads iso/limine.cfg
├── loads kernel.elf into RAM
├── switches display → linear framebuffer (1024→768→32bpp VESA)
└── jumps to boot.asm [32-bit protected mode]
├── verify Multiboot2 magic (0x36D76289)
├── set up temporary GDT
├── enable PAE + paging
- ├── PDPT[0] → pd0: identity-map 0-1 GiB (2 MiB pages)
- └── PDPT[3] → pd3: identity-map 3-4 GiB (VESA FB BAR)
├── set EFER.LME → long mode (64-bit)
├── enable SSE (CR4.OSFXSR | CR4.OSXMMEXCPT)
└── far-jump → kernel_main(mb2_addr) src/kernel/main.cpp
Kernel init sequence (kernel_main - src/kernel/main.cpp)¶
Each step prints [OK] to the screen when it succeeds.
| # | Call | What it does | Source file |
|---|---|---|---|
| 1 | call_global_ctors() |
Run C++ static constructors (.init_array) |
src/kernel/main.cpp |
| 2 | console::init() |
VGA 80→25 text mode + COM1 serial output | src/kernel/console.cpp |
| 3 | scan MB2 tags | Locate framebuffer tag + detect text cmdline flag |
src/kernel/main.cpp |
| 4 | fb::init() |
Register VESA framebuffer (addr, width, height, pitch) | src/kernel/drivers/fb.cpp |
| 5 | console::init_fb() |
Switch console rendering to pixel framebuffer | src/kernel/console.cpp |
| 6 | pmm::init(mb2_addr) |
Parse MB2 memory map, build physical page bitmap | src/kernel/mm/pmm.cpp |
| 7 | heap::init() |
First-fit free-list kernel heap (backs operator new) |
src/kernel/mm/heap.cpp |
| 8 | gdt::init() |
Load 64-bit GDT (null/kern/user segments) + TSS | src/kernel/gdt/gdt.cpp |
| 9 | idt::init() |
Install 256 interrupt gates (ISRs + IRQ stubs) | src/kernel/idt/idt.cpp |
| 10 | vmm::init() |
Register #PF handler on vector 14 |
src/kernel/mm/vmm.cpp |
| 11 | syscall::init() |
Configure EFER.SCE, STAR, LSTAR, SFMASK MSRs | src/kernel/syscall/syscall.cpp |
| 12 | vfs::init() + ramfs::create_superblock() + vfs::mount_root() |
Mount RamFS as / |
src/kernel/vfs/vfs.cpp, src/kernel/vfs/ramfs.cpp |
| 13 | vfs::mkdir("/bin /dev /tmp") |
Create standard directories in RamFS | src/kernel/vfs/vfs.cpp |
| 14 | embed README | Write .rodata blob → /tmp/readme.md (from readme_data.asm) |
src/kernel/main.cpp, src/kernel/readme_data.asm |
| 15 | devfs::init() |
Create /dev/null, /dev/zero, /dev/tty, /dev/fb0, /proc/meminfo |
src/kernel/devfs/devfs.cpp |
| 16 | VFS self-test | Write + read /tmp/hello.txt to verify RamFS |
src/kernel/main.cpp |
| 17 | blkdev::register_ata(0,1) |
Register ATA drives 0 and 1 in block device table | src/kernel/drivers/blkdev.cpp |
| 18 | ata::init() |
IDENTIFY DEVICE on primary bus; print drive models | src/kernel/drivers/ata.cpp |
| 19 | vfs::mkdir("/disk/disk0 /disk/disk1") |
Create mount-point stubs in RamFS | src/kernel/vfs/vfs.cpp |
| 20 | VMM self-test | Map page at 2 GiB, write 0xDEADBEEF, verify readback | src/kernel/main.cpp |
| 21 | Vector self-test | kstd::Vector<uint32_t> - 16 elements, sum = 1240 |
src/kernel/main.cpp |
| 22 | sched::init() |
Create Task 0 = kernel_main (becomes the idle task) |
src/kernel/sched/scheduler.cpp |
| 23 | pic::init() |
Remap 8259A: IRQ 0-7 → vec 32-39, IRQ 8-15 → vec 40-47 | src/kernel/pic/pic.cpp |
| 24 | timer::init(100) |
PIT at 100 Hz → IRQ0 → sched::tick() → context switch |
src/kernel/drivers/timer.cpp |
| 25 | keyboard::init() |
PS/2 on IRQ1; scancode → ASCII ring buffer | src/kernel/drivers/keyboard.cpp |
| 26 | uhci::init() |
Scan PCI for UHCI USB controller; set up TD/QH rings | src/kernel/drivers/uhci.cpp |
| 27 | wm::init() + mouse::init() |
Compositor + terminal canvas; PS/2 mouse on IRQ12 | src/kernel/gfx/wm.cpp, src/kernel/drivers/mouse.cpp |
| 28 | e1000::init() + net::init() |
Scan PCI for Intel 82540EM; init TCP/IP stack | src/kernel/net/e1000.cpp, src/kernel/net/net.cpp |
| 29 | sched::create_task("compositor") |
Compositor task: wm::compose_and_flip() at ~30 fps |
src/kernel/sched/scheduler.cpp, src/kernel/gfx/wm.cpp |
| 30 | sched::create_task("shell") |
Spawn interactive shell with 64 KiB stack | src/kernel/sched/scheduler.cpp, src/kernel/shell/shell.cpp |
| 31 | sched::create_user_task("user_init") |
Ring-3 demo task at 0x40000000 |
src/kernel/sched/scheduler.cpp, src/kernel/userspace/user_init.asm |
| 32 | STI |
Enable interrupts - scheduler takes over, tasks start running | src/kernel/main.cpp |
| 33 | sched::create_task("usb_stage2") |
Start async USB stage-2 worker (wait → probe → MSC/HID detect) | src/kernel/main.cpp |
| 34 | shell::boot_done = true |
Signal shell task that boot output is complete | src/kernel/main.cpp |
| 35 | for(;;) hlt |
kernel_main becomes the idle task (Task 0) |
src/kernel/main.cpp |
Shell startup (shell::run - src/kernel/shell/shell.cpp)¶
Runs as a separate scheduler task, waits for boot_done, then:
| # | Call | What it does | Source file |
|---|---|---|---|
| 1 | while (!boot_done) pause |
Wait until kernel_main finishes printing boot messages |
src/kernel/shell/shell.cpp |
| 2 | tinyfs::mount(0) |
Try to mount ATA drive 0 as TinyFS v4 | src/kernel/fs/tinyfs.cpp |
| 3 | vfs::mount_at("/disk/disk0", sb) |
Attach TinyFS superblock to VFS tree | src/kernel/vfs/vfs.cpp |
| 4 | create_disk_layout("/disk/disk0") |
Bootstrap /etc, /log, /home/root, /usr/bin; create default passwd, shadow, group, resolv.conf, httpd.conf, ftpd.conf, pci.ids; create empty auth.log, error.log, services.log, kernel.log |
src/kernel/shell/shell.cpp |
| 5 | run_startup_services() |
Read /etc/startup-services; launch each line as a background task |
src/kernel/shell/shell.cpp |
| 6 | startup_login() |
Show login: prompt; verify password (max 3 attempts → 3 s lockout); write to auth.log |
src/kernel/shell/shell.cpp |
| 7 | net_load_config() |
Parse /etc/networks; configure IP/netmask/gateway on eth0 |
src/kernel/shell/shell.cpp, src/kernel/net/net.cpp |
| 8 | dns_load_config() |
Parse /etc/resolv.conf; set DNS nameserver |
src/kernel/shell/shell.cpp, src/kernel/net/dns.cpp |
| 9 | tinyfs::mount(1) |
Silent auto-mount of ATA drive 1 (optional data disk) | src/kernel/fs/tinyfs.cpp |
| 10 | hist_load() |
Load ~/.history for the logged-in user |
src/kernel/shell/shell.cpp |
| 11 | canvas::update_sysinfo() |
First CPU%/RAM update on title bar | src/kernel/gfx/canvas.cpp |
| 12 | print_prompt() |
Print user@tinycore:/$ |
src/kernel/shell/shell.cpp |
| 13 | main loop | keyboard::getchar() → dispatch_fg() → repeat; sysinfo refresh every 1 s |
src/kernel/shell/shell.cpp |
Timing (approximate, QEMU)¶
0 ms BIOS POST
500 ms Limine loads kernel.elf from ISO
501 ms boot.asm: long mode, paging
502 ms kernel_main: console → PMM → heap → GDT → IDT → VMM → syscall
503 ms VFS/RamFS → devfs → ATA → sched → PIC → PIT → keyboard
504 ms WM → mouse → e1000 → tasks created → STI
519 ms async USB stage-2 task starts (150 ms settle runs in background)
520 ms shell::run() starts, boot_done received
521 ms disk0 mount + disk layout bootstrap
522 ms "login: " prompt (or immediate shell if diskless)
670 ms USB stage-2 block printed + login prompt repainted
Graphical Framebuffer (M8)¶
The Limine bootloader sets up a 1024 → 768 → 32 bpp VESA framebuffer.
QEMU uses -vga std (VBE 2.0). The framebuffer physical address
(typically 0xFD000000) is in the 3-4 GiB range, identity-mapped in
boot.asm via pd3 - no runtime page-table changes needed.
Desktop layout¶
-───────────────────────────────────────────────────────────────────
- TinyCoreOS M22 - Userspace Runtime Fixes CPU 1% RAM 210 MiB- → 28 px title bar (live sysinfo top-right)
├──────────────────────────────────────────────────────────────────+
- -
- [OK] TinyFS v4: 4 KiB blocks, up to 2 TB partition, up to 4 GB file, file-level dedupe -
- [OK] Disk 0 (TinyFS v4) mounted at /disk/disk0 -
- login: root -
- CoreShell (type 'help' or press F1 for README) -
- root@tinycore:/$ _ -
- (terminal area - Page Up/Down scrolls) -
├──────────────────────────────────────────────────────────────────+
- F1 Readme - F2 Files - F3 Tasks - F4 Net - → - F8 Monitor - F9 Cls- → dynamic F-key bar
└──────────────────────────────────────────────────────────────────-
Font rendering¶
TinyCoreOS uses stb_truetype for antialiased
TrueType font rendering (see Font System for full details).
Default font: Hack Regular (embedded in the kernel binary as a C array;
always available regardless of filesystem state).
Glyphs are pre-rasterized at boot into an 8 → 16 px grayscale alpha cache
(95 printable ASCII chars). Each pixel is alpha-blended at draw time - no
per-character floating-point cost at runtime.
Box-drawing characters (U+2500 range, encoded as 0x80-0x8A internally) use the
original 1-bit bitmap renderer as a fallback since TTF fonts do not contain them.
Dual-mode console¶
| Feature | VGA text mode | Framebuffer mode |
|---|---|---|
| Output target | 0xB8000 VGA cell buffer |
Linear pixel framebuffer |
| Scrollback | VGA CRTC start-address | Character buffer + fb::scroll_region_up() |
| Colour | 4-bit VGA attribute | 32-bit RGB via canvas::RGB[16] |
| Page Up/Down | CRTC hardware scroll | Full buffer redraw (200-row history) |
CoreShell¶
Runs as a kernel task (ring 0). Features:
- Blinking cursor (
_) via PIT tick counter - Command history - ^/→ arrow keys; persisted to
~/.historyper user - Tab completion - cycles directories first, then files; prefix-aware
- Background jobs - append
&to any command;jobsto list - Console scrollback - Page Up / Page Down (200 rows)
- Dynamic F-key bar - context-sensitive function key menu at screen bottom
Dynamic F-key Bar (M21)¶
The bottom status bar shows function key labels that change with the active mode.
Pressing a labelled F-key either executes the command immediately or pre-fills
the command line so arguments can be added before pressing Enter.
Default mode
| Key | Action |
|---|---|
| F1 | Open /tmp/readme.md in the built-in editor |
| F2 | Enter Files sub-menu |
| F3 | Reserved (Tasks, no default action) |
| F4 | Enter Net sub-menu |
| F5 | Enter Logs sub-menu |
| F6 | Show command history tail |
| F7 | Reserved (Jobs, no default action) |
| F8 | Enter Custom sub-menu (from shell.prefs) |
| F9 | Clear screen |
Files sub-menu (entered via F2; F10 returns to default)
| Key | Injects / runs |
|---|---|
| F1 | ls |
| F2 | cat |
| F3 | find . |
| F4 | edit |
| F5 | copy |
| F6 | rename |
| F10 | Back |
Net sub-menu (entered via F4; F10 returns to default)
| Key | Injects / runs |
|---|---|
| F1 | ipconfig |
| F2 | netinit |
| F3 | ping |
| F4 | arp |
| F10 | Back |
Logs sub-menu (entered via F5; F10 returns to default)
| Key | Shows (last 30 lines via tail) |
|---|---|
| F1 | /disk/disk0/log/auth.log - login / su audit trail |
| F2 | /disk/disk0/log/error.log - error log |
| F3 | /disk/disk0/log/services.log - service events |
| F4 | /disk/disk0/log/kernel.log - kernel messages |
| F10 | Back |
Custom sub-menu (entered via F8; F10 returns to default)
Slots F1..F9 are loaded from /disk/disk0/prefs/shell.prefs using:
fkeys.custom.fN.label=<text>fkeys.custom.fN.action=inject:<cmd>|exec:<cmd>|builtin:none|builtin:clear
If a slot has no action, it remains visible but inactive.
Monitor sub-menu (optional / non-default mapping)
| Key | Injects / runs |
|---|---|
| F1 | lscpu |
| F2 | lsram |
| F3 | lsproc |
| F4 | lspci |
| F5 | lseth |
| F6 | lsusb |
| F7 | jobs |
| F8 | sensors |
| F10 | Back |
Filesystem commands¶
| Command | Description |
|---|---|
pwd |
Print current working directory |
cd [path] |
Change directory |
ls [-a] [-c] [-u] [-s] [path] |
List directory (-a detailed columns incl. Created/Modified/Cks, -c sort by creation time, -u owner, -s recursive dir sizes) |
mkdir <path> |
Create directory |
rm [-r] [-f] [-v] <path\|glob ...> |
Remove files/dirs (-r recursive, -f force, -v verbose; wildcard support * ?; prints summary) |
write <path> [text] |
Create / overwrite file |
cat [-su] <path\|glob> [...] |
Print file contents (Ctrl+C to interrupt); -s sort lines, -u unique lines |
tail [-n N] [-f] <path\|glob> [...] |
Print last N lines (default 30); wildcard support; -f follows exactly one resolved file |
grep [-inrlsu] <pat> <file\|dir\|glob> [...] |
Search in files; -i ignore case, -n line numbers, -r recursive, -l filenames only, -s sort output, -u unique output; reads from pipe when no file given |
find <path> [name] |
Recursive filename search: wildcard match (*,?) or substring mode when no wildcard |
rename <old> <new> |
Rename file or directory |
copy [/y\|/s] <src> <dst> |
Recursive copy; src supports wildcard (*,?) expansion in last path component |
edit [path] |
Full-screen text editor (F2 save, F3 save-as, Esc quit) |
clear |
Clear the terminal screen |
I/O operators¶
| Syntax | Description |
|---|---|
cmd > file |
Redirect stdout to file (overwrite) |
cmd >> file |
Redirect stdout to file (append) |
cmd1 \| cmd2 |
Pipe: stdout of cmd1 becomes stdin of cmd2 |
cmd1 \| cmd2 \| cmd3 |
Multi-stage pipe (up to 8 stages) |
cmd1 \| cmd2 > file |
Pipe and redirect final stage to file |
Pipe-aware built-ins (read from pipe when no file argument is given): grep, cat, tail.
Archive commands¶
| Command | Description |
|---|---|
zip <out.zip> <file\|glob...> |
Create ZIP archive (Store method); input supports wildcard expansion |
unzip <file.zip> [dest] |
Extract ZIP (Store + Deflate via miniz) |
zipls <file.zip> |
List ZIP archive contents |
Disk / filesystem commands¶
| Command | Description |
|---|---|
df |
Disk usage: capacity, used, free, %, mount point |
format [drv] [size] |
Format TinyFS v4 drive; optional size limits partition range |
mount [drv] [path] |
Mount TinyFS v4 drive; creates standard dirs on first mount |
tfs status [path] |
TinyFS mount health: read-only state, corrupted inodes, dedupe stats |
tfs dedupe scan [path] |
Force full dedupe scan and print scanned/deduped counters |
format size examples:
format 0 # first available drive (auto-resolve when vdrv0 is absent)
format 0 256M # first 256 MiB only
format 0 4G # first 4 GiB only
format 2 1T # drive 2, first 1 TiB
format 0 512000 # raw sector count (512 000 → 512 B ? 256 MB)
Accepted size suffixes: K (KiB), M (MiB), G (GiB), T (TiB).
The rest of the physical disk beyond the specified size is left untouched.
TinyFS v4 dedupe smoke test:
format 0 /y
mount 0
write /disk/disk0/a "hello dedupe"
copy /disk/disk0/a /disk/disk0/b
tfs status /disk/disk0
ls -ac /disk/disk0
Expected:
- tfs status reports dedupe shared payloads >= 1 and non-zero dedupe saved bytes.
- Modifying one of the shared files triggers COW and increments dedupe COW events.
- After reboot + remount, tfs status still reports consistent dedupe counters.
Process management commands¶
| Command | Description |
|---|---|
jobs |
List all background tasks (running / done) |
kill <pid> |
Terminate a task by its scheduler PID |
lsproc |
List all scheduler tasks (id, state, name) |
<cmd> & |
Run any command in the background (8 slots) |
System information commands¶
| Command | Description |
|---|---|
lscpu |
CPU vendor, brand, family/model/stepping, features |
lsram |
Physical memory: total, free, heap stats |
lspci |
PCI device list with vendor/device IDs; enriched from /etc/pci.ids |
lseth |
Ethernet interfaces: MAC address, model |
lsusb |
USB devices attached to the UHCI controller |
Network commands¶
| Command | Description |
|---|---|
netinit |
Re-read /etc/networks + /etc/resolv.conf and (re)init NIC |
dhcp [seconds] |
Request DHCP lease now (default 20s timeout, range 1-30s) |
ipconfig |
Show interface mode and current IP/netmask/gateway (plus DNS for DHCP lease) |
arp |
Show ARP cache |
ping <host\|IP> |
ICMP echo request; resolves hostname via DNS |
resolve <hostname> |
DNS A-record lookup |
tftp <file> <dst> |
Download file from QEMU built-in TFTP server (10.0.2.2:69) |
wget <url> <dst> |
HTTP downloader with progress output |
update |
App update helper (manifest + package fetch) |
ftp <host> ... |
Interactive FTP client (PASV data mode) |
ftpc [session] |
Dual-panel FTP commander |
httpd |
Manage HTTP/1.1 server (start|stop|status|restart, config: /etc/httpd.conf) |
ftpd |
Manage FTP server (start|stop|status|restart, config: /etc/ftpd.conf) |
sshd |
Manage SSH server (start|stop|status|restart) |
User & security commands¶
| Command | Description |
|---|---|
whoami |
Print current username |
id |
Print uid=N(name) gid=N(name) |
su [user] |
Switch user; root bypasses password; others authenticate |
login <user> |
Log in (passwd + shadow check, 3-attempt lockout) |
logout |
End session, return to login prompt |
passwd [user] |
Change password (min 4 chars, salted FNV-1a hash) |
useradd <user> |
Create account + home dir + standard subdirs (Desktop, Downloads, Documents, Music, Videos, Pictures, tmp) + .history + .env + .profile |
userdel <user> |
Delete account and home directory |
Group commands¶
| Command | Description |
|---|---|
groups [user] |
List groups |
groupadd <name> |
Create group (root only) |
groupdel <name> |
Delete group (root only) |
usermod -g <grp> <user> |
Add user to group (root only) |
Permission commands¶
| Command | Description |
|---|---|
chmod <octal> <path> |
Change mode (e.g. chmod 755 /disk/disk0/usr/bin) |
chown <user> <path> |
Change owner (root only) |
CoreShell GUI Apps¶
Two full-screen TUI applications are built directly into the kernel shell
(no busybox or ELF required). Each replaces the canvas F-key bar with its
own context while active, then restores the shell bar on exit.
nc - Not Commander¶
Dual-panel file manager inspired by Norton Commander.
nc
Layout
[menu bar] Left File Right Commands Options Shortcuts
┌────────────── /left/path ─────────────┬───────────── /right/path ────────────┐
│ Name* Size T Date Ver │ Name Size T Date Ver │
│ .. │ │ .. │
│ Desktop/ │ │ bin/ │
│ Downloads/ │ │ dev/ │
│ ... │ │ ... │
└──────────────────────────────────────┴──────────────────────────────────────────┘
[status bar] panel stats + operation progress (copy/move/search/etc.)
[f-key bar] F1 Help F2 Refresh F3 View F4 Edit F5 Copy F6 Move F7 MkDir F8 Del F9 Menu F10 Quit
[alt f-key bar] hold Alt: F5 ZIP F6 UnZIP F7 Find
Keyboard reference
| Key | Action |
|---|---|
↑ / ↓ |
Move cursor in active panel |
Tab |
Switch between left / right panel |
Enter |
Open directory or file (Enter = Y in confirmation dialogs) |
Backspace |
Go up one directory |
Space / Ins |
Mark / unmark entry (Insert key = Space); for directories, marking also calculates recursive size |
Del / F8 |
Delete file/directory under cursor (recursive for dirs) |
s |
Cycle sort mode: Name → Date → Size (per panel, shown with * in header) |
Ctrl+O |
Suspend NC, drop into mini-shell; Ctrl+O again restores NC |
F1 |
Show keyboard reference |
F2 |
Refresh both panels and drive list |
F3 |
View file contents (read-only pager) |
F4 |
Edit file in the built-in editor |
F5 |
Copy marked entries to the other panel |
F6 |
Move / rename; bare filename (no /) = rename in current directory |
F7 |
Create directory in active panel |
Alt+F5 |
ZIP selected entries |
Alt+F6 |
UnZIP selected archive |
Alt+F7 |
Find dialog |
F8 |
Delete marked entries (confirmation required; recursive for dirs) |
F9 |
Open top menu bar |
F10 |
Quit Not Commander (with confirmation; sets shell CWD to active panel) |
F9 top menu
| Menu | Options |
|---|---|
| Left / Right | Re-read, Swap panels, Sort by name / date / size |
| File | View, Edit, Copy, Move, MkDir, Delete, Select all, Deselect all |
| Commands | Shell Ctrl+O, FTP client (ftpc) |
| Options | About NC |
| Shortcuts | This PC, User folder (Desktop / Documents / Music / Downloads / Pictures / Videos), Root / |
ftpc - FTP Commander¶
Norton-Commander-style dual-panel FTP client.
Left panel = local disk, Right panel = remote FTP server.
ftpc [session_name]
Sessions are saved to /disk/disk0/etc/ftpclient.conf (INI format)
and restored across invocations.
Keyboard reference
| Key | Action |
|---|---|
^ / → |
Move cursor in active panel |
Tab |
Switch between local / remote panel |
Enter |
Enter directory |
Backspace / .. |
Go up one directory |
F5 |
Transfer selected entry to the other panel |
F7 |
Create directory in active panel |
F8 |
Delete selected entry (confirmation required) |
F9 |
Session manager - new connection, reconnect, delete session |
F10 / q |
Quit ftpc |
Session manager (F9)
| Action | Description |
|---|---|
| New | Enter hostname, port, user, password → connect |
| Reconnect | Re-open the current session |
| Delete | Remove session from config file |
Font System (M23)¶
Architecture¶
Kernel binary
L→ hack_regular_data.h → Hack Regular TTF embedded as uint8_t[]
→ font::init()
src/vendor/stb/
L→ stb_truetype_impl.c → stb_truetype compiled as C (CFLAGS_STB, SSE2, -fno-builtin)
L→ compat/ → freestanding shims: string.h, stdlib.h, math.h, assert.h
→ stb_bake_glyph()
src/kernel/gfx/font.cpp
L→ s_cache[95][16][8] → pre-baked grayscale alpha (12 KB, 95 ASCII glyphs)
→ font::draw_char()
Alpha-blend over fb / window buffer
Glyph cache¶
At font::init() (called after heap::init()), stb_truetype rasterizes all 95
printable ASCII characters (0x20-0x7E) at 13 px height into an
s_cache[95][16][8] static array of 8-bit alpha values (? 12 KB total).
draw_char() / draw_char_to() then read from this cache with a fast integer
alpha-blend: out = fg → ?/255 + bg → (255-?)/255.
Box-drawing chars (0x80-0x8A) fall back to the original 1-bit bitmap renderer.
Runtime font switching¶
font # list available fonts + show current
font /usr/share/fonts/JetBrainsMono.ttf # switch immediately (re-bakes cache)
font::load_file(path) reads the TTF from VFS into a heap buffer, calls
stb_font_init(), re-bakes all 95 glyph cells, and frees the previous
heap-allocated font data. The change is instantaneous - the next
draw_char() call uses the new glyphs.
Adding fonts¶
Development / QEMU (manual):
mcopy -i build/fatdisk.img -o MyFont.ttf ::/usr/share/fonts/
Project fonts directory (fonts/):
cp MyFont.ttf fonts/
make fonts-to-fat # requires mtools (pacman -S mingw-w64-x86_64-mtools)
Real hardware: include fonts in the disk image before flashing to USB/disk.
stb_truetype build notes¶
stb_truetype is compiled as a separate C translation unit with its own flags:
| Flag | Reason |
|---|---|
-msse2 |
Float return values require XMM registers on x86-64 ABI |
-fno-builtin |
Prevents GCC from emitting libm calls for sqrt/pow/cos |
-I src/vendor/stb/compat |
Freestanding shims replace <string.h>, <stdlib.h>, <math.h> |
-w |
Suppress vendor warnings |
-O1 |
Light optimisation; SSE2 sqrtsd/sqrtss inline asm used for sqrt |
malloc/free in stb are redirected to heap::alloc/heap::free via
extern "C" wrappers already present in heap.cpp.
Networking¶
All networking is polling-based (no interrupt handler) on the Intel 82540EM
(e1000) emulated by QEMU (-device e1000).
| Layer | Implementation |
|---|---|
| Ethernet II | e1000 TX/RX descriptor rings |
| ARP | Request/reply, 16-entry cache, opportunistic MAC learning |
| IPv4 | Checksum, routing heuristic (same-subnet / gateway) |
| ICMP | Echo request / reply (ping) |
| UDP | Send / receive (dns) |
| DNS | A-record resolver (UDP port 53) |
| TCP | Active client + server-pool (tcp_srv) listeners; loopback path (127.0.0.1) and minimal in-order payload reassembly |
Network config files¶
# /disk/disk0/etc/networks
[eth0]
mode = dhcp
For static networking, switch to:
[eth0]
mode = static
ip = 10.0.2.15
netmask = 255.255.255.0
gateway = 10.0.2.2
# /disk/disk0/etc/resolv.conf
nameserver 10.0.2.3
HTTP Server (httpd)¶
A built-in HTTP/1.1 server (v2) that runs as a background scheduler task.
- Configuration:
/disk/disk0/etc/httpd.conf(port, docroot, index file, mime mappings) - Default document root:
/disk/disk0/usr/www/ - Up to 6 concurrent HTTP sessions (
tcp_srvpool); additional clients wait for a free slot instead of immediate forced disconnect - Connection behavior is configurable in
httpd.conf(max_sessions,idle_timeout_sec) - Serves static files with correct MIME types (
text/html,text/plain,image/*, →) - Directory listing when no index file is present
- Path traversal protection (
..rejected with 403) - Access log:
/disk/disk0/log/httpd.log(timestamp, client IP:port, method/path, status, disconnect reason) - QEMU forwards host port 18000 → guest port 80 (
hostfwd=tcp::18000-:80) and 2222 → 22 for SSH
# /disk/disk0/etc/httpd.conf
port = 80
docroot = /disk/disk0/usr/www
index = index.html
max_sessions = 6
idle_timeout_sec = 5
# mime.types section
mime.html = text/html; charset=utf-8
mime.css = text/css
mime.js = application/javascript
mime.json = application/json
mime.png = image/png
mime.jpg = image/jpeg
mime.jpeg = image/jpeg
Usage:
netinit
httpd
# → open http://localhost:18000 in a host browser
FTP Server (ftpd)¶
Built-in FTP server running as a background scheduler task, with Explicit FTPS
(AUTH TLS / PBSZ 0 / PROT P) backed by mbedTLS.
- Configuration:
/disk/disk0/etc/ftpd.conf - Control channel can listen on multiple ports (
port=repeated orports=...) - Passive mode (PASV) with configurable data-port range (
pasv_port_start/pasv_port_end) - Per-user roots and write permissions (
user.<name>.root,user.<name>.write) - Supports passwordless
anonymousaccount - IP filtering with allow/deny rules (
allow_ip,deny_ip, wildcard*per octet) - Directory/file operations include
MKD,DELEand recursiveRMD LISTexposes file type, permissions, size and modification timestamp (from VFS metadata)- Optional authentication backend:
auth_backend = local(passwords fromftpd.conf)auth_backend = shadow(verification against/etc/passwd+/etc/shadow)- Built-in brute-force throttling:
auth_fail_window_secauth_max_failuresauth_block_sec- Explicit FTPS toggles in config:
tls_enable = 0|1ftps_require_prot_p = 0|1- FTPS runtime:
- TLS control-channel upgrade on
AUTH TLS PROT Penables TLS on PASV data channel (LIST/RETR/STOR)- Current certificate source: bundled mbedTLS test RSA certificate/key (self-signed)
Supported FTP commands
| Command | Purpose |
|---|---|
USER, PASS, QUIT, NOOP |
Session/auth basics |
SYST, FEAT, OPTS, TYPE, AUTH, PBSZ, PROT |
Capability, FTPS negotiation and transfer mode control |
PWD, CWD, CDUP |
Directory navigation |
PASV |
Passive data connection |
LIST, NLST |
Directory/file listing |
RETR, STOR, SIZE |
File download/upload/size |
MKD, DELE, RMD |
Create dir, delete file, recursive remove dir |
# /disk/disk0/etc/ftpd.conf
port = 21
#ports = 21,2121
max_sessions = 3
idle_timeout_sec = 60
pasv_port_start = 21000
pasv_port_end = 21015
# IP ACL
#allow_ip = 127.0.0.1
#allow_ip = 10.0.2.*
#deny_ip = 10.0.2.15
# Brute-force protection
auth_fail_window_sec = 300
auth_max_failures = 5
auth_block_sec = 180
# Auth backend: local | shadow
auth_backend = local
# Explicit FTPS policy (requires mbedTLS-enabled FTPS runtime)
tls_enable = 0
ftps_require_prot_p = 1
user.anonymous.root = /disk/disk0/usr/ftp
user.anonymous.password =
user.anonymous.write = 0
user.root.root = /disk/disk0/home/root
user.root.password =
user.root.write = 1
Usage:
netinit
ftpd start
ftpd status
ftpd stop
Startup Scripts¶
Two configuration files control automatic command execution:
/disk/disk0/etc/startup-services¶
Executed at boot before the login prompt, right after disk0 is mounted.
Each non-comment line is launched as a background task.
# /disk/disk0/etc/startup-services
# Uncomment to auto-start the web server at every boot:
# httpd
# ftpd
# netinit
/disk/disk0/etc/startup¶
Executed after each successful login as the logged-in user.
Each non-comment line is run as a foreground shell command.
# /disk/disk0/etc/startup
# cd /disk/disk0/home/root
# netinit
Edit both files with edit /disk/disk0/etc/startup.
Optional GUI integration: if /disk/disk0/prefs/startup.prefs exists, startup commands are filtered by its toggles (coredesk, network, httpd, ssh).
If the prefs file is missing, startup behavior stays exactly as before (shell-first path unchanged).
Dual-Disk Support¶
| Drive | QEMU index | Default mount | Role |
|---|---|---|---|
| Primary master | index=0 |
/disk/disk0 |
System disk |
| Primary slave | index=1 |
/disk/disk1 |
Data disk (auto-mounted if formatted) |
Both disks are blank images created on first make run
(build/tinyfs.img, build/tinyfs1.img).
The default QEMU size is configurable in the Makefile; TinyFS v4 supports
up to ~2 TB per partition. Use format 0 <size> to initialise only a
portion of a larger disk.
Disk layout on disk0¶
/disk/disk0/
├── home/ → user home directories
│ └── root/
│ └── .history
├── etc/
│ ├── passwd → user database (user:uid:homedir)
│ ├── shadow → password hashes (user:8charsalt:16charhash)
│ ├── group → group database (groupname:gid:member1,member2)
│ ├── networks → NIC configuration (INI-style)
│ ├── resolv.conf → DNS nameservers
│ ├── pci.ids → custom PCI device DB (VVVV:DDDD=Vendor|Device)
│ ├── httpd.conf → HTTP server config
│ ├── ftpd.conf → FTP server config
│ ├── startup → post-login commands
│ └── startup-services → boot-time background services
├── log/
│ └── auth.log → login / su audit trail
└── usr/
├── bin/ → user ELF programs (M10+)
└── www/ → HTTP server document root
└── index.html
Security & User Management¶
Authentication files¶
| File | Format | Notes |
|---|---|---|
etc/passwd |
user:uid:homedir |
User database |
etc/shadow |
user:8charsalt:16charhash |
Salted FNV-1a hashes |
etc/group |
groupname:gid:member1,member2 |
Group database |
log/auth.log |
<ticks> <event> |
Audit trail |
Password hashing¶
FNV-1a 64-bit mixed with an 8-character hex salt (from timer tick xorshift):
root:a1b2c3d4:e5f6a7b8c9d0e1f2
^^^^^^^^ ^^^^^^^^^^^^^^^^
8-char 16-char hash of (password + salt)
salt
Empty hash = no password. Old single-field entries (user:hash) accepted for
backward compatibility. Minimum password length: 4 characters.
Login security¶
- 3-attempt lockout: 3-second cooldown after 3 consecutive failures.
- Audit log: every login and
suattempt written toauth.log. - Root bypass: uid=0 can
suto any account without a password.
File permissions¶
All inodes carry mode (rwxrwxrwx) and uid. Enforced in open, mkdir,
unlink:
- uid=0 (root) bypasses all checks.
- Owner: user bits (
S_IRUSR/S_IWUSR). - Others: other bits (
S_IROTH/S_IWOTH).
TinyFS v4 persists mode, uid, ctime, mtime, version, and dedupe
metadata (content_crc32c, owner/refcount) to disk
(OnDiskInode). FAT32 stores ctime/mtime in standard directory-entry
date/time fields and repurposes nt_res for the version counter. Both RamFS and
TinyFS implement setattr for chmod/chown.
The version field in vfs::Inode / vfs::Stat is a write counter
(1 = freshly created, increments on each write(), saturates at 255).
It is displayed in nc (Ver column) and ls -a.
VFS¶
| Interface | Operations |
|---|---|
InodeOps |
lookup, create, mkdir, unlink, rename, setattr |
FileOps |
read, write, readdir, close |
vfs::rename |
Cross-directory rename (RamFS: linked-list splice; TinyFS: in-place) |
vfs::mount_at |
Overlay a superblock at a path |
vfs::chmod / chown |
Update permission bits / owner UID |
vfs::set_current_uid |
Set active security context on login/logout |
Filesystem tree¶
/ → RamFS (always in RAM)
├── bin/
├── dev/
├── tmp/
└── disk/
├── disk0/ → TinyFS primary master (auto-mounted)
└── disk1/ → TinyFS primary slave (auto-mounted if formatted)
ATA PIO Driver¶
| Operation | Detail |
|---|---|
| Detection | IDENTIFY DEVICE (0xEC); skips ATAPI |
| Addressing | 28-bit LBA |
| Read | CMD 0x20, poll DRQ, 256 → 16-bit words/sector |
| Write | CMD 0x30, poll DRQ, write + FLUSH CACHE (0xE7) |
| Drives | 0 = primary master, 1 = primary slave |
TinyFS v4 On-Disk Format¶
TinyFS v4 is a custom scalable filesystem with 4 KiB data blocks and a
fully dynamic metadata layout calculated at format time.
Disk layout (512-byte sectors)¶
Sector 0 Superblock (magic "TFS4", version 4)
Sectors 1 .. B Block bitmap (1 bit per 4 KiB data block)
Sectors B+1 .. B+I Inode bitmap
Sectors B+I+1 .. B+I+T Inode table (4 inodes → 128 B per sector)
Sector data_start+ Data blocks (4 KiB = 8 sectors, block-aligned)
All offsets (bitmap_start, imap_start, itbl_start, data_start) are
stored in the superblock and computed at format time - no hardcoded positions.
Capacity¶
| Resource | Limit |
|---|---|
| Partition | ~2 TB (uint32_t block numbers → 4 KiB) |
| File size | ~4 GB (12 direct + 1024 single + 10242 double indirect) |
| Inodes | up to 1 000 000 (1 per 256 KiB of data) |
| Filename | 247 characters |
Key on-disk structures¶
struct OnDiskSb { // 512 bytes, sector 0
uint32_t magic; // 0x54465334 ("TFS4")
uint32_t version; // 4
uint32_t block_size; // 4096
uint32_t inode_count; // total inodes
uint64_t block_count; // total 4 KiB data blocks
uint64_t free_blocks;
uint32_t free_inodes;
uint32_t root_inode; // always 0
uint32_t bitmap_sectors, imap_sectors, itbl_sectors;
uint64_t bitmap_start, imap_start, itbl_start, data_start;
uint8_t pad[424];
};
struct OnDiskInode { // 128 bytes; 4 per 512-byte sector
uint32_t mode, uid;
uint64_t size; // file size in bytes (uint64_t)
uint32_t ctime, mtime; // Unix timestamps
uint32_t nlinks, blocks, flags;
uint32_t direct[12]; // 4 KiB block numbers
uint32_t indirect; // single-indirect block
uint32_t dindirect; // double-indirect block
uint32_t tindirect; // triple-indirect (reserved)
uint8_t version; // write counter (1-255)
uint32_t content_crc32c; // full-file hash for dedupe
uint32_t dedup_owner_ino; // owner inode for shared payload
uint32_t dedup_refcount; // valid on owner inode
uint8_t dedup_flags; // owner/shared flags
uint32_t meta_crc32c; // inode metadata checksum
uint8_t pad[14];
};
struct OnDiskDirent { // 256 bytes; 16 per 4 KiB block
uint32_t inode; // 0 = unused
uint16_t rec_len; // always 256
uint8_t name_len;
uint8_t type; // 1=file, 2=dir
char name[248]; // max 247 usable chars
};
Block addressing¶
| Block range | Mechanism | Adds |
|---|---|---|
| 0-11 | direct[12] |
48 KiB |
| 12-1035 | single-indirect | +4 MiB |
| 1036-1 049 611 | double-indirect | +4 GiB |
Inode metadata fields¶
Every inode stores:
- ctime - creation time (Unix timestamp; filesystem-backed where available, fallback to mtime when unknown)
- mtime - last modification time (Unix timestamp, set by RTC on write)
- version - write counter, incremented on every write, saturates at 255, initialised to 1 at creation
- content_crc32c - full-file content hash used by TinyFS v4 file-level dedupe
- dedup_owner_ino / dedup_refcount - shared payload ownership and reference counting
vfs::stat() currently surfaces ctime, mtime, version, and metadata
checksum status. ls -a shows Created/Modified/Cks; nc and ls -a
show version.
How file-level dedupe works¶
TinyFS v4 deduplicates whole-file payloads (not variable chunks):
- On write/truncate completion, filesystem computes
content_crc32c. - Candidate files are filtered by same
size+ samecontent_crc32c. - Candidates are verified block-by-block (CRC match alone is never trusted).
- On exact match:
- current inode becomes shared member (dedup_owner_inopoints to owner),
- ownerdedup_refcountis incremented,
- duplicate payload blocks are released. - On future write to a shared file, copy-on-write (COW) detaches it first
so modifications do not affect other files sharing the payload. - On unlink/truncate, refcount lifecycle is preserved:
- non-owner unlink decrements owner refcount,
- owner unlink elects replacement owner when needed,
- physical blocks are freed only when effective refcount reaches zero.
You can force full re-deduplication with:
tfs dedupe scan /disk/disk0
And inspect live counters with:
tfs status /disk/disk0
Backward compatibility¶
TinyFS v4 uses a new magic/version (TFS4, version 4).
Older disks are recognised as "present but not formatted" and must be
reformatted with format [0|1] to use v4.
Text Editor (edit)¶
Full-screen terminal editor with line-oriented buffer and incremental redraw:
| Key | Action |
|---|---|
| Arrow keys | Move cursor |
| Home / End | Start / end of current line |
| Page Up / Dn | Scroll one screen up / down |
| Backspace | Delete character / join lines |
| Del | Delete character under cursor / join with next line |
| Enter | Insert newline / split line |
| F2 | Save to current file |
| F3 | Save As (prompts for filename) |
| Esc | Quit (warns on unsaved changes) |
ZIP Support¶
| Function | Detail |
|---|---|
zip::list |
Iterates Central Directory |
zip::extract |
Store (method 0) + Deflate (method 8 via miniz tinfl) |
zip::create |
Store-method archives with proper EOCD |
miniz_tinfl.c compiled as a separate C translation unit with freestanding
shim headers in src/vendor/miniz/compat/.
Memory layout¶
| Region | Virtual address | Description |
|---|---|---|
| Kernel text/data/bss | 0x100000 - _kernel_end |
Linked at 1 MiB |
| PMM bitmap | after _kernel_end |
~128 KiB for 4 GiB RAM |
| Kernel heap | grows up from PMM | first-fit free list |
| Identity map 0-1 GiB | 0x00000000 - 0x3FFFFFFF |
2 MiB huge pages |
| Identity map 3-4 GiB | 0xC0000000 - 0xFFFFFFFF |
2 MiB huge pages (VESA FB) |
| User code (M6) | 0x40000000 |
4 KiB page, PF_USER |
| User stack (M6) | 0x40001000 |
4 KiB page, PF_USER |
Preemptive Scheduler¶
PIT fires IRQ0 at 100 Hz → sched::tick() → round-robin next Ready task →
switch_context.
switch_context(old_rsp*, new_rsp):
push rbp/rbx/r12-r15 → save callee-saved regs on OLD stack
[old_rsp*] = RSP → store old RSP
RSP = new_rsp → load NEW stack
pop r15-r12/rbx/rbp → restore new task's regs
ret → resume new task at saved RIP
CPU usage measurement¶
sched::tick() increments two counters on every PIT interrupt:
s_total_ticks- always incrementeds_busy_ticks- incremented only when the interrupted task was not idle
Tasks signal idleness by calling sched::hint_idle() immediately before a
hlt instruction or an unconditional sched::yield() with no work to do.
The next tick() sees the hint, skips s_busy_ticks, and clears the flag.
CPU% is sampled once per second: busy / total → 100.
Task lifecycle API¶
| Function | Description |
|---|---|
sched::create_task(name) |
Spawn kernel task |
sched::exit_current() |
Terminate the calling task |
sched::kill_task(id) |
Mark any non-idle task Dead from outside |
sched::hint_idle() |
Signal that the calling task is about to sleep/idle |
sched::find_task(id) |
Look up a Ready/Running/Blocked task by ID |
sched::for_each_task(fn) |
Iterate all non-Dead tasks |
Syscall ABI (introduced in M6)¶
| Number | Name | Arguments | Returns |
|---|---|---|---|
| 0 | SYS_EXIT | exit_code | - |
| 1 | SYS_WRITE | fd, buf*, len | bytes written |
| 2 | SYS_OPEN | path*, flags, mode | fd or -1 |
| 3 | SYS_CLOSE | fd | 0 or -1 |
| 4 | SYS_READ | fd, buf*, len | bytes read |
| 5 | SYS_READDIR | fd, DirEntry*, max | count |
| 6 | SYS_MKDIR | path*, mode | 0 or -1 |
| 7 | SYS_STAT | path, Stat | 0 or -1 |
Historical Notes¶
Detailed milestone-by-milestone implementation notes (M10-M44) were moved out of this file to keep the main README focused on current usage.
- See
docs/history.mdfor the archived summary. - For exact low-level changes, use repository history (
git log -- README.mdand per-file history).
PHP Migration Plan¶
- Migration plan for bringing PHP into TinyCoreOS (MVP -> CGI -> hardening):
docs/php_migration_plan.md
Make targets¶
| Target | Description |
|---|---|
make / make all |
Build kernel ELF + ISO |
make kernel |
Compile only (no ISO) |
make iso |
Package into bootable ISO |
make run |
QEMU graphical window; creates disk images if absent |
make serial |
QEMU to stdout, no window |
make debug |
QEMU paused, GDB on port 1234 |
make debug-boot |
QEMU with -no-reboot -d int,cpu_reset |
make limine-get |
Clone Limine binaries (run once) |
make monocypher-get |
Download Monocypher crypto library (run once, for SSH) |
make userspace |
Build user-space ELF programs with musl-gcc (WSL2/Linux) |
make userspace-docker |
Build user-space ELF programs inside Docker |
make fonts-to-fat |
Copy fonts/*.ttf → build/fatdisk.img:/usr/share/fonts/ (requires mtools) |
make clean |
Remove build/ (disk images included) |
Toolchain¶
| Component | Target | Notes |
|---|---|---|
| GCC 13.x | x86_64-elf |
-std=c++20 -ffreestanding -fno-exceptions -fno-rtti |
| GCC 13.x (C) | x86_64-elf |
-std=c11 -msse2 -fno-builtin for miniz_tinfl.c and stb_truetype_impl.c with compat shims |
| NASM | ELF64 | boot.asm, isr_stubs.asm, context.asm, syscall_entry.asm, readme_data.asm |
| Limine | v5.x-branch-binary | Multiboot2; handles VESA framebuffer setup |
| QEMU | qemu-system-x86_64 |
-m 2048M -serial stdio -vga std -netdev user,id=net0,hostfwd=tcp::18000-:80,hostfwd=tcp::2222-:22,tftp=build -device e1000,netdev=net0 |
Compile flags of note:
-march=x86-64 -mno-sse -mno-sse2 -mno-avx -mno-avx2 - kernel: baseline x86-64, no SSE (kernels must not use XMM registers)
-mno-red-zone - required for ring-0 interrupt handlers
-fno-stack-protector - no libgcc stack cookie in freestanding
-fno-threadsafe-statics - no __cxa_guard_* needed
-MMD -MP - automatic header dependency tracking
-fno-omit-frame-pointer - RBP chain preserved for kernel stack traces
stb_truetype (vendor C, separate flags):
-msse2 -fno-builtin - SSE2 enabled (font init runs before scheduler; XMM safe at boot);
-fno-builtin prevents GCC from silently emitting libm calls for sqrt/pow/cos
Network & TCP/IP (M30)¶
M30 completes the network stack with the missing components needed for automatic configuration and inter-process communication.
DHCP Client (net/dhcp.hpp/cpp)
- Full RFC 2131 / 2132 four-message exchange: DISCOVER → OFFER → REQUEST → ACK
- net::udp_send_bcast() added to net.cpp - sends from 0.0.0.0 to 255.255.255.255 via broadcast MAC before an IP is assigned
- On ACK: calls net::set_config(0, ...) with leased IP/mask/gateway and dns::set_server(0, ...) with the DNS server
- dhcp [seconds] shell command: manual lease request with live output and configurable timeout
- netinit automatically runs DHCP when /etc/networks has mode=dhcp
- VMware/NAT compatibility: client now accepts valid DHCP OFFER/ACK packets shorter than 548 bytes
- DHCP negotiation phases (DISCOVER/OFFER/REQUEST/ACK, timeouts, NAK) are mirrored to hdd.log for diagnostics
Loopback Interface (127.0.0.1)
- net::loopback_send() injects raw IP packets into an 8-slot ring buffer (no NIC, no Ethernet)
- net::poll() drains the loopback ring before the NIC RX ring
- handle_ip() now accepts broadcast (255.255.255.255) and loopback (127.x.x.x) destinations in addition to the interface IP
- ping 127.0.0.1 and localhost resolution work without network
/etc/hosts Lookup
- resolve_host() wrapper in shell.cpp checks /disk/disk0/etc/hosts before calling dns::resolve()
- Standard format: <ip> <hostname> [<alias>...]; comments with #
- All commands that resolve hostnames (ping, wget, ftp, ftpc, smbclient, resolve) use the wrapper
- localhost resolves to 127.0.0.1 without a file (hardwired in dns::resolve)
AF_UNIX Domain Sockets (net/socket.hpp/cpp)
- AF_UNIX = 1, struct SockAddrUn { sun_family; sun_path[108] }
- KSock extended with domain, unix_pair, unix_side, unix_path
- Socket pool enlarged from 4 to 16 slots; 8 UnixPair ring-buffer pairs (8 KiB each direction)
- unix_bind(path) - registers path in binding table, creates VFS placeholder inode
- unix_connect(path) - finds listener, allocates pair, connects side A
- unix_accept(listener) - creates new KSock for side B, returns File*
- unix_read/unix_write - bidirectional ring buffers (A→B and B→A)
- Syscall layer updated: SYS_BIND, SYS_CONNECT, SYS_ACCEPT/ACCEPT4 dispatch on KSock::domain
Shell I/O Plumbing (M31)¶
M31 adds the syscalls required for a user-space shell to set up I/O redirection and pipes, as well as standard file-manipulation operations that musl/libc programs rely on.
dup / dup2 / dup3 (syscalls 32 / 33 / 292)
- vfs::File gains a refcount field (starts at 1); vfs::close() only destroys when it reaches 0
- FdTable::replace(new_fd, f) - atomically installs f and returns the old occupant for closing
- fork() and clone() bump the refcount for every inherited fd
- dup2(old_fd, new_fd) closes the previous file at new_fd before installing the new reference
- dup3 adds O_CLOEXEC semantics; requires old_fd != new_fd
- SYS_READ/SYS_WRITE now check fd_table[0/1/2] first - dup2 redirections to stdin/stdout work correctly
pread64 / pwrite64 (syscalls 17 / 18)
- Read/write at an arbitrary offset without changing f->pos
- Saves and restores f->pos around the underlying VFS call
- Required by sqlite3, busybox's dd, and many database-like programs
truncate / ftruncate (syscalls 76 / 77)
- vfs::FileOps gains a truncate(File*, int64_t new_size) callback (7th field)
- ramfs: file_truncate() grows (zero-fills) or shrinks the buffer; adjusts f->pos if beyond new size
- tinyfs / fat32: callback is nullptr; VFS generic fallback handles shrink-only via inode->size
- vfs::truncate(path, size) opens the file internally and delegates to ftruncate
rename / renameat / renameat2 (syscalls 82 / 264 / 316)
- vfs::rename() was already implemented in vfs.cpp; this adds the syscall dispatch
- renameat/renameat2 resolve paths relative to dirfd (AT_FDCWD treated as cwd)
- Required by editors, package managers, and any program using atomic save-to-temp patterns
getpgrp (syscall 111)
- Returns sched::current()->pgid; needed for job-control queries by shells
FD_MAX increased from 16 → 64
- Busybox and most programs need more than 16 simultaneous open files
- FdTable size increases from 128 to 512 bytes per task (heap-allocated, no stack impact)
PTY - Pseudo-Terminal (M32)¶
M32 adds a complete pseudo-terminal subsystem, enabling user-space shells (busybox sh, bash) to run interactively with job control, line editing, and proper terminal semantics.
Architecture (src/kernel/tty/pty.hpp / pty.cpp)
- Up to 4 simultaneous PTY pairs; each pair has two 4 KiB ring buffers:
- m2s (master→slave): keyboard/terminal-emulator input going to the program
- s2m (slave→master): program output going back to the terminal
- Per-pair tty::Termios struct - each PTY has its own independent terminal settings
/dev/ptmx - PTY master
- open("/dev/ptmx") / posix_openpt() is intercepted in SYS_OPEN/SYS_OPENAT before VFS
- Each open allocates a fresh slot and returns a master File* with custom FileOps
- ioctl(master_fd, TIOCGPTN, &n) → returns the PTY index n
- ioctl(master_fd, TIOCSPTLCK, &0) → unlocks the slave so it can be opened
- TCGETS/TCSETS on the master operate on the slave's termios
/dev/pts/N - PTY slave
- open("/dev/pts/N") is intercepted; returns nullptr if slot n is locked or unused
- ioctl(slave_fd, TIOCSCTTY, 0) - sets controlling terminal (stub; updates session)
- ioctl(slave_fd, TCGETS/TCSETS) - reads/writes the per-PTY termios
- TIOCGWINSZ / TIOCGPGRP delegate to global console state
N_TTY line discipline (slave side)
- Canonical mode (ICANON): slave_read blocks until \n or ^D; slave_poll_read scans the ring for a complete line
- Raw mode: bytes are delivered immediately on each vfs::read
- Echo (ECHO/ECHOE): input written to m2s is echoed back to s2m for the master to display; DEL (0x7F) sends BS SP BS erase sequence
- ICRNL: CR → NL on master write (input processing)
- ONLCR: NL → CR+NL on slave write (output post-processing)
Blocking reads
- Both master and slave read ops call sched::yield() until data is available - safe because SYS_READ enables interrupts (STI) before calling vfs::read
Busybox on Disk (M33)¶
M33 delivers a complete user-space toolchain on disk via the statically-linked busybox multi-call binary, plus the kernel infrastructure needed to run it correctly.
ELF loader - full Linux ABI stack (src/kernel/exec/elf.cpp)
elf::load now accepts argv[], argc, envp[], envc. The initial user-stack layout strictly follows the System V AMD64 ABI:
RSP → [argc]
[argv[0] ptr] → [argv[argc-1] ptr] [NULL]
[envp[0] ptr] → [envp[envc-1] ptr] [NULL]
[AT_PAGESZ, 4096] [AT_HWCAP, 0] [AT_CLKTCK, 100]
[AT_HWCAP2, 0] [AT_SECURE, 0] [AT_RANDOM, ptr] [AT_NULL, 0]
[high VA] argv/envp strings (packed) + 16-byte random block
A built-in minimal environment is injected when envp is not provided:
HOME, PATH=/bin:/usr/bin:/disk/disk0/bin:/disk/disk0/usr/bin, TERM=vt100, SHELL=/bin/sh, USER=root.
SYS_EXECVE - user-space argv/envp (src/kernel/syscall/syscall.cpp)
The execve handler now copies the user-space char **argv and char **envp arrays into kernel buffers (heap-allocated, 8 KiB each, up to 64 strings) and passes them to elf::load. On failure the buffers are freed before calling sched::exit_current.
Shell - cmd_exec argv splitting
cmd_exec(arg, argv0_override) splits the tail of the exec argument string into tokens and builds a proper const char* argv[]. The optional argv0_override allows the PATH dispatcher to pass the typed command name (e.g. sh) as argv[0] even when the resolved binary is /disk/disk0/bin/busybox - this is the mechanism that makes busybox applet dispatch work via symlinks.
Shell - PATH search extended
The implicit PATH search now covers four directories in order:
1. /bin
2. /usr/bin
3. /disk/disk0/bin
4. /disk/disk0/usr/bin
bb-install shell command
bb-install [url]
- Downloads
busyboxvia TFTP from10.0.2.2:69(QEMU built-in, no extra server needed) - or via HTTP if a full URL is passed chmod 755 /disk/disk0/bin/busybox- Shows an interactive group-selection menu (Space = toggle, Enter = install, Esc = cancel):
| Group | Default | Applets |
|---|---|---|
| Shell | ? on | sh |
| File utilities | ? on | ls cat cp mv rm find du df → |
| Text processing | ? on | grep sed awk sort head tail cut → |
| Process utils | ? on | ps kill top free sleep → |
| Archive | ? on | tar gzip bzip2 → |
| Network | ? on | wget ping netstat |
| System utils | ? on | date uname whoami id |
| Permissions | ? on | chmod chown chgrp |
| Scripting | ? on | test expr xargs seq |
| Misc | ? on | md5sum hexdump od strings sha256sum |
| Overlap/extra | ? off | mount umount dmesg vi ash hwclock → |
nc(netcat) is always excluded - the name conflicts with the built-in Not Commander (nc).
Interactive shell fix (M33 post) - fully working ?
After the initial M33 implementation busybox sh loaded but exited immediately, and later ran but showed no prompt. The root causes were a sequence of missing kernel behaviours fixed iteratively:
| Fix | File | What changed |
|---|---|---|
TIOCGPGRP returns caller's pgid |
syscall.cpp |
tcgetpgrp(0) == getpgrp() → busybox sees itself as foreground process |
| SIGTTIN / SIGTTOU / SIGTSTP ignored | signal.cpp |
Default action changed from "terminate" to "ignore" (real Linux: stop) |
fd 0/1/2 pre-installed as /dev/tty |
shell.cpp |
Child process has real File* objects for stdio; dup()/fcntl(F_DUPFD_CLOEXEC) work |
F_DUPFD_CLOEXEC respects min-fd |
syscall.cpp |
fcntl(0, F_DUPFD_CLOEXEC, 10) now returns fd ? 10 as POSIX requires |
SYS_WRITEV (nr=20) |
syscall.cpp |
busybox uses writev() to write the prompt - missing syscall = invisible prompt |
SYS_READV (nr=19) |
syscall.cpp |
scatter read; busybox uses for terminal input |
sched_yield (nr=24) |
syscall.cpp |
cooperative yield used by busybox busy-wait loops |
| ANSI escape sequence parser | console.cpp |
\033[K erase-to-EOL, cursor movement, SGR colors stripped; prompt no longer garbled |
\r carriage return |
console.cpp |
\r now moves cursor to col 0 instead of rendering as literal character |
getdents64 debug removed |
syscall.cpp |
removed [gd64] per-entry debug print that flooded the console on ls |
| fork register restore | syscall_entry.asm, scheduler.cpp |
enter_userspace_fork saves/restores RBX/RBP/R12-R15; without this the fork child crashed immediately (R13 = wrong pointer → page fault in [R13+0x21]) |
Build guard - make run requires busybox
make run and make serial now call check-busybox first. If build/busybox is missing, make stops with a clear error and instructions. Python is no longer required for any build or run step (only optionally for make serve).
Complete M33 workflow
# Step 1 (host, once): get the busybox binary into build/
make busybox-prebuilt # MSYS2/Windows: download pre-built x86-64 binary
# OR: make busybox # WSL2/Linux: compile with musl-gcc
# OR: make busybox-docker
# Step 2 (host): start QEMU - TFTP is built in, no Python/HTTP server needed
make run
# Step 3 (kernel shell, first boot)
format 0 # initialise TinyFS on disk0 (once)
mount 0 # mount disk0 at /disk/disk0
netinit # bring up eth0 (DHCP via QEMU SLIRP)
bb-install # TFTP busybox + chmod 755 + interactive group-selection menu for symlinks
# Step 4: launch interactive busybox shell
sh # → busybox sh prompt / #
ls / # list root filesystem
echo hello world # basic test
TFTP vs HTTP:
bb-installuses QEMU's built-in TFTP server (10.0.2.2:69) which serves thebuild/directory automatically - nomake busybox-serve/ Python needed. Pass an HTTP URL to use wget instead:bb-install http://10.0.2.2:9090/busybox.
CoreShell CLI Completeness (M35)¶
M35 rounds out the CoreShell built-in command set so day-to-day shell usage does not require busybox.
New built-in commands
| Command | Description |
|---|---|
rm [-r] [-f] <path> |
Remove file; -r recursive directory removal, -f suppress errors |
rmdir <dir> |
Remove empty directory |
touch <file> |
Create file if absent, update mtime otherwise |
echo [-n] <text> |
Print text; -n suppresses trailing newline |
mv <src> <dst> |
Rename/move (alias for rename) |
cp <src> <dst> |
Alias for copy (recursive; wildcard-capable source) |
ln [-s] <target> <link> |
Hard link or -s symbolic link (alias for link/symlink) |
uptime |
Elapsed time since boot in Xd Xh Xm Xs format |
source / . <file> |
Execute shell script in-place (no subprocess) |
alias <name>=<val> |
Define command alias (persisted in session) |
unalias <name> |
Remove alias |
Alias expansion
All aliases are expanded before dispatch, so user-defined shortcuts work transparently for every built-in and external command.
lsproc enhancements
lsproc now displays per-process memory usage and a full status legend:
PID NAME STATE PRIV STK_KB MEM_KB CWD
──────────────────────────────────────────────────────
1 init S K 64 64 /
2 shell R K 128 132 /disk/disk0/home/root
3 compositor S K 64 64 /
Legend:
STATE: R=Running S=Sleeping D=Disk-wait Z=Zombie
PRIV: K=Kernel U=User *=own address space
MEM_KB = kernel stack + task_brk heap (user tasks only)
SSH Client & Server (M36)¶
M36 adds a full SSH2 implementation, enabling encrypted remote access to TinyCoreOS from any OpenSSH-compatible client.
Architecture (src/kernel/net/ssh.hpp / ssh.cpp)¶
The SSH implementation lives entirely in the kernel (ring-0). The server (sshd) runs on the TCP server pool (tcp_srv_listen/tcp_srv_accept) with a small worker pool (up to 5 concurrent sessions), while client mode uses the active TCP path, allowing stable local self-connect tests (ssh localhost) and concurrent service operation.
Recent reliability/UX updates:
- Per-session SSH scratch buffers replaced shared static packet buffers to avoid client/server data races.
- KEX flow was adjusted so the client sends KEXINIT immediately after version exchange (prevents localhost deadlock).
- SSH banner identity is built from release macros (TCOS_PRODUCT_NAME, TCOS_PRODUCT_CODENAME, TCOS_PRODUCT_VERSION) with safe token sanitization.
- Interactive shell output is buffered/chunked (instead of per-byte sends) for better stability.
- SSH shell prompt is visibly different: (ssh) $.
Cryptography stack
| Primitive | Source | Notes |
|---|---|---|
| X25519 key agreement | Monocypher | ECDH for key exchange (curve25519-sha256) |
| ChaCha20 stream cipher | Monocypher | chacha20-poly1305@openssh.com AEAD |
| Poly1305 MAC | Monocypher | Packet authentication |
| Ed25519 signatures | Monocypher primitives + custom SHA-512 | Standard RFC 8032 ssh-ed25519 host key |
| SHA-256 | Custom (from scratch) | KEX hash, fingerprint |
| SHA-512 | Custom (from scratch) | Ed25519 signing |
| CSPRNG | CPUID-detected RDRAND → fallback xorshift64 | seeded from timer::ticks() + heap address entropy |
SSH protocol subset
| Layer | Algorithm | Notes |
|---|---|---|
| Key exchange | curve25519-sha256 |
RFC 8731 |
| Host key | ssh-ed25519 |
RFC 8709 |
| Encryption | chacha20-poly1305@openssh.com |
OpenSSH extension |
| Authentication | password |
passwd/shadow aware; supports empty-shadow compatibility flow for existing users |
| Channel | session + exec / interactive shell |
SSH_MSG_CHANNEL_DATA loop |
Host key persistence
On first sshd launch the server generates a random Ed25519 key pair and saves the private key to /disk/disk0/etc/ssh/ssh_host_ed25519_key. Subsequent starts load the saved key so the host fingerprint remains stable across reboots.
Shell integration
SSH sessions execute commands via shell::exec_cmd() (exported from shell.cpp). Output is captured with console::set_char_hook, normalized to CRLF for terminal clients, and sent in buffered chunks.
Commands¶
sshd start # start SSH server on port 22 (background task)
sshd status # check state
sshd stop # stop service
sshd restart # restart service
ssh [user@]host [command] # connect to remote SSH server; masked password prompt (`*`)
Usage example¶
# Host (QEMU port-forwarded to 2222):
ssh -p 2222 root@localhost
# → prompts for password
# → interactive CoreShell session over encrypted SSH
# Self-connect inside TinyCoreOS for local debugging:
ssh localhost
# Run a single command:
ssh -p 2222 root@localhost "lsproc"
Monocypher vendor library¶
Monocypher is downloaded once via:
make monocypher-get # fetches monocypher.c + monocypher.h from GitHub (tag 4.0.2)
The library is compiled as a plain C translation unit with -std=c11 -Os -fno-builtin and linked into the kernel. It is checked as a build prerequisite → a missing monocypher.c produces a clear error with instructions.
AC97 Audio (M37)¶
M37 adds a complete AC97 audio subsystem, enabling sound output from the kernel and user-space programs.
Architecture (src/kernel/drivers/ac97.hpp / ac97.cpp)¶
The driver targets QEMU's emulated Intel ICH AC97 codec (-device AC97, PCI 0x8086:0x2415).
Hardware layout
| Component | BAR | Size | Purpose |
|---|---|---|---|
| Native Audio Mixer (NAM) | BAR0 (I/O) | 256 B | Codec mixer registers: volume, sample rate |
| Native Audio Bus Master (NABM) | BAR1 (I/O) | 64 B | DMA controller: BDL, CIV, LVI, SR, CR |
DMA ring buffer
BDL: 32 → 8-byte entries
[phys_addr : 32] [count : 16 (→ int16_t)] [flags : 16]
DMA buffers: 32 → 4096 bytes = 128 KiB contiguous (pmm::alloc_n_frames)
Audio format: 48000 Hz, 16-bit signed, stereo (4 bytes / frame)
Buffer duration: 4096 / 4 / 48000 ? 21 ms per buffer
Ring capacity: 32 → 21 ms ? 680 ms
Playback flow
- Copy PCM data into DMA buffers (up to 32 per batch)
- Write BDL physical address to
NABM_PO_BDBAR - Set
NABM_PO_LVI= last filled buffer index - Set
NABM_PO_CRbit 0 (RPBM) → DMA starts - Poll
NABM_PO_SRbit 0 (DCH = halted) or yield viasched::yield() - Repeat for remaining data
Variable Rate Audio (VRA)
If the codec reports VRA support (NAM Extended Audio ID bit 0), the driver enables it and programs the front DAC sample rate register to 48000 Hz.
/dev/dsp
Registered as a VFS inode with custom FileOps. Writing raw PCM bytes calls ac97::play_pcm() directly → compatible with simple OSS-style audio programs.
Shell commands¶
| Command | Description |
|---|---|
beep [freq_hz] [ms] |
Play a square-wave beep; defaults: 880 Hz, 200 ms |
volume [0-100] |
Get or set master volume (maps to AC97 6-bit attenuation) |
play <file> |
Play a WAV or raw PCM file (16-bit stereo 48000 Hz) |
WAV support: the play command detects RIFF/WAV headers, validates 16-bit stereo format, and skips the standard 44-byte header automatically.
QEMU setup¶
# Makefile already adds -device AC97 to QEMU_FLAGS
make run
# Inside the OS:
beep # 880 Hz beep (tests audio output)
beep 440 1000 # A4 note for 1 second
volume 80 # reduce volume to 80%
play /disk/disk0/sounds/test.wav
Roadmap¶
Phase 1 -> Widget Toolkit & Screen System (M39->M40) ✅¶
Foundation of the entire GUI stack. Without this layer, no graphical component can exist.
| ID | Name | Key goals |
|---|---|---|
| M39 | Widget Toolkit (Intuition) | libui widgets and layouts used in production CoreDesk/Control Panel apps (button/label/input/list/dialog patterns) |
| M40 | Screen & Palette System | Backdrop/wallpaper, palette reload, runtime cursor (arrow/wait), runtime mode switch (twm_set_mode) without restart |
Phase 2 -> Workbench Core (M41->M43) ¶
Desktop with icons, drawers, and top-screen menus: the heart of the AmigaOS-style experience.
| ID | Name | Key goals |
|---|---|---|
| M41 | Icon System | Themed PNG/BMP icons with category fallbacks, transparent rendering, robust icon metadata parsing |
| M42 | Workbench Desktop | CoreDesk desktop: icons, drawer launch, taskbar/sysbar, context menu, dynamic "My stuff", reduced redundant redraws |
| M43 | Menu System | Global top-screen menu with multi-level submenus, disabled/check/radio items, Win/Ctrl+Esc activation, mouse+keyboard navigation |
Phase 3 -> System Applications (M44->M47)¶
Daily workflow tools: shell, preferences, multimedia viewer, and dock.
| ID | Name | Key goals |
|---|---|---|
| M44 | Preferences System | Single-window Control Panel: ScreenMode/Palette/Font/Sound/Input/Time/Startup + Default Apps, persisted to /disk/disk0/prefs/ |
| M45 | Shell Window (CLI) | PTY slave terminal window; ANSI escape codes; dynamic resize (TIOCSWINSZ); 1000-line scrollback; mouse copy/paste; window cloning (Amiga+K) |
| M46 | Dock / Application Bar | Application dock (left or bottom edge); icons with tooltips; launch bounce animation; auto-hide; config in prefs/dock.prefs |
| M47 | Multimedia Viewer | Datatypes: PNG (stb_image), BMP, PCM/WAV (AC97); MultiView -> extension-based preview; zoom fit/1:1/2x; waveform for audio files |
Phase 4 -> Polish & Tooling (M48->M50)¶
Visual polish, text editor, and system notifications.
| ID | Name | Key goals |
|---|---|---|
| M48 | GUI Text Editor | Syntax highlighting (C, Shell, INI); line numbers; undo/redo (200 ops); regex Find & Replace; tabs (tab bar) |
| M49 | Visual Theming | Window skins: Classic Amiga (gray), AmigaOS4 Blue (metal), Dark; window animations (zoom-in/out, fold); PNG desktop wallpaper; .theme format (ZIP) |
| M50 | Notifications & Clipboard | Notification banner (top-right, auto-hide after 4s); priorities: info/success/error; system clipboard; SYS_CLIPBOARD_COPY/PASTE; clipboard history (10 entries) |