Skip to content

Boot Sequence

opencode-agent[bot] edited this page Jun 6, 2026 · 3 revisions

Boot Sequence

JNode boots from power-on through BIOS POST, an El Torito CD-ROM, GRUB Multiboot, x86 assembly setup, and into Java's Main.vmMain(), which initializes the VM and starts system plugins.

Overview

The boot sequence has six major phases:

  1. BIOS POST and CD-ROM Boot — Power-on, BIOS enumerates hardware, loads GRUB via El Torito
  2. GRUB Multiboot Load — GRUB decompresses and loads the kernel image into memory
  3. Protected Mode Assemblykernel.asm sets up CPU, paging, interrupts, FPU
  4. VM Entry — Transition to userspace and jump to Main.vmMain()
  5. VM InitializationVmSystem.initialize() brings up the Java runtime
  6. Plugin Startup — Plugin manager loads and starts system plugins

Phase 1: BIOS POST and CD-ROM Boot

BIOS POST

  1. CPU powers on in real mode (16-bit), jumps to reset vector (0xFFFFFFF0)
  2. BIOS runs Power-On Self-Test (POST): detects RAM, initializes chipset, enumerates PCI buses
  3. BIOS scans boot devices in configured order (hard disk, CD-ROM, floppy, network)
  4. For CD-ROM boot, BIOS loads the El Torito boot catalog from the ISO 9660 filesystem

El Torito → GRUB Stage 1

The ISO image (all/build/cdroms/jnode-*.iso) is built by the Boot-CD-ROM-Builder with this layout:

Component Location Purpose
El Torito boot image boot/grub/eltorito.s2 GRUB stage 2 in no-emulation mode
GRUB stage files boot/grub/ Stage 1.5, stage 2, FAT support
Boot menu boot/grub/menu.lst GRUB configuration
Compressed kernel /jnode32.gz or /jnode64.gz The Multiboot kernel image
Init JAR /default.jgz, /full.jgz, etc. Plugin JARs as GRUB modules

The BIOS reads eltorito.s2 from the CD-ROM into memory and transfers control to it. This is GRUB stage 2 (the core image), which:

  1. Loads its configuration from boot/grub/menu.lst
  2. Displays the boot menu (default entry: "JNode (default)")
  3. Processes the selected boot entry

GRUB Configuration

From all/conf/x86/menu-cdrom.lst:

title JNode (default)
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
kernel /jnode32.gz mp=no kdb lkd
module /default.jgz

Phase 2: GRUB Multiboot Load

GRUB uses the Multiboot protocol to load the kernel:

  1. Locates /jnode32.gz on the ISO filesystem
  2. Decompresses the gzip image — GRUB has built-in gzip decompression for Multiboot kernels
  3. Parses the ELF headers of the decompressed binary to determine load segments
  4. Loads the ELF segments into physical memory at the addresses specified in the ELF program headers
  5. Resolves the Multiboot header embedded in kernel.asm to find the entry point (real_start)
  6. Passes the init JAR (/default.jgz) as a Multiboot module — the second line in menu.lst
  7. Jumps to the entry point with:
    • EAX = 0x2BADB002 (Multiboot magic number)
    • EBX = pointer to the Multiboot info structure (memory map, module addresses, etc.)

NOTE: The gzip-compressed image (jnode32.gz) is created during the build when BootImageBuilder writes bootimage.bin and the Ant gzip task compresses it (all/build-x86.xml:225).

The Multiboot Header

In core/src/native/x86/kernel.asm:20-34:

mb_header:
    dd 0x1BADB002             ; Magic
    dd 0x00010007             ; Feature flags
    dd 0-0x1BADB002-0x00010007 ; Checksum
    dd mb_header              ; header_addr
    dd sys_start              ; load_addr
    dd 0                      ; load_end_addr (patched by BootImageBuilder)
    dd 0                      ; bss_end_addr (patched by BootImageBuilder)
    dd real_start             ; entry_addr

Master Assembly File

All assembly files are assembled as a single unit via the master include file core/src/native/x86/jnode.asm, which includes kernel.asm, cpu.asm, mm32.asm, ints.asm, console.asm, vm.asm, and all other .asm files. See Assembly-Files for the complete breakdown.

Phase 3: Protected Mode Assembly (kernel.asm)

Entry: sys_start → real_start

core/src/native/x86/kernel.asm:17-18:

sys_start:
    jmp real_start

The jmp at the entry point is necessary because the Multiboot header (aligned at offset 4) must appear immediately after the entry point.

real_start Initialization

core/src/native/x86/kernel.asm:36-206 performs the following steps in order:

  1. Set up kernel stackmov esp, Lkernel_esp (line 37)
  2. Clear screen via the CLEAR_SCREEN macro (line 39)
  3. Verify Multiboot magic — checks EAX == 0x2BADB002 (lines 41-43); jumps to no_multiboot_loader and halts if invalid
  4. Copy Multiboot info block — copies the multiboot structure from EBX to multiboot_info (lines 46-51)
  5. Parse command line — copies the kernel command-line string (lines 53-60)
  6. Parse memory map — copies the BIOS-provided memory map entries (lines 67-96)
  7. Copy VBE info — copies VESA BIOS Extensions data if available (lines 98-114)
  8. Locate init JAR — reads the Multiboot module address and records initJar_start/initJar_end (lines 117-138)
  9. Check A20 gate — verifies the A20 address line is enabled (lines 143-149); loops forever if not (common BIOS bug)
  10. Initialize kernel debugger — calls kdb_init (line 152)
  11. Print version string — displays the kernel version (line 155)
  12. Detect CPU features — calls test_cpuid (line 163)
  13. Setup memory manager (paging) — calls Lsetup_mm (line 168), which is defined in mm32.asm (32-bit) or mm64.asm (64-bit). Sets up GDT, page tables, and enables paging
  14. Setup interrupts — calls Lsetup_idt (line 176), building the Interrupt Descriptor Table
  15. Initialize FPU — calls init_fpu (line 181)
  16. Initialize SSE — calls init_sse if CPU supports SSE (line 183)

Transition to Userspace

core/src/native/x86/kernel.asm:187-206:

; Go into userspace
mov ABX, Luser_esp
mov ACX, go_user_cs
mov ASI, USER_DS
mov ADI, USER_CS
push ASI           ; old SS
push ABX           ; old ESP
pushf              ; old EFLAGS
push ADI           ; old CS
push ACX           ; old EIP
pushf
pop AAX
and eax, ~F_NT     ; Clear nested task flag
push AAX
popf
iret

The iret switches from kernel-mode segment selectors to user-mode segments (USER_CS = 0x1B, USER_DS = 0x23) and jumps to go_user_cs.

go_user_cs

core/src/native/x86/kernel.asm:212-257:

  1. Loads user-mode segment registers (ds, es, gs, fs)
  2. Sets up the kernel stack end for the boot processor
  3. Enables interrupts (sti)
  4. Sets up a minimal stack frame (ABP is the frame pointer)
  5. Loads the address of vm_start into EAX
  6. Adds BootImageBuilder_JUMP_MAIN_OFFSET32 — this offset is the size of an object header, calculated so the jump target lands at Main.vmMain() inside the boot image
  7. Calls via AAX

Phase 4: VM Entry

vm_start

The label vm_start at core/src/native/x86/jnode.asm:162 is a 4KB-aligned placeholder. During the build, BootImageBuilder (builder/src/builder/org/jnode/build/x86/BootImageBuilder.java) writes a reference into the boot image so that:

vm_start + JUMP_MAIN_OFFSET32 = Main.vmMain()

The offset (JUMP_MAIN_OFFSET32) equals the size of a Java object header (header slots + alignment), ensuring the first Java method in the image is reached.

Main.vmMain()

core/src/core/org/jnode/boot/Main.java:

@LoadStatics
@Uninterruptible
public static int vmMain() {
    Unsafe.debug("Starting JNode\n");
    ...
    VmSystem.initialize();
    ...
}

This is the first Java method ever executed on JNode. It is annotated with:

  • @LoadStatics — static fields must be initialized at runtime, not from build-time values
  • @Uninterruptible — must not trigger GC safepoints during early boot

vm.asm — VM Runtime Support

core/src/native/x86/vm.asm provides native runtime support for the VM. While not part of the early boot path per se, it contains the machinery that is called once Java code starts running:

Routine Role
vm_athrow Exception throw handler — walks the stack, calls SoftByteCodes.findThrowableHandler
vm_print_string Print a java.lang.String to the console (used by Unsafe.debug())
vm_print_chararray Print a char[] to the VGA console
vmint_print_stack Low-level stack trace printer for unhandled interrupts

vm.asm is included into jnode.asm at line 139, alongside vm-invoke.asm (Java method invocation), vm-ints.asm (interrupt bridge), and vm-jumptable.asm (bytecode dispatch table). These files form the native half of the VM interpreter.

Phase 5: VM Initialization

VmSystem.initialize()

File core/src/core/org/jnode/vm/VmSystem.java

Called from Main.vmMain(). Sets up:

  1. Boot loggingBootLogInstance for early console output via Unsafe.debug()
  2. System classloaderVmSystemClassLoader with pre-loaded boot classes
  3. System propertiesjava.home, os.name, etc.
  4. Memory manager — Heap initialization, GC setup
  5. SchedulerVmScheduler starts the idle thread and timer
  6. I/O system — Console streams (System.out, System.err)
  7. Log4j — Sets up logging via ConsoleAppender

Phase 6: Plugin Startup

Main.vmMain() (continued)

After VmSystem.initialize():

  1. Load initjarInitJarProcessor extracts plugin JARs and descriptors from the init-jar (the Multiboot module passed by GRUB)
  2. Create PluginManagerDefaultPluginManager manages the plugin lifecycle
  3. Start system pluginspiMgr.startSystemPlugins(descriptors) activates all plugins in dependency order
  4. Launch main class — Reads Main-Class from the plugin list manifest (typically org.jnode.shell.CommandShell) and invokes its main() method

Plugin Loading Order

Plugins are started in dependency order. The default plugin list (all/conf/default-plugin-list.xml) specifies:

  1. Core classpath extensions (org.classpath.ext.*)
  2. Driver framework (org.jnode.driver)
  3. Block devices and partitions
  4. Filesystem service
  5. Console and input drivers
  6. Shell and commands
  7. Network stack (if included)
  8. GUI (if included)

Key Annotations in Boot Path

Annotation Effect
@Uninterruptible Method must not be interrupted (no GC safepoints)
@LoadStatics Static fields are initialized at boot time, not build time
@SharedStatics Static fields are shared across all isolates
@KernelSpace Method runs in kernel context

Main.vmMain() is annotated with both @LoadStatics and @Uninterruptible because it runs before the scheduler and GC are fully operational.

Gotchas & Non-Obvious Behavior

  • No start32.asm — Earlier JNode versions may have had a separate start32.asm for the protected mode transition, but the current codebase does everything in kernel.asm. The protected mode entry point is real_start in core/src/native/x86/kernel.asm:36.
  • Build time vs. boot timeBootImageBuilder pre-compiles classes and serializes static fields into the image. @LoadStatics marks fields that should be re-initialized at actual boot rather than using build-time values.
  • pluginRegistryMain.pluginRegistry is set by BootImageBuilder.initMain() during image creation, not at runtime.
  • No exceptions before VmSystem.initialize() — The exception handling machinery isn't ready until the VM is initialized. Early boot code uses Unsafe.debug() for output.
  • The initjar — This is a nested archive inside the boot image containing all plugin JARs. It's created by the build process and accessed via VmSystem.getInitJar().
  • Kernel image compression — The kernel is gzip-compressed as jnode32.gz (or jnode64.gz). GRUB decompresses it before loading. There is no custom decompression routine in JNode's assembly code.
  • A20 gate loop — If A20 is not enabled (lines 143-149 of kernel.asm), the kernel loops forever. This is a known BIOS issue on some hardware.

Related Pages

Clone this wiki locally