Awesome.
Hang onto those functions. They are all pretty easy and can even be reimplemented as macros.
So, FrameToLinear and LinearToFrame are not any more difficult -- they just shift bits. I may not have been clear in my explanation. I may have further confused the issue by not calling them PhysicalToFrame() and FrameToPhysical() -- which more accurately describes their purpose.
Physical address 0x00100000 sits in frame 0x00000100. Physical address 0x00100334 also sits in frame 0x00000100. The conversion from physical address to the frame number is: pAddr >> 12. As you can imagine the conversion the other direction is frame << 12 -- which will give you the starting address for the frame. You will want to implement these as functions/macros as well and will likely use them in your final code.
So, let's pick apart a more interesting address using your functions before I get into the actual page entry structure: 0xc0651234 -- let's say somewhere in your kernel data (heap). If you put this through your program, you will see the PD index is 0b1100000001 or 0x301 or 769. Moreover your PT index is 0b1001010001 or 0x251 or 593. This then leaves the index into the page, which is 0x234.
Taking this address, the MMU will look in the cr3 register for the frame number of the Page Directory (it has a frame number plus 12 additional bits where there are flags, which might have been what you were trying to say earlier). This frame number is converted to a physical address (recall: pAddr = frame << 12).
The PD index is then used to calculate an offset into the Page Directory (0x301 or 769 from my example address).
The PageTableEntry_t at that physical address is then loaded, which contains the frame number of the Page Table (I did not offer a concrete frame number for my example -- don't let that throw you). That frame number is then turned into a physical address.
Now, the PT index is then used ot calculate the offset into the Page Table (0x251 or 593 from above).
At that point, you will have a PageTableEntry_t for the frame of real physical memory that is backing your virtual address of 0xc0651234.
The offset into the start of the frame is the same as the offset into the Page.
All of this happens with each virtual address you reference and if any of this breaks for any reason (page is not present, table missing, other flags set wrong, really lots of reasons) you will end up with a #PF Page Fault.
But this is also useful information as you can use that to write a DumpPageTables() function to use when you are debugging your initialization code -- I really think a function like this is useful and should be implemented. This also gives you how the system will translate a virtual address into a physical one. It also helps because you will need to build your tables to support all this work.
To help me keep things straight when I come back to my code days/weeks/months later, I like to do something like the following:
Code: Select all
typedef uint32_t frame_t; // -- valid values from 0x00000000 to 0x000fffff
typedef uint32_t pAddr_t; // -- valid values from 0x00000000 to 0xffffffff
And of course there is the magical Page Table Entry:
Code: Select all
typedef struct PageTableEntry_t {
unsigned int p : 1; // Is the page present?
unsigned int rw : 1; // set to 1 to allow writes
unsigned int us : 1; // 0=Supervisor; 1=user
unsigned int pwt : 1; // Page Write Through
unsigned int pcd : 1; // Page-level cache disable
unsigned int a : 1; // accessed
unsigned int d : 1; // dirty (needs to be written for a swap)
unsigned int pat : 1; // set to 0 for tables, page Page Attribute Table (set to 0)
unsigned int g : 1; // Global (set to 0)
unsigned int avl : 3; // Available for software use
unsigned int frame : 20; // This is the 4K aligned page frame address (or table address)
} __attribute__((packed)) PageTableEntry_t;
I have not seen all your code, but I assume you have something like this somewhere. What I want to call out here is that I have a 'frame' field whose size matches the range I want to use with 'frame_t' (20 bits). This is where your FrameToPhysical() and PhysicalToFrame() come in.
The real magic is how to populate the tables such that none of the rules are broken and you end up with a #PF (and if you don't have a interrupt handler a triple fault).
So, the first thing you will do is allocate a Page Directory from your PMM (whether you are allocating physical addresses or frames is up to you). This is the Physical Address and while you are constructing your
initial tables you can use the physical address. I recommend initializing this to all 0's -- in particular to set the p flag to be 'not present'.
So let's go through mapping the example address above for 0xc0651234. You need Page Directory Entry 0x301. You get the Page Directory Entry (really PageTableEntry_t type) for that offset and realize that the p flag is 0. OK, there is no Page Table for that Entry -- let's make one!
Go get another frame from your PMM. Initialize the associated physical memory to 0. (Are you starting to see the value of FrameToPhysical() and PhysicalToFrame()?) Place the frame in PageTableEntry_t.frame of your Page Directory Entry from above and set all the other bits (especially p!). With that, now you can get the Page Table Entry at 0x251. I the p flag set? Nope (well we just initialized it) -- good!! If we are trying to map an address to a frame that already is mapped to a different frame, this might be a bug. It's better to umap before you try to remap and trap this condition.
Anyway, now we can map this page to the desired frame, setting all the proper bits here as well (p!). Congratulations, you have mapped a virtual address to point to a physical frame (and by extension the physical memory).
Now, if you look at the next page in virtual address space (0xc0652000), the Page Directory Entry 0x301 is the same. No problem there, we already have that mapped to a Page Table. The Page Table Entry (0x252 this time) is not mapped (p == 0), so we will map it to the proper physical frame.
This gives you enough information to build your paging tables for your
initialization code. This would be a MapVirtualAddressToFrame() function. Using similar logic, you will implement an UnMapVirtualAddress() function.
Now, once you are established in higher memory, you will need to use recursive mapping to do all this work -- a clever trick to set aside some of the virtual address space (not physical memory) for managing these tables using virtual addresses rather than physical addresses. They will be readily available using the address space from 0xffc00000 to 0xfffff000 -- assuming you map PD[1023] to its own frame. When you get to that point, refer back to the wiki.
One final note -- when you get to the point where you are beyond your identity mapped space, you will have trouble with page faults. For this reason I use a bitmap and frame number to manage my physical memory. I am not saying that your implementation is wrong -- quite the opposite. But it is something to think about how you will handle this later.