The best thing I can tell you is that you should write your own implementation from scratch (this way you will know its downsides and upsides and such), but I understand this may not be feasible at this time. A memory manager is usually implemented on two layers: the physical and the virtual memory manager. I'll try to tell you about an example implementation (note there are other ways, but I'll try to do it as JamesM does it).
As you know, physical memory is divided into chunks of 4 kB. So that means you take memory_amount and divide it by 4096 (or 0x1000) to find the amount of chunks you need (note that available memory may not always be 4 kB-aligned, so you might lose some chunks at the end, but you can fix that later). Then you create a bitmap where each bit represents one 4 kB-chunk (called a "physical frame"). This bitmap will now be used to indicate whether a frame in physical memory is in use or not. You will most likely need it later on when you have one or more heaps and they need to find some free memory. This is basically all the physical memory manager does. Usually it has some functions like FindFirstFreeFrame, AllocFrame, FreeFrame, and so on. You should now have a picture in your head of the memory in your computer, divided into 4 kB chunks.
On top of the previous layer, you create a virtual memory manager. It usually deals with paging (and sometimes, the heap as well). In the beginning, you will most likely only have one address space (or page directory). You may need more later when you have multiple processes running. The virtual memory manager initializes itself and identity maps the kernel. First you need to put your page directory somewhere. When that is done, you clear it and start initializing (e.g. set the first page table to point to the first 4 MB, as I explained above). Then paging is turned on.
This is basically how both are implemented. Now, I will try to explain how the MMU translates addresses and how virtual addresses are translated into physical addresses. Suppose paging is turned off, and you access adres 0x12345678. Nothing special is involved here, you simply access that memory adres as you desire (it must exist of course, i.e. you must have that amount of memory installed in your computer). With paging turned on, it is a whole different story. Different types of paging (e.g. standard, PAE, PML4, etc.) translate virtual addresses differently, but I will focus on standard x86 paging as that's what you're implementing here. So let's get back to our address 0x12345678. When using this address, the MMU will use the currently active address space or page directory (which is stored in CR3 as a physical address). Generally, virtual addresses look like this:
Code: Select all
A typical 32-bit virtual address:
32 22 12 0
+----------------------+------------------+------------------+
| Page Directory Index | Page Table Index | Offset From Page |
+----------------------+------------------+------------------+
As you can see, the first 12 bits are an offset from the page. 12 bits means 10^12 possibilities, thus 4096 possibilities (0 - 4095). This value is usually of no use to you. It describes how far the address is from the page in question. The next 10 bits are the index into your page table. 10^10 = 1024 possibilities, what a coincidence, since there are 1024 pages in each page table! The last 10 bits are the index into your page directory, so they indicate what page table is used. Now let's go back to address 0x12345678 and see how it fits in. 0x12345678 translates to binary is 0001001000_1101000101_011001111000 (the underscores mark where the fields above end). The highest bits indicate our page directory index and the value is 000100100, which is 72, so the MMU will take your page directory and go to page_directory[72]. page_directory[72] holds the physical address of a page table, so the MMU jumps to this address and continues looking. Note that the value here contains physical_adres | page_present | other_flags. If the value were to be 0, you'll get a page fault (page not present), because the MMU doesn't know what address to use for this virtual address. Now that we have the page table we want, we continue looking: the index into the page table is 110100010, or 418, so let's see what physical address is in page_table_at_index_72[418]. This address will also be equal to some_physical_adres | page_present | other_flags (if page_present is not set, you will again get a page fault). This time, the MMU has found the physical address it needs. Suppose the value of this page is 0x4001. As you know, the first bit of a page is PAGE_PRESENT (0x1), so this is 0x4000 | 0x1. We discard the flags (we only need the physical address), and we find 0x4000. The MMU now knows that address 0x12345678 is mapped to physical frame 0x4000. The only thing remaining now are the last 12 bits, which define the offset. The offset here is 011001111000, or 1656. The MMU is now finished, the physical address you are actually using when you use virtual address 0x12345678 is 0x4000 + 1656 = 18040 or 0x4678.
The last paragraph should give you a general idea of how the MMU translates addresses. Now that you know this, you know why making sure the pages inside each page table must have the right values and that the kernel must be identity mapped to itself so it doesn't just "disappear" when paging is activated.
Note that I'm just trying to explain this to you, the error in your code is most likely just a small mistake you made somewhere (e.g. forgot to memset a paging structure, etc.)., as JamesM tutorial on paging does work properly (although most people roll their own once they understand it completely). I also assumed that you know about the bits that have to be set when you set an entry in a page directory or page table (shameless plug):
Code: Select all
typedef struct page
{
u32int present : 1; // Page present in memory
u32int rw : 1; // Read-only if clear, readwrite if set
u32int user : 1; // Supervisor level only if clear
u32int accessed : 1; // Has the page been accessed since last refresh?
u32int dirty : 1; // Has the page been written to since last refresh?
u32int unused : 7; // Amalgamation of unused and reserved bits
u32int frame : 20; // Frame address (shifted right 12 bits)
} page_t;
This structure is not entirely correct (as some of the unused bits aren't actually unused, so you can best not use them at this time, but JamesM doesn't do anything special to them so you probably aren't either). For you to not get the "page fault - page not present" exception, the "present" bit must always be set. You can do this by:
Code: Select all
page_table[index] = physical_address_that_this_page_will_point_to | 0x1;
But you probably knew that (since you should know your bitwise operators
).