Hello everyone,
I want to start paging early in my OS, and it's the first thing I do in my kernel right now (using the GDT trick from the wiki). It works fine, but now I need to read the memory map grub gave me, before I can start writing a memory allocator. But I can't easily read the memory map - because it may be in a page over 4 MB (I mapped the first 4MB to the first 4MB of physical memory). So to read the memory map, I would need to add temporary pages for the kernel. But to do that, I would need to allocate memory (not really, but to do it nicely I would), which I can't, nicely, because I don't have a memory map yet!
Currently the best way seems to copy the memory map to directly after the kernel, assume it fits in the first 4 MB (can't imagine it ever wouldn't), and read it from there. Of course, I'd have to be extremely careful I don't accidentally overwrite other multi boot info like that, because it might be immediately after the kernel.
It seems like a sort of circular dependency for which there can't be a very nice solution. It wouldn't be too complex if I'd assume a maximum size for the memory map and simply pre-allocate that size in the kernel. However, all these solutions just feel like a hack.
Is there a simpler way to do this? Or is there a certain assumption that is safe to make about the multi boot info, for instance that it's in the first 4 MB of physical memory?
Thanks in advance
Best way to start paging early
Re: Best way to start paging early
Hi,
Given that you know the physical location of the memory map (from GRUB), why not use it to perform you initial allocations (before paging is enabled)? If you want the memory map immediately after your kernel, you can always use the GRUB memory map itself to check that any assumptions are valid.
Cheers,
Adam
Given that you know the physical location of the memory map (from GRUB), why not use it to perform you initial allocations (before paging is enabled)? If you want the memory map immediately after your kernel, you can always use the GRUB memory map itself to check that any assumptions are valid.
Cheers,
Adam
Re: Best way to start paging early
Thanks for your answer. I have considered it, but I think it would involve writing two separate memory allocation functions. Though come to think of it - one could do it just fine as long as I create identity pages for the pages the memory map is in as well.AJ wrote:Hi,
Given that you know the physical location of the memory map (from GRUB), why not use it to perform you initial allocations (before paging is enabled)? If you want the memory map immediately after your kernel, you can always use the GRUB memory map itself to check that any assumptions are valid.
Cheers,
Adam
Thanks
Re: Best way to start paging early
Hi,
You can't safely assume anything about where the multi-boot information might be, or where any modules you've asked GRUB to load might be. Also, because these things aren't mentioned in the memory map (e.g. any area reported as "usable RAM" in the memory map may contain the multi-boot information, your "kernel", or any modules) you can't even safely use RAM reported as "usable RAM" in the memory map (without extra checking to determine if "usable RAM" isn't already in use).
Note: The specification also doesn't guarantee that the multi-boot information will all be in one place. The "Multiboot information structure" could be at 0x12345678, the "cmndline" could be at 0x87654321, the memory map could be at 0x01020304, the "vbe control info" could be at 0x33333333, the "vbe mode info" could be at 0x43434343, etc. Checking if an area reported as "usable RAM" is actually usable (and isn't already in use) wouldn't be easy.
In addition, multi-boot doesn't guarantee that the "memory map" exists. There may be no memory map and you may have to use the "mem_lower" and "mem_upper" fields instead (it doesn't guarantee that those fields exist either - if there's no memory map and no "mem_lower" and "mem_upper" you're in trouble).
If a memory map does exist, multi-boot doesn't guarantee that it is sane (most boot loaders just copy the data the BIOS provides with no sanity checks whatsoever). This means the memory map might contain entries with "size = 0", might contain overlapping areas of the same type, might contain overlapping areas of different types, and might contain entries for adjacent areas of the same type (e.g. a 20 MiB area of "usable RAM" might be described as 20 small 1 MiB areas that could be combined into one entry but weren't).
To solve all of these problems, I'd reserve space for a new memory map in your kernel's ".bss" (space for 256 entries should be enough); and then use the memory map provided by the boot loader (or the "mem_lower" and "mem_upper" fields if there was no memory map) to initialise your own memory map, including sorting entries (so they're in order from "lowest base address" to "highest base address"), checking for overlapping areas, combining adjacent areas of the same type, removing "zero size" areas, etc. I'd also add new entries for RAM used by the kernel and any modules to your memory map. Then I'd copy anything else you need from the multi-boot information into your ".bss" (so you don't need to add any "used by multi-boot information" entries in you sanitized memory map to ensure that "usable RAM" actually means RAM that is usable).
Once you've constructed a sanitized memory map (and stored anything else you need from the multi-boot information), you can use the sanitized memory map for dynamically allocating RAM (e.g. by splitting "usable RAM" entries and creating "used by kernel" entries). Alternatively you could use the sanitized memory map to initialise a proper physical memory manager (and then use the physical memory manager to allocate pages to use for page directories, page tables, etc).
Of course this is an annoying pain in the neck. Wouldn't it be nice if the kernel started with paging enabled; with the kernel mapped to 0xC0000000 or something, with a pre-sanitized memory map mapped at some fixed virtual address (maybe mapped to 0x40000000) and any other information the kernel might want mapped to another fixed virtual address (maybe at 0x00000000)? That would simplify things for the kernel a lot. It's also entirely possible - imagine if GRUB loaded a "stage 2" as a kernel instead of the real kernel, and loaded the real kernel as a "module". In this case your "stage 2" code could sort out things like the memory map and multi-boot information, and initialise/enable paging before it passes control to the real kernel.
Cheers,
Brendan
Strictly speaking (if your OS complies with multi-boot, and isn't restricted to specific version/s of GRUB), you can't assume anything that the multi-boot specification doesn't guarantee, and the multi-boot specification doesn't guaranteed anything.evoex wrote:Or is there a certain assumption that is safe to make about the multi boot info, for instance that it's in the first 4 MB of physical memory?
You can't safely assume anything about where the multi-boot information might be, or where any modules you've asked GRUB to load might be. Also, because these things aren't mentioned in the memory map (e.g. any area reported as "usable RAM" in the memory map may contain the multi-boot information, your "kernel", or any modules) you can't even safely use RAM reported as "usable RAM" in the memory map (without extra checking to determine if "usable RAM" isn't already in use).
Note: The specification also doesn't guarantee that the multi-boot information will all be in one place. The "Multiboot information structure" could be at 0x12345678, the "cmndline" could be at 0x87654321, the memory map could be at 0x01020304, the "vbe control info" could be at 0x33333333, the "vbe mode info" could be at 0x43434343, etc. Checking if an area reported as "usable RAM" is actually usable (and isn't already in use) wouldn't be easy.
In addition, multi-boot doesn't guarantee that the "memory map" exists. There may be no memory map and you may have to use the "mem_lower" and "mem_upper" fields instead (it doesn't guarantee that those fields exist either - if there's no memory map and no "mem_lower" and "mem_upper" you're in trouble).
If a memory map does exist, multi-boot doesn't guarantee that it is sane (most boot loaders just copy the data the BIOS provides with no sanity checks whatsoever). This means the memory map might contain entries with "size = 0", might contain overlapping areas of the same type, might contain overlapping areas of different types, and might contain entries for adjacent areas of the same type (e.g. a 20 MiB area of "usable RAM" might be described as 20 small 1 MiB areas that could be combined into one entry but weren't).
To solve all of these problems, I'd reserve space for a new memory map in your kernel's ".bss" (space for 256 entries should be enough); and then use the memory map provided by the boot loader (or the "mem_lower" and "mem_upper" fields if there was no memory map) to initialise your own memory map, including sorting entries (so they're in order from "lowest base address" to "highest base address"), checking for overlapping areas, combining adjacent areas of the same type, removing "zero size" areas, etc. I'd also add new entries for RAM used by the kernel and any modules to your memory map. Then I'd copy anything else you need from the multi-boot information into your ".bss" (so you don't need to add any "used by multi-boot information" entries in you sanitized memory map to ensure that "usable RAM" actually means RAM that is usable).
Once you've constructed a sanitized memory map (and stored anything else you need from the multi-boot information), you can use the sanitized memory map for dynamically allocating RAM (e.g. by splitting "usable RAM" entries and creating "used by kernel" entries). Alternatively you could use the sanitized memory map to initialise a proper physical memory manager (and then use the physical memory manager to allocate pages to use for page directories, page tables, etc).
Of course this is an annoying pain in the neck. Wouldn't it be nice if the kernel started with paging enabled; with the kernel mapped to 0xC0000000 or something, with a pre-sanitized memory map mapped at some fixed virtual address (maybe mapped to 0x40000000) and any other information the kernel might want mapped to another fixed virtual address (maybe at 0x00000000)? That would simplify things for the kernel a lot. It's also entirely possible - imagine if GRUB loaded a "stage 2" as a kernel instead of the real kernel, and loaded the real kernel as a "module". In this case your "stage 2" code could sort out things like the memory map and multi-boot information, and initialise/enable paging before it passes control to the real kernel.
Cheers,
Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Re: Best way to start paging early
Hi,
Absolutely agree with what Brendan says, with one modification:
Cheers,
Adam
Absolutely agree with what Brendan says, with one modification:
Assuming you're using GRUB/2 and you're on a fairly modern (last 10 years?) pc, you're going to get the memory map passed to the kernel. My preference would be to panic if the memory map is not passed, rather than attempting to go by mem_lower and mem_upper. Surely just using these 2 values is going to require a certain amount of additional probing to build the actual memory map that the kernel uses?Brendan wrote:In addition, multi-boot doesn't guarantee that the "memory map" exists.
Cheers,
Adam
Re: Best way to start paging early
By means of linker script I do make my kernel's .bss section intentionally large (128KiB IIRC). Bootloader honors it and will not put anything there. A link-time symbol helps to determine, where actual .bss ends and where my "fake" starts.
I spend a few pages from it here and there (stack, page directory, etc.). Then I seed my kmalloc() with the rest of it. From this point on I can use (almost) fully functional malloc()-style allocation. The "almost" part being - heap is not yet able to expand past those 116-or-so KiB, but I've never ran into issues with it.
I spend a few pages from it here and there (stack, page directory, etc.). Then I seed my kmalloc() with the rest of it. From this point on I can use (almost) fully functional malloc()-style allocation. The "almost" part being - heap is not yet able to expand past those 116-or-so KiB, but I've never ran into issues with it.
If something looks overcomplicated, most likely it is.
Re: Best way to start paging early
Thanks guys... It's kind of what I was afraid of. Copying everything into .bss it is...
Re: Best way to start paging early
The bootloader I use does almost all of that. It starts your kernel right in 32-bit protected mode, or even 64-bit long mode, with paging enabled, your kernel mapped to it's proper virtual address, and a sanitized memory map. The memory map is slightly modified from standard E820 format, though. It has extra memory types for memory used by the bootloader itself. For example, the page tables set up by the bootloader, the boot tags, and the space used by the kernel itself. With a tiny little hack that I added it even sets up the page directory so it is recursively mapped. And with some pre-processor macros when you compile your kernel, you can instruct the bootloader to set up extra virtual mappings for you, select a video mode, and more.Brendan wrote:Hi,
Of course this is an annoying pain in the neck. Wouldn't it be nice if the kernel started with paging enabled; with the kernel mapped to 0xC0000000 or something, with a pre-sanitized memory map mapped at some fixed virtual address (maybe mapped to 0x40000000) and any other information the kernel might want mapped to another fixed virtual address (maybe at 0x00000000)? That would simplify things for the kernel a lot.
It's not perfect though, there's a couple of odd things which I won't get into just yet, and it lacks EFI support, which I'm slowly working on adding.