Hi, in the following lectures we will delve into the essential part of the memory manager. We have collected the free memory
and saved them in the linked list.
In this video, we are going to remap our kernel to the same address space, but we will use 2m pages
instead of 1g page which is used by the kernel so far.
To do the kernel remap, we need to set up the corresponding table entries. We have learned
the structure of the translation tables.
So let's implement paging using these tables.
The tables used in this example are pml4 table,
page directory pointer table and page directory table.
The entry in the page directory table will point to 2m physical page.
OK, let's open the project.
We open the memory.c file.
First off, let’s see what functions we will implement in this file.
The first function is find pml4 table entry, as its name implies, find the specific entry according to the virtual address
.
The entry we get is pointing to page directory pointer table.
Then function find pdpt entry is used to find the entry in the directory pointer table which points to
page directory table.
And in the page directory table, we can set the corresponding entry to map the addresses to the physical page.
So we use map pages
to do the mapping.
The function setup kernel virtual memory is used to remap our kernel using 2m pages
and switch vm is used to load cr3 register with the new translation table to make the mapping work.
Ok we start from function initialize kvm which then call setup kvm. Because we have collected the physical memory
when we call this function, we can allocate a new free memory page using function
kalloc.
This page is actually the new page map level 4 table. Since what we do here is initializing kernel vm,
if the allocation failed we simply stop the system.
So here we use assert to test the condition.
If the check pass, we zero the page using memset function because the page could include random values
when they get the page from function kalloc.
The function map pages take a few parameters.
The first one is pml4 table. Next two parameters are the start and end address of memory region we want to map.
The arguments we pass in this example are the base of the kernel
and the end of free memory, both of which are virtual addresses.
The next one is the start of physical page we want to map into.
Because we want to map the kernel to the same physical address,
we pass physical address of kernel base in this case.
The last one holds the attribute of the page table or physical page. If the mapping failed, it returns false
.
Here we simply add assert to test the conditions.
Because we used Boolean type,
we add the header file stdbool.
OK.
These are macros we defined in the header file. So let’s take a look.
As you see the kernel base is the address we have seen in the last lecture.
The p w u and entry are the attributes of the table entries.
They are in decimal or hexadecimal formats.
If you convert them to binary forms, you can see they just set the corresponding bits in the table entries
.
The pde address and pte address are used to retrieve the address of the next level page or physical page.
The entries in page map level 4 table and page directory pointer table have attributes within the lower 12 bits.
So we clear the lower 12 bits of the entry to get the correct address.
The page table entry has attributes within the 21 bits.
So we clear 21 bits of the address to get the physical address.
Alright, let's move on.
These three customized data types are used when finding the translation tables.
The pdptr points to page directory
and page directory points to page directory entry, etc.
Ok let’s back to c file.
So here we specify that the kernel memory is readable, writeable and not accessible by the user applications.
Let's move to
map pages function. In the function, we define two variables vstart and vend
which save the aligned virtual addresses.
For example, if we get unaligned start virtual address, we need to map the page where the start address is located
.
So here we align the address to the previous 2m page so that we can include the start address.
The same thing happens with end address
.
In this case, we align the address to the next 2m page to include the page where the end address is located
.
Then we define variable pd which is used to set the page directory entry. The index is used to locate the specific entry in the table.
In this example, we also perform some checks.
The first one checks the start and end of the region. The next one is to make sure
the physical address we want to map into is page aligned.
The last one checks to see if the end of physical address is outside the range of 1g memory
.
OK, now we do the mapping.
The first thing we are going to do is we are going to find the page directory pointer table entry
which points to page directory table by calling function find pdpt entry. We will see this function in a minute
.
If it returns null, it means that it failed and we simply return false.
If it returns a valid table, we use the index to locate the correct page entry according to the virtual address.
We have learned that the virtual address is divide into several parts to locate the different table entries.
Here the table entry we want is page directory entry,
the index value is 9 bits in total starting from bits 21.
So we shrift right 21 bits and clear other bits except the lower 9 bits of the result.
The value in the index is used to find the entry in the page directory table.
Here we check the present bit of the entry. If the present bit is set, it means that we remap to the used page.
We don’t allow it to happen in our system.
So we use assert to check that.
If the check pass, what we are going to do next is we are going to set the entry
with the physical address and attributes.
Don't forget to add the attribute page entry to indicate that this is 2m page translation.
Ok now we have mapped one page.
Then we move to the next page by adding the page size to the virtual address and physical address.
If the virtual address is still within the memory region, we continue the process until we reach
the end of the region.
Ok let’s see the function find pdpt entry.
The parameters it takes are the page map level 4 table, virtual address and alloc indicating
whether or not we will create a page if it does exist. The last one is attribute.
The last one is attribute.
You can see we pass the value 1 to the function. So we will create a page if it does exist.
Ok, in the function find pdpt entry,
we define the variable pdptr pd and index. Since we find the pdpt entry in this case, the index value
is located from the bits 30 of the virtual address and is also 9 bits in total.
So we preserve the low 9 bits of the result.
Then we call function find pml4 table entry to get the page directory pointer table and we check if it returns null.
If it returns a valid table,
we will find the correct entry in the table using index value.
If the present attribute is 1, then it means that the value in the entry points to the next level table
which is page directory table.
We use macro pde address to clear the attribute bits to get the address.
Note that here we use p2v to covert the physical address to virtual address because the addresses in the table entries
are physical addresses.
OK, let's continue.
If the present bit is cleared, then it’s an unused entry. We will check the value of alloc.
If it’s equal to 1, we will allocate a new page and set the entry to make it point to this page.
Also, we use v2p to convert the virtual address to physical address and save it to the entry.
In the end we return the address of page directory table.
The function find pml4 table entry
has the same parameters as find pdpt entry.
The pml4 table holds the entries which point to the page directory pointer tables.
So we define the variable map entry
which is a pointer to type pdptr and variable pdptr.
Since we find the entry in pml4 table, the index is located from bits 39 of the virtual address
.
Then we locate the correct entry and check to see if the present bit is set.
If it is set, we copy the address to pdpt.
otherwise we will check the alloc. If it is 1, we will allocate a new page and set the entry
to point to the page.
This process is pretty much the same as in function
find pdpt entry.
At the end of the function, we return pdptr.
OK, the last function we implement
is switch vm. With the translation table prepared,
we need to load cr3 register with the new pml4 table
.
Here we convert
the virtual address to physical address because cr3 register stores the physical address of the table
.
We haven’t written load cr3 function. Let’s do it now.
We add it in the trap assembly file.
So we opened trap assembly file.
Here is load cr3 function we use in the file. In this function, we just load the address to cr3 register and return
.
Also, we declare it global to make it accessible in the c file.
Since it is defined in the assembly file and we will use it in the c file.
we add the declaration of the load cr3 function in the memory header file
Alright, let's go back to memory.c file. After we set up kvm, we call switch vm to
use the new paging setup in the kernel.
We also add print function to print memory manager is working now
.
OK, let's build the project.
As you can see, we print the addresses on the screen. But there is no memory manager is working on the screen.
So in this example, the last message printed on the screen is the memory end
which means after we switch vm, the printk function is not working as we expect.
The reason is that we use the new paging setup. Remember we don’t remap the lower virtual address space
.
But in the print.c file,
you can see the screen buffer is still pointing to the address b8000.
In order to access the physical address b8000, we have to convert it to virtual address using macro
p2v.
So we need to add memory.h
We should see the message on the screen this time.
So let's check it out.
OK, you can see memory manager is working now.
Alright, that's it for this lecture.