|
miniOS
x86_64 hobby kernel with SMP, VFS, and POSIX process model
|
miniOS supports symmetric multiprocessing with up to MAX_CPUS=8 CPUs. The SMP subsystem consists of four layers: CPU discovery, AP boot, per-CPU state management, and inter-processor communication.
acpi_find_madt() locates the ACPI MADT to enumerate all CPUs:
The BSP is always at smp_lapic_ids[0] (index 0). smp_cpu_count holds the total count including BSP.
smp_boot_aps() starts all non-BSP CPUs using the standard INIT-SIPI-SIPI protocol:
The BSP calls smp_wait_for_aps() after smp_boot_aps() and blocks until all APs have checked in (polling smp_aps_ready).
Each CPU has a cpu_t struct in the global g_cpus[] array:
The IA32_GS_BASE MSR on each CPU points to &g_cpus[cpu_id]. The cpu_local() macro reads gs:0 to obtain a pointer to the calling CPU's cpu_t in a single instruction:
This design was chosen because it requires no explicit cpu_id argument and works correctly regardless of how many CPUs are active.
miniOS uses a FIFO ticket spinlock (spinlock_t) to protect shared kernel data structures from concurrent access across CPUs:
| Protected resource | Lock variable |
|---|---|
| PMM frame allocator | pmm_lock (in pmm.c) |
| Kernel heap | heap_lock (in heap.c) |
| VMM page table | vmm_lock (in vmm.c) |
| Thread pool + run queues | thread_pool_lock (in sched.c) |
IRQ-safe locking: Because PMM and scheduler data can be accessed from both normal kernel context and the LAPIC timer ISR, all locks use spinlock_irqsave/ spinlock_irqrestore which disable local interrupts before acquiring.
The ticket algorithm guarantees FIFO ordering: each waiter atomically claims a ticket (incrementing next[15:8]), then spins until owner[7:0] reaches its ticket. Supports up to 256 simultaneous waiters (well above MAX_CPUS=8).
Each CPU's cpu_t holds an independent circular run queue (FIFO, intrusive linked list via thread->next). The per-CPU idle thread is always in the queue as the circular sentinel — it is never removed.
Enqueueing: sched_balance_enqueue() tail-inserts a new thread onto the CPU with the smallest queue_depth. If the target differs from the calling CPU, a scheduler-kick IPI (SCHEDULER_KICK_VECTOR=50) wakes the target from hlt.
Work-stealing: When an AP's idle thread runs and finds queue_depth == 0, it calls sched_work_steal() which:
Two IPI channels are implemented (both using lapic_send_ipi()):
When sys_munmap unmaps a page, ipi_tlb_shootdown(virt) broadcasts to all other CPUs. Each CPU's tlb_shootdown_isr executes invlpg(g_tlb_barrier.virt), then calls ipi_barrier_ack() (decrement-before-EOI guarantee), then sends EOI. The initiator blocks in ipi_barrier_wait() until g_tlb_barrier.ack_count == 0.
kernel_panic() calls ipi_panic_halt() which broadcasts PANIC_HALT_VECTOR=52 to all APs. Each AP's panic_halt_isr executes cli; hlt. No EOI is sent — the halted CPU never needs LAPIC unblocked. The BSP then prints the panic message and halts with cli; hlt as well.