Sourcer wrote:
Thank you for your reply.
You're right about GRUB not reporting regions whose size is a not power of 2, my bad.
Secondly, by '8-16 bytes' per page, do you mean you calculate how much available memory you have
and 'statically'(once) allocate a chunk of memory to hold that list? i thought about it too, but i'm not sure
what is the advantage of this approach. Is mapping the physical page to virtual memory is that expensive?(sure i'll need to flush the TLB, but is it..?)
At bootup, currently It takes stock of what regions GRUB reports, and for each of those it allocates enough pages to store all the bookkeeping needed for the pages in that region. The reason I went with this approach are the following:
1. It is a lot simpler to code. Not having to change page maps and the like removed a big chunk of potential bugs, mainly by removing the code needed to manage those mappings
2. It provides better cache locality. Because of the (admittedly somewhat simplistic) way I've implemented other parts of the kernel, it is relatively common for my page allocator to be called several times in quick succession to allocate a number of pages that don't necessarily need to be consecutive. By having the managing datastructures in one location, I have a better chance of hitting the cache for those on the later invocations in such a cycle.
3. This also ties into the third reason: TLB flushes are expensive. Even in the best-case scenario, when there is only a need to flush one entry, this will cause several extra memory accesses, and the fact that you are then immediately accessing the newly mapped page for a read ensures that one of those is almost guaranteed to be a direct read from memory, which incurs an extremely large performance penalty on modern x86 processors.
Of these, reason number 1 was the primary choice (I only have finite time, and I decided the easier development was worth the increase in memory use), though the other advantages are definitely nice to have.
Again, the other choice, using the pages themselves to keep the linked list structure is also valid, it will definitely save you some memory. It's a trade off.
Sourcer wrote:
About freeing, i thought about it just a bit, and i thought about keeping a 'state' of a buddy within its struct.
state could contain:
1. Split, right side.
2. Split, left side.
3. Not split(i.e. buddies are together)
This way when the user frees address (X), i could check its buddie's state(by a simple calculation), and restore buddies accordingly.
And someone also asked why use a buddy allocating: I tried to merge both of the 'stack allocation' approach and the buddy allocation. I use the free pages to store information about free pages(just like a stack), but i like to think about it like i have N stacks of N orders(2^N)
I have a few small points. If you keep proper track of starting points of the various regions handed to you by GRUB, you wont need to differentiate between cases 1 and 2. Instead, you can simply use the address itself to determine whether this block is the left or right block of its pair. This means that you can get away with a single bit per block, simply stating whether it is used or free. Again, [Knuth] has an excellent explanation of the details.