323 lines
10 KiB
Markdown
323 lines
10 KiB
Markdown
# NT-Lite -- an x86-64 kernel
|
||
|
||
## The Monster From ~~Redmond~~ Kraków
|
||
|
||
Boots. Runs processes. Mimics Windows to some extent.
|
||
|
||
## what is it?
|
||
|
||
NT-Lite is a 64-bit operating system kernel written from scratch. It includes a UEFI bootloader, physical and virtual memory managers, a preemptive scheduler, a HAL, a VFS, a PE loader, and partial Win32 and POSIX layers. It boots into a shell.
|
||
|
||
The codebase is roughly 20k lines of C and assembly.
|
||
|
||
## features
|
||
|
||
- UEFI bootloader
|
||
- physical memory manager
|
||
- virtual memory manager with kernel pool
|
||
- HAL for screen, keyboard, mouse, timer, disk, PCI, and APIC
|
||
- preemptive scheduler with priority levels
|
||
- processes and threads
|
||
- VFS with IRPs
|
||
- drive-letter paths such as `0:/apps/shell.exe`
|
||
- INI-based boot and driver configuration
|
||
- PE import resolution and dynamic driver loading
|
||
- partial Win32 personality
|
||
- partial POSIX personality
|
||
|
||
## architecture notes
|
||
|
||
### memory layout
|
||
|
||
NT-Lite uses a single virtual address space per booted system rather than a fully separate kernel address space and user address space. The distinction between kernel and user mode comes from page permissions and CPU privilege checks, not from having a different page table for the kernel.
|
||
|
||
The kernel is mapped into the higher half of virtual memory and user processes only receive the regions they are explicitly granted. Kernel pages are marked supervisor-only, so user-mode code cannot read or write them directly. User pages are mapped with user permissions and are isolated from one another by the kernel’s allocator, VMM, and process creation path.
|
||
|
||
### boot chain
|
||
|
||
The bootloader reads configuration, loads the core images, maps the higher-half layout, and hands control to `KernelMain`. After that, the kernel takes over interrupt setup, paging, scheduler bring-up, runtime driver loading, and user process launch.
|
||
|
||
### drivers
|
||
|
||
Drivers are PE32+ `.sys` files with a conventional `DriverEntry` export that accepts a pointer to `HAL_API`.
|
||
|
||
Drivers are split into two groups:
|
||
|
||
- essential boot drivers, which are loaded before the kernel starts doing real work
|
||
- runtime drivers, which are loaded later from disk once storage and the VFS are available
|
||
|
||
The driver model is intentionally small. Disk drivers expose a `DISK_DRIVER` vtable and register with `IoRegisterDisk(index, &driver)`. Filesystem drivers expose an `IO_FILESYSTEM` vtable and register with `IoRegisterFilesystem(index, &fs, &vol)`.
|
||
|
||
Currently shipping: AHCI (interrupts), IDE (PIO/DMA), and FAT32.
|
||
|
||
### I/O and filesystems
|
||
|
||
All file access flows through the VFS layer. That keeps disk access, filesystem parsing, and path translation separate instead of spreading storage logic across the rest of the kernel.
|
||
|
||
Paths are not interpreted as Windows paths by default. A drive such as `0:/` is a mounted namespace, while the Win32 and POSIX personalities translate their own path styles into the same underlying VFS.
|
||
|
||
### processes, threads, and syscalls
|
||
|
||
User processes are launched from PE images, imports are resolved before the first thread is queued, and the syscall layer provides the boundary between user code and kernel services. `syscall.dll` is the shared entry point for the supported personalities.
|
||
|
||
The scheduler is preemptive and priority-based, so threads do not depend on cooperative yielding to keep the system responsive. Process state is tracked separately from thread state, which allows a process to exit, be reaped, or be restarted by `init` without bringing down the rest of the system.
|
||
|
||
### personalities
|
||
|
||
The Win32 personality supports programs that expect drive letters, familiar process APIs, and a Windows-style runtime. The POSIX personality maps the same kernel primitives into a tree-shaped namespace and a different userspace contract.
|
||
|
||
## how it boots
|
||
|
||
1. UEFI loads `boot.ini` and reads `drivers.ini`
|
||
2. the bootloader maps the kernel, HAL, and boot drivers into the upper half
|
||
3. a direct physical window is mapped at `0xFFFF880000000000`
|
||
4. control passes to `KernelMain`
|
||
5. PMM, VMM, GDT, IDT, HAL, IRQs, and scheduler initialization run
|
||
6. runtime drivers are loaded from disk
|
||
7. `shell.exe` is launched
|
||
|
||
## how to build
|
||
|
||
- open the VS x64 build tools as administrator
|
||
- change into this directory
|
||
- run `build.bat`
|
||
- use one of `run.bat` (IDE), `rund.bat` (IDE, debug), `runa.bat` (AHCI), or `runad.bat` (AHCI, debug)
|
||
|
||
## paths
|
||
|
||
```text
|
||
"apps/shell.exe" -> current working directory
|
||
"0:/apps/shell.exe" -> drive 0
|
||
"2:/data/config.ini" -> drive 2
|
||
```
|
||
|
||
Drives 0-7 are available. A drive only works if a filesystem is registered on it. The Win32 personality translates drive letters to drive numbers, while the POSIX personality maps drives onto a tree.
|
||
|
||
## screenshots
|
||
|
||

|
||
|
||
<details><summary>Boot logs</summary>
|
||
|
||
```text
|
||
BdsDxe: loading Boot0001 "UEFI QEMU HARDDISK QM00005 " from PciRoot(0x0)/Pci(0x4,0x0)/Sata(0x0,0xFFFF,0x0)
|
||
BdsDxe: starting Boot0001 "UEFI QEMU HARDDISK QM00005 " from PciRoot(0x0)/Pci(0x4,0x0)/Sata(0x0,0xFFFF,0x0)
|
||
1
|
||
2
|
||
3
|
||
4
|
||
Loading colonel.exe...
|
||
Loading hal.dll...
|
||
5
|
||
6
|
||
7
|
||
8
|
||
Found RSDP at: 0xF77E014
|
||
9
|
||
HAL: screen 1280x800
|
||
HAL: FADT PM1a_CNT=0xb004 PM1b_CNT=0x0000 DSDT=0x0f77a000
|
||
HAL: _S5_ SLP_TYP_A=0x00 SLP_TYP_B=0x00
|
||
HAL: LAPIC=00000000fee00000 IOAPIC=00000000fec00000 HPET=00000000fed00000
|
||
HAL: lapicPhys=0xFEE00000, lapicVirt=0xFFFF8800FEE00000, hpetPhys=0xFED00000
|
||
HAL: Attempting to calibrate APIC timer via HPET...
|
||
HAL: APIC timer: 62981 ticks/ms
|
||
HAL: initialisation complete.
|
||
KernelMain: Handing over to scheduler...
|
||
SystemInitThread: Thread started. Igniting the system...
|
||
InitializeDrivers: Initializing drivers...
|
||
InitializeDrivers: Executing DriverEntry for ide.sys
|
||
IDE: Init
|
||
IDE: No drive on primary channel
|
||
InitializeDrivers: Driver ide.sys failed to init!
|
||
InitializeDrivers: Executing DriverEntry for ahci.sys
|
||
AHCI: driver loading...
|
||
AHCI: controller at PCI 00:04.0
|
||
AHCI: PCI IRQ line = 11
|
||
AHCI: ABAR phys=0x81060000 version=0x00010000
|
||
AHCI: cap=0xc0141f05 ports=6 slots=32 64bit=1
|
||
AHCI: port 0: link up, initializing...
|
||
AHCI: port COMRESET done: sig=0x00000101 ssts=0x00000113
|
||
AHCI: drive 0: 524288 sectors (256 MB)
|
||
AHCI: drive 0 registered
|
||
AHCI: port 1: no link (ssts=0x00000000)
|
||
AHCI: port 2: no link (ssts=0x00000000)
|
||
AHCI: port 3: no link (ssts=0x00000000)
|
||
AHCI: port 4: no link (ssts=0x00000000)
|
||
AHCI: port 5: no link (ssts=0x00000000)
|
||
AHCI: ISR registered on IRQ 11; initialised 1 drive(s)
|
||
InitializeDrivers: Executing DriverEntry for fat32.sys
|
||
FAT32: Init
|
||
FAT32: Init complete (status=0, clusters=128480).
|
||
Found runtime driver: usb.sys
|
||
Test runtime driver
|
||
SystemInitThread: Loading system DLLs...
|
||
Memory Status:
|
||
-> Free (PMM): 151.71 MB
|
||
-> Kernel Pool: 4.00 MB
|
||
-> User Heap: 0.55 MB
|
||
LaunchUserProcess: IoOpenFile: 0
|
||
LaunchUserProcess: IoGetFileSize: 5120
|
||
LaunchUserProcess: MmAllocateUserMemory: 0x8E020
|
||
LaunchUserProcess: IoReadFile: 0
|
||
LaunchUserProcess: IoCloseFile
|
||
Created process (PID 2) with command line: init.exe
|
||
LaunchUserProcess: PE Image mapped! Base 0x90020, Entry 0x91600
|
||
LaunchUserProcess: Thread created and queued!
|
||
[init] starting
|
||
[init] waiting
|
||
shell: ready
|
||
> help
|
||
Available commands:
|
||
help - Show this list
|
||
dir - List files
|
||
cd <path> - Change directory
|
||
type <file> - Display file contents
|
||
del <file> - Delete a file
|
||
write <file> <tx> - Create a file with text
|
||
exit - Close the shell
|
||
> dir
|
||
System Volume Information
|
||
drivers
|
||
efi
|
||
colonel.exe
|
||
hal.dll
|
||
syscall.dll
|
||
win32.dll
|
||
posix.dll
|
||
init.exe
|
||
shell_win32.exe
|
||
shell_posix.exe
|
||
shell_native.exe
|
||
doom.exe
|
||
video.exe
|
||
boot.ini
|
||
bink2w64.dll
|
||
video.bk2
|
||
doom1.wad
|
||
> shell_win32.exe
|
||
shell: ready
|
||
shell_win32.exe
|
||
> doom.exe
|
||
doom: starting
|
||
getenv: Unknown variable DOOMWADDIR
|
||
DOOM Shareware Startup v1.10
|
||
V_Init: allocate screens.
|
||
M_LoadDefaults: Load system defaults.
|
||
Z_Init: Init zone memory allocation daemon.
|
||
W_Init: Init WADfiles.
|
||
adding ./doom1.wad
|
||
===========================================================================
|
||
Shareware!
|
||
===========================================================================
|
||
M_Init: Init miscellaneous info.
|
||
R_Init: Init Doom refresh daemon - [.. ]
|
||
InitTextures
|
||
InitFlats........
|
||
InitSprites
|
||
InitColormaps
|
||
R_InitData
|
||
R_InitPointToAngle
|
||
R_InitTables
|
||
R_InitPlanes
|
||
R_InitLightTables
|
||
R_InitSkyMap
|
||
R_InitTranslationsTables
|
||
P_Init: Init Playloop state.
|
||
I_Init: Setting up machine state.
|
||
I_InitSound: I_InitSound: pre-cached all sound data
|
||
I_InitSound: sound module ready
|
||
D_CheckNetGame: Checking network game status.
|
||
startskill 2 deathmatch: 0 startmap: 1 startepisode: 1
|
||
player 1 of 1 (1 nodes)
|
||
S_Init: Setting up sound.
|
||
S_Init: default sfx volume 8
|
||
HU_Init: Setting up heads up display.
|
||
ST_Init: Init status bar.
|
||
doom: entering game loop
|
||
I_ShutdownSound: NOT finishing pending sounds
|
||
> shell_posix.exe
|
||
shell: ready
|
||
> help
|
||
Available commands:
|
||
help - Show this list
|
||
dir [path] - List directory contents
|
||
cd <path> - Change directory
|
||
type <file> - Display file contents
|
||
del <file> - Delete a file
|
||
write <file> <txt> - Create a file with text
|
||
exit - Close the shell
|
||
> dir
|
||
System Volume Information
|
||
drivers
|
||
efi
|
||
colonel.exe
|
||
hal.dll
|
||
syscall.dll
|
||
win32.dll
|
||
posix.dll
|
||
init.exe
|
||
shell_win32.exe
|
||
shell_posix.exe
|
||
shell_native.exe
|
||
doom.exe
|
||
video.exe
|
||
boot.ini
|
||
bink2w64.dll
|
||
video.bk2
|
||
doom1.wad
|
||
.doo
|
||
> video.exe
|
||
video: starting
|
||
video.exe
|
||
video: video.bk2 loaded
|
||
shell: goodbye
|
||
> exit
|
||
shell: goodbye
|
||
> exit
|
||
shell: goodbye
|
||
> exit
|
||
shell: goodbye
|
||
[init] done
|
||
ProcessWatcherThread: Process "init.exe" (PID 2) exited with code 0.
|
||
ProcessWatcherThread: Shutting down.
|
||
```
|
||
|
||
</details>
|
||
|
||
## faq
|
||
|
||
Q: Why?
|
||
A: Learning. Fun.
|
||
|
||
Q: Does it run Doom?
|
||
A: `doom.exe`
|
||
|
||
Q: How often does it crash?
|
||
A: Yes.
|
||
|
||
Q: Is the Win32 layer real?
|
||
A: Real enough to load binaries that expect it. Not real enough to run Notepad.
|
||
|
||
Q: Are processes isolated?
|
||
A: No. But IPC is free.
|
||
|
||
Q: Is this finished?
|
||
A: No.
|
||
|
||
Q: Will it corrupt my data?
|
||
A: It shouldn't.
|
||
|
||
Q: Why is the path syntax so weird?
|
||
A: It makes sense to me.
|
||
|
||
Q: Why are the logs so verbose?
|
||
A: How else do I debug it?
|
||
|
||
Q: What should I read first?
|
||
A: This document.
|
||
|
||
Q: Can I use this in production?
|
||
A: Don't.
|
||
|
||
#### Projekt dedykowany dla szanownego Pana profesora Leszka Wójcika przez którego znienawidziłem Unixa.
|