When initializing my kernel, I have a few things that need to happen: 1) paging needs to be enabled, 2) the physical memory manager needs to parse the memory map from grub, and 3) assorted startup code needs to access data that needs to stay there for later (e.g. the GDT, IDT, memory management structures).
The dependencies between these steps are driving me crazy. With higher-half, the kernel is linked at its virtual address and so the options I've come up with are 1) enable paging in assembly, which would involve following all the multiboot pointers (in assembly) so they'll still be accessible to the physical memory manager and then later unmapping them all, 2) link the startup code at its physical address and then do some pointer manipulation to access kernel structures at their physical addresses as well, or 3) don't use a higher-half kernel.
Also involved is bootstrapping the physical memory manager without knowing the amount of physical memory at compile time. I'm pretty sure I have to either carefully avoid all the multiboot structures when allocating the first structures, or use them all first and then don't worry about overwriting them (although I'd still have to deal with modules and this approach probably involves copying the multiboot tables to a known location as I need them while setting up the physical memory manager).
These problems are why I've avoided a higher half kernel up to now. Does anyone have a good system for resolving these dependencies? Maybe some variation on this GDT trick to access both the kernel at its linked/virtual address and the multiboot tables at their physical address, or using some kind of pre-defined page tables that avoid the problems above, maybe involving PSE?
This is how I tackled this problem:
My kernel image is loaded by GRUB at (physical) address 0x01000000 (16MB, just above the ISA DMA region). This image basically consists of two parts:
- An "early init" section. This section contains code that is executed to prepare to jump to the higher half kernel. I also reserve some space in this section for a stack and a heap used during this preparation. All code in this section is linked at (virtual) address 0x01000000.
- The rest of the image contains code and data that is part of the higher half kernel. All code in this part is linked at (virtual) address 0xc0000000 (3GB).
Since the code in the early init section is linked at the same address as where it is loaded, GRUB can jump into this code without any problems. This early init code performs the following steps:
- Relocate the MBI structure that GRUB passes to the kernel. The heap inside the early init section is used for this.
- Identity map all pages starting at physical address 0x0 up to the physical address of the last page used by the early init section. Identity mapping means that the virtual addresses are the same as the physical addresses. This makes sure that the code in the early init section can still be executed after paging is enabled.
- Map the higher half kernel at virtual address 0xc0000000.
- Enable paging.
- Jump into the higher half kernel.
At this point the rest of the initialization is done from higher half code. This includes setting up the GDT, IDT, memory management,... Note that the MBI is relocated to a well-known location so you shouldn't have to worry about overwriting it with your own data structures.
A small word about the physical memory manager: What I do is calculate the amount of pages needed for my data structures, allocate these structures starting at the first page after the kernel image and start dealing pages starting after these data structures.
I hope this explanation is clear. If it's not, please let me know. I could also provide you with a copy of my kernel if you'd like that.