Skip to content

Boot Process

kazah-png edited this page Jul 26, 2026 · 6 revisions

Boot Process

From GRUB handing off control to a logged-in desktop. Every stage below is real code you can follow: kernel/boot.asm for stages 1–2, kernel_main() in kernel/kernel.c for stages 3–6.

See also: Architecture, Memory Management, SMP, GUI Subsystem, Security

Stage 1 — GRUB

boot.asm publishes two Multiboot headers: a Multiboot 1 header (0x1BADB002) kept for compatibility, and a Multiboot 2 header (0xE85250D6), which is what the generated grub.cfg actually uses:

menuentry 'NyxOS' {
    multiboot2 /boot/nyx-kernel.bin
    boot
}

GRUB loads the kernel, leaves the CPU in 32-bit protected mode, and jumps to _start with the boot magic in EAX and the info-structure pointer in EBX. Both are stashed immediately, because the long-mode climb clobbers general registers.

Stage 2 — Protected mode → long mode (boot.asm)

  1. cli; save EAX/EBX (Multiboot magic and info pointer)
  2. Set up a minimal 32-bit GDT
  3. Confirm long-mode support via CPUID leaf 0x80000001
  4. Enable PAE in CR4
  5. Build temporary page tables — PML4 → PDPT → two page directories covering the low 128 MB with 2 MB pages
  6. Load CR3
  7. Set EFER.LME
  8. Enable paging (CR0.PG) — the CPU is now in long mode
  9. Load the 64-bit GDT (ring-0 code/data + ring-3 code/data)
  10. Far jump to reload CS, then load the data segment registers
  11. Point RSP at the 128 KB .bss boot stack
  12. Zero BSS and call kernel_main(magic, mboot_ptr)

The boot stack is 128 KB and is placed before the page tables in .bss. It was 16 KB and sat directly below them, so a deep compositor redraw chain could overflow into the live page tables and corrupt them — a bug that presented as crashes at unrelated addresses.

Stage 3 — kernel_main()

Initialisation runs in a fixed order, because each step depends on the ones above it. The 23-step progress bar on the splash screen is driven by bootsplash_update() calls threaded through this sequence.

Early — no memory manager yet

init_screen() → clear_screen() → init_gdt() → init_idt() → init_isr()
→ init_irq() → init_serial() → enable_sse_fpu()

Memory

The Multiboot info block is parsed here. For Multiboot 2 the memory-map tag is copied out entry by entry (base, length, type) and handed to the allocator, so the physical allocator honours real firmware holes instead of assuming a flat region.

init_memory(mem_total, mmap, mmap_count) → init_paging() → enable_smep_smap()

Core services

init_apic() → init_heap() → slab_init_all() → smp_init()

smp_init() runs here — before the timer — because the IST allocation below needs to know how many cores are online.

Display

vbe_init() → fb_init(w, h, bpp, lfb) → bootsplash_init()

If no VBE framebuffer can be set, the kernel stays in text mode and later drops to the serial shell instead of the desktop.

Per-CPU exception stacks

One double-fault and one NMI IST stack per online core (8 KB each), so a fault on one core cannot scribble on another core's exception stack.

Scheduling, syscalls, filesystems

init_timer(1000) → init_keyboard() → init_process() → ensure_idle_process()
→ smp_start_ap_threads() → init_syscalls() → cpu_install_gs_base(0)
→ setup_syscall_msrs() → [allocate syscall kernel stack]
→ init_vfs() → init_load_modules() → init_ext2()

cpu_install_gs_base() must run before anything can issue a syscall: the entry stub reaches its per-CPU scratch slots through GS and has no spare register to find them any other way.

Network

init_net() → tcp_init() → tcp_echo_init() → [auto-DHCP if a NIC is present]

Auto-DHCP leases an address at boot so ping, httpget and Selene Browser work without a manual dhcp. It is skipped entirely when no NIC is present, so a diskless/netless boot never stalls.

Devices and interrupts

init_background_tasks() → mouse_init() → speaker_init() → sb16_init()
→ rtc read → irq_install_handler(1, 5, 12)
→ ioapic_redirect_irq(2, 32, apic_id) → ioapic_unmask_irq(2)
→ irq_unmask(1|5|12) → sti

The PIT arrives on I/O APIC pin 2, not pin 0 — QEMU's ACPI interrupt-source override remaps ISA IRQ 0. Unmasking pin 0 was a no-op, which is why tick_count stayed at zero for several releases.

Userspace

initramfs_load() → initramfs_boot() → shared_libc_load() → [register /init.elf]
→ ext2 probe/mount at /mnt

shared_libc_load() maps the single prelinked libc.so that every user program links against (see Userspace).

Stage 4 — Boot splash

Roughly five seconds: the NyxOS crescent-moon logo, a starfield, a spinner, and a 23-step progress bar whose captions come from the bootsplash_update() calls above. Fades to black before the login screen.

Stage 5 — Login

auth_setup() then login_screen(), drawn straight to the linear framebuffer (no back buffer). The screen offers a username/password card and a create-account panel with profile-picture selection.

  • Credentials live in /etc/passwd on the EXT2 disk, one line per user: user:salt_hex:iterations:hex_hash:avatar
  • Hashing is PBKDF2-HMAC-SHA256, 10 000 iterations, with a random 8-byte per-user salt
  • LOGIN_MAX_ATTEMPTS is 3, after which the screen enters a lockout cooldown
  • With no EXT2 disk attached, an in-memory fallback account is used so the machine still boots

See Security for the threat model.

Stage 6 — Desktop

create_process("compositor", compositor_run, 0)   // registered with the scheduler
loop:
    fb_use_lfb_direct(); login_screen()
    compositor_init(); compositor_run()
    if (!compositor_logout_requested) break

The compositor comes up with 10 desktop icons, a taskbar, a Start menu and 4 workspaces. Choosing Log out from the taskbar user menu returns to the login screen without rebooting — compositor_init() frees the previous session's windows first, so the next user starts on a clean desktop.

If no framebuffer was available, launch_shell() runs the kernel shell over serial instead. See Shell.

See also

  • Building - producing the ISO GRUB boots
  • Debugging - reading a boot log, and what a stalled stage means
  • SMP - application-processor bringup
  • Security - the login and credential model

External resources

Clone this wiki locally