-
Notifications
You must be signed in to change notification settings - Fork 0
Paging Implementation
JNode's x86 hardware paging setup: flat 4GB identity-mapped address space with null-pointer guard pages and kernel read-only protection.
JNode uses hardware paging on x86 to establish a flat, identity-mapped 4GB virtual address space. Paging is initialized entirely in assembly during early boot, before the JVM is active. The design choices prioritize simplicity (no virtual-to-physical translation overhead) and safety (guard pages at null and the high end of memory).
The 4GB virtual address space is laid out as follows:
| Virtual Range | Size | Page Size | Protection | Purpose |
|---|---|---|---|---|
0x00000000 |
4KB | 4KB | Not present | Null-pointer guard |
0x00001000–0x003FFFFF
|
4MB | 4KB | RW (user) / RO (kernel) | Low memory, kernel image |
0x00400000–0xFFBFFFFF
|
~4080MB | 4MB | RW (user) / RO (kernel) | Main address space |
0xFFC00000–0xFFFFFFFF
|
4MB | 4MB | Not present | High-end guard |
The 64-bit setup uses a 4-level page table hierarchy (PML4 → PDP → PD → PT) with PAE enabled:
| Structure | Physical Address | Entries |
|---|---|---|
| PML4 | 0x1000 |
512 |
| PDP | 0x2000 |
512 |
| PD0–PD3 |
0x3000–0x6000
|
512 each |
| PT0 (low 2MB) | 0x7000 |
512 |
The first 2MB (0x00000000–0x001FFFFF) is mapped via PT0 using 4KB pages (with page 0 cleared as a guard). The remaining addressable space uses 2MB pages via the PD tables.
| File | Role |
|---|---|
core/src/native/x86/mm32.asm |
32-bit page table initialization, enable_paging
|
core/src/native/x86/mm64.asm |
64-bit PML4/PDP/PD/PT setup, long-mode transition |
core/src/native/x86/kernel.asm |
Calls Lsetup_mm after multiboot, before IDT |
core/src/core/org/jnode/vm/memmgr/ |
GC heap managers (Default, MMTk) built above paging |
core/src/core/org/jnode/vm/MemoryBlockManager.java |
Runtime physical page block allocation |
The setup code in mm32.asm performs these steps:
; 1. Fill Page Directory with 4MB pages (PF_DEFAULT | PSE flag)
mov eax, PF_DEFAULT | iPF_PSE
mov edi, pd_paddr ; 0x1000
mov ecx, 1024
pd_lp:
stosd
add eax, 0x400000 ; next 4MB
loop pd_lp
; 2. Fill first page table (pg0) with 4KB identity-mapped entries
; Kernel regions marked RO; user regions marked RW
mov edi, pg0_paddr ; 0x2000
...
; 3. Clear first entry (page 0) to create null-pointer guard
and dword [edi], 0
; 4. Mark last PDE (entry 1023) as not present
and dword [pd_paddr + (1023*4)], 0
; 5. Enable paging
mov eax, pd_paddr
mov cr3, eax ; Load page directory
mov eax, cr4
or eax, CR4_PSE ; Enable 4MB page size extension
mov cr4, eax
mov eax, cr0
or eax, CR0_PG ; Enable paging
mov cr0, eax; Clear all table structures
CLEAR_PAGE_TABLE pml4_addr ; 0x1000
CLEAR_PAGE_TABLE pdp0_addr ; 0x2000
CLEAR_PAGE_TABLE pd0_addr ; 0x3000
; ... pd1, pd2, pd3, pt0 ...
; Link PML4 → PDP → PD0-PD3
SET_PT_ENTRY pdp0_addr, 0, PF_DEFAULT ; in PML4
SET_PT_ENTRY pd0_addr, 0, PF_DEFAULT ; in PDP
SET_PT_ENTRY pd1_addr, 0, PF_DEFAULT
SET_PT_ENTRY pd2_addr, 0, PF_DEFAULT
SET_PT_ENTRY pd3_addr, 0, PF_DEFAULT
; Fill PDs with 2MB pages (PF_DEFAULT | iPF_PSE)
mov edi, pd0_addr
SETUP_PDIR (PF_DEFAULT | iPF_PSE)
; ... same for pd1, pd2, pd3 ...
; Override first PD entry with 4KB page table for low 2MB
SET_PT_ENTRY pt0_addr, 0, PF_DEFAULT ; in pd0
SETUP_PTABLE PF_DEFAULT ; fill pt0 with 4KB entries
; Clear first entry of pt0 (null guard)
SET_PT_ENTRY 0, 0, 0 ; at pt0 startA page fault (interrupt 14) occurs when:
- A PDE or PTE has its Present (P) bit cleared
- A write is attempted to a Read-Only page
- A user-mode access is made to a User (U) bit-cleared page
JNode's page fault handler is dispatched via the IDT (see vm-ints.asm and Interrupt-Handling). The handler determines the faulting virtual address, checks the page table entry flags, and either:
- Grow the heap — if the fault is in the heap region and a new physical page can be allocated
-
Throw a
NullPointerException— if the faulting address is near zero (null dereference) -
Throw a
StackOverflowError— if the fault is in the guard page below a thread's stack -
Deliver a
SegmentationFault-style exception — for invalid high-memory accesses
The guard pages at virtual 0x00000000 and 0xFFC00000 catch both null-pointer dereferences and accesses to unmapped high memory, converting them into Java exceptions rather than CPU faults.
iPF_PRESENT equ 0x001 ; Page is present in memory
iPF_WRITE equ 0x002 ; Writable
iPF_USER equ 0x004 ; Accessible from user mode
iPF_ACCESSED equ 0x020 ; Has been accessed
iPF_DIRTY equ 0x040 ; Has been written to
iPF_PSE equ 0x080 ; 4MB/2MB page (PSE bit in PDE/PTE)
iPF_ADDRMASK equ ~0xFFF ; Mask to extract physical address
PF_DEFAULT equ iPF_PRESENT | iPF_WRITE | iPF_USER
PF_DEFAULT_RO equ iPF_PRESENT | iPF_USER- Identity mapping: Virtual addresses == physical addresses throughout the address space. No translation overhead, but also no isolation between processes.
- Single address space: Because paging does not remap addresses, JNode relies on Java type safety and the Isolate abstraction (not hardware page protection) for process isolation.
-
Null guard: The first 4KB page (32-bit) or first page entry in PT0 (64-bit) is deliberately left unmapped. Any
Object o = null; o.hashCode()pattern immediately triggers a page fault →NullPointerException. - Kernel read-only: The JIT-compiled kernel code and boot image are marked read-only in the page tables, catching accidental writes to code pages.
-
MMTk and paging: MMTk requests physical memory pages from
MemoryBlockManagerwhich operates above the paging layer. MMTk manages its own virtual-to-physical mapping tables internally. -
4MB page alignment: The 32-bit PDEs use
iPF_PSEfor 4MB pages throughout most of the address space. This is only valid when the physical memory being mapped is itself 4MB-aligned.
- Memory-Management — GC heap management built above paging
- Boot-Sequence — Where paging is initialized in the boot chain
- Assembly-Files — Overview of all x86 assembly files
- Interrupt-Handling — Page fault dispatch and exception delivery
- Architecture — How paging fits into the overall system