In this lecture, we will collect the free memory, divide them into a bunch of 2m pages
and save them in the linked list for later use.
The free memory we are talking about in this example is physical memory.
Let’s start from where we left off. In the last video, we collect the memory map using the for loop
and print on the screen.
In this video, we will divide the free memory into a bunch of 2Mb pages
and these pages are used for the memory module.
So the memory module use 2m pages instead of 1g page.
But here is the problem.
remember we have mapped our kernel to the higher part of the memory, and we are using virtual address
in the system after we enter 64-bit mode.
The free memory the memory module uses is physical address.
So the first thing we need to do is find the physical address according to the virtual address.
To achieve it,
we use a very simple method.
As you see, this is the virtual address space we have talked about before,
the lower 1g address space is mapped to the corresponding physical page at this point.
And we also mapped our kernel which is located at the higher part of virtual space to the same physical page
.
In the following lectures, we will only remap the kernel space and leave the user space unused.
So the kernel space is what we will focus on. In this example, we just map the whole 1g of memory space
to the 1g physical space, which means the offsets within the memory space remain unchanged
.
So to get the physical address we can simply subtract the value which is the base of kernel space from the virtual address.
The equation is like this
The virtual address is equal to physical address plus the base of the kernel.
And also, we only use the first 1gb of ram and simply ignore other free memory.
The reason is that we only mapped 1gb of ram in the loader file and 1gb of ram is enough for our small kernel.
If you want to use more ram,
don’t forget to set the corresponding items in the page translation tables.
OK, back to the project.
In the memory header file,
you can see the macro page size which is set to 2mb.
The next two macros align the address to 2m boundary. The page align up will align the address
to the next 2m boundary if it is not aligned. The page align down
will align the address to the previous 2m boundary
.
There are several ways to do it. In this example, we simply shift right 21 bits and then shift left
which will clear the 21 bits of the value and now we get the aligned address.
The p2v and v2p, as we have talked about, converts between virtual address and physical address
by adding or subtracting the base of the kernel.
OK.
in the memory.c file, we divide the free memory into a bunch of 2mb pages. Here we define two variables
vstart and vend which represent the beginning and end of the memory region.
You can see we use p2v to convert the physical address to virtual address
and assign it to the vstart.
Then we set vend by adding the length of region to vstart.
So vstart and vend hold the virtual addresses instead of physical address.
Now we get to the initialization of free memory part.
In this system, the memory we care about is from the end of our kernel to the end of the 1st gigabytes of memory
.
So how do we know where the end of our kernel is.
To get the info, we can add a symbol in the linker script.
At the end of the linker script, we write provide end = ., which means the label end
will represent the current position that is the end of the kernel.
Ok at this point, we can use the label end in the c file.
So let's go back to C file.
Here we extern end, since it is not define in this module and set it to char type.
Alright, the next thing we are going to do is we are going to check the location of the memory region.
If the start of memory region is larger than the end of kernel, this is the free memory we want
and we do the initialization by calling function free region.
Note that here we need the address of end because the end is a symbol instead of a variable.
OK, let's move on.
If the start address of the memory region is less than the end of kernel,
we will compare the end of the region with the end of the kernel. If it is larger than the end of kernel, we will initialize the memory
from the end of the kernel to the end of the region.
Otherwise we ignore the region because it is within the kernel.
Let’s see the function free region.
This function is simple, all it does is just divide the region into 2mb pages
and call the function kfree to collect the pages.
So in the for loop, we start off by aligning the address to the next 2m boundary.
Then we compare this page with the end of the region.
If it is within the region, we call the free function.
Note that here we also add another check.
This value is 1g above the base of kernel.
So what it does is just check to see if the page we are about to initialize is beyond the first 1g of ram
.
Remember we only use the first 1g of ram in the program.
OK, let's move to the kfree function.
This is the function where we do the initialization.
Before we start, we add these simple checks, the first one is to make sure that the virtual address is page aligned.
The next one assumes that the virtual address is not within the kernel.
And the last one checks 1g memory limits.
OK, now we can collect the pages.
These bunch of pages are stored in the form of linked list.
As you see, when we add a page, what we really do is that we link the page to the existing one.
As we added more pages, we will extend the list.
In the project, we use the link list except that we add pages in the reverse order.
Let’s see how to do it.
In the header file,
the structure page is used to implement the linked list.
It includes only one item which is the pointer to the next page structure.
So you see we use the first 8 bytes of every page to store the location of the next page
which creates a list of pages.
Ok in the c file,
we define the variable free memory which is the head of the linked list.
In the function, we first convert the virtual address to the pointer to type structure page.
Next we copy the head of the list to the first 8 bytes of the page and then save the virtual address to free memory.
At this point, the head of the linked list points to the current page.
Alright, that’s it for the memory initialization.
Let's go back to the function, initialize memory.
After we exit out the for loop, we have collected the free memory pages and we define a variable memory end
to save the end of the free memory.
Because we add the pages to the list in the reverse order, the head of the list points to the last page we collect,
then we add the page size. The result is the end of free memory we can use in the system.
Here we print the message on the screen. We will check it out when we run the system.
The last function we will implement in this file
is kalloc used in the kernel mode. Allocating memory is just removing a page from the page list
and return to the caller.
So we copy the first page in the list to page address and check to see if it is NULL. If the page is not NULL
,
this is a valid page and we will return the page to the caller.
As we did in the function kfree, we add the same checks before we allocate the page.
And then make the head of the list point to the next page.
No, we have removed the page from the list.
Return the page and we are done.
Let’s run the build script and test it out.
let's run bochs
As you see, the end of free memory in this example is this value.
The free memory in the fourth entry is starting from 1mb. We add it to the length of the region
which produce the value 3fff0000 and we add the base of the kernel.
The result is ffff80003fff0000.
As you can see, the end of the free memory the system collect is different.
Remember the free memory is stored as a list of 2m pages.
The memory address we get is not 2m aligned.
If we align it to the previous 2m boundary,
we will find that we get the exact value showing on the screen.
And also here we cannot align the address to the next 2m boundary, if we do it, we will include
the reserved memory.
OK, that's it for this video.