I want to summarize xv6’s paging. In the kernel main function, the page table and page directory initialization is performed twice.

The first initialization of the paging mechanism maps the lower 4MB of virtual addresses 0x0 ~ 0x400000 directly to physical addresses 0x0 ~ 0x400000. Also, virtual addresses 0x80000000 ~ 0x80400000 with 0x80000000 as an offset are also mapped to the same physical addresses 0x0 ~ 0x400000. This paging is only used during kernel initialization. By enabling CR4’s Page Size Extension (4th bit), the page size is expanded to 4MB (super pages). With super pages, only the page directory is used without page tables. The upper 10 bits of the virtual address correspond to the page directory index, and the lower 10 bits correspond to the offset within the 4MB page. In other words, single-level paging. The kinit1 function executes kfree on pages from the end of the kernel to 4MB (0x801154a8 ~ 0x80400000), and links them to the freelist (a linked list of free memory).

Next, in the second initialization, it targets virtual addresses exceeding 4MB. This doesn’t use super pages, so the page size remains 4KB, and both the page directory and page tables are used. In other words, two-level paging. The kinit2 function links pages from 0x80400000 ~ 0x8e000000 to the freelist.

This time, there was a bug in the second initialization, so it was writing to an invalid address in kinit2. I reviewed the page walk in the two-level paging mechanism and resolved it. Note that the upper 20 bits of CR3 point to the base physical address of the page directory, but that address is not CR3 >> 20 but CR3 & 0xFFFFF000. The same applies to page directory entries. In other words, page directories and page tables are aligned to 4KB.