Physical Page Allocator
Definition of the problem
Essentially, this comes down to a data structure problem with a twist on the restrictions on coding a kernel. I have decided to represent each page on a stack, where each page element keeps track of at least the physical and virtual addresses of a page. Optionally, a page element will most likely hold some sort of flags variable to keep track of page usage information, but this is not required for an initial implementation.
The problem involves the allocation of physical page addresses or a structure that represents a page. So, either an address pointing to a free page or a structure that contains the physical address, virtual address, flags, etc. is returned. This functionality should co-exist utilizing the same data structure.
Given:
- Start of page addressable memory (starts at the end of kernel memory)
- End of memory (address of)
- Page size (given by the architecture)
- Size of my page structure (see previous post for code)
Kernel "rules":
Not so much about rules, but behavior exhibited by working in kernel code.
- The allocation of the page structures cannot be done on the kernel's stack due to memory/stack size constraints. Thus, the space for all of the page structures must be taken from what is available from the page allocator.
Of course, this leads to a circular dependency that you need to deal with in code, but it is possible (and not too difficult). One way around this is to simply pop the top n pages from the stack, where n is the number of pages required to store all of the page structures.
A side effect of this circular dependency is that the physical page allocator cannot utilize kernel memory allocation routines due to the possibility of the page table scheme not completely initialized when it is used. This would require paging to be enabled!
Implementation Details
API:
void init_page_table(unsigned long pt_start, int num_pages, unsigned long pt_size);
page_t *pop_page();
void push_page(page_t *page);
unsigned long *alloc_page();
int dealloc_page(unsigned long *page);
pop_page() and push_page() deal with page_t structures, while alloc_page() and dealloc_page() deal only with the physical address of a page. The distinction is subtle, but important to understand because their implementation reflects how the page table stack is modified.
To achieve the manipulation of both page_t structures or physical addresses, and to use them in any order without corrupting the stack I decided to use a doubly-linked list implementation of a stack. However, this stack is not normal because I don't have access to any memory management facilities (remember, I am still in the midst of setting everything up for memory management). A doubly-linked implementation would also ensure that the stack would not need to be used in any sort of order (i.e. pushing/popping along with allocation/de-allocation in the same logical order).
It is important to note that de-allocation or popping of a page does not actually free any memory to the system. It only gives the illusion that the page is no longer in the page table, but in fact the total page table stack needs to persist in kernel memory for the life of the kernel. Additionally, one cannot traverse this list at any given moment and find out what all the pages are being used for. Not all pages are guaranteed to be represented in this page table, but the space for all pages needs to exist.
pop_page() returns an entire page element, not just its physical page address, so it is similar to just removing that element from the list, which can be simulated by updating the next and prev pointers.
By removing elements you will effectively remove the ability to de-allocate a physical page address to a page structure that is currently in use, which acts as a protection mechanism since allocating an address on a page that has been popped off would be bad.
Allocating a physical address removes the address from the element (setting its value to 0, which is not a possible real value for a physical or virtual address), but the page structure stays linked on the beginning of the list. The head pointer is moved to the next available element, but an empty structure remains on the heads prev pointer. This will allow de-allocation of a pointer by following head's prev pointer to an empty and unused page structure so its physical address can be re-initialized by whatever address was passed in by the dealloc_page() function.
All four functions execute in constant time. However, the page struct will require two pointers for a doubly-linked list. This is expensive, but it allows me to work around any O(n) searches on push_page() or dealloc_page() functions.
The mixing of pop_page() and alloc_page() in any order will work well with this implementation.