3

Series catalog

Overview of kernel virtual memory

article 160fecd90b9315 GDT and protected mode , this article will be the focus of the loader. First, we need to create virtual memory in the kernel space. If you are not familiar with the principle of virtual memory, please be sure to first. Here you can provide a 160fecd90b9318 document for reference.

So far we have always operated on physical memory, to be precise in 1MB , all of which are simple and straightforward. But then the loader is about to prepare for loading the kernel, we need to plan the data and code on the 4GB

3GB Linux system, we will use the high address space above 060fecd90b9357 as the kernel space for all subsequent work. For example, the most basic, the current physical low address 1MB will be mapped to the virtual address 0 ~ 1MB and the space above 3GB 0xC0000000 ~ (0xC0000000 + 1MB) :

After entering the kernel, access to the lower 1MB space will use the 0xC0000000 ~ (0xC0000000 + 1MB) virtual address, which mainly includes the currently used stack and the memory map corresponding to the display:

So the video memory base address will 0xC00B8000 , but you don't need to go into it for now, it will be explained in detail in the display and printing article later.


In addition to the most basic low 1MB memory space, loader also needs to further expand in 0xC0000000 , which mainly includes two parts:

  • The page directory ( page directory ) and page table ( page table ) used by the kernel;
  • Reading of kernel binary image, and loading of code and data;

virtual-to-physical memory map to be built during the entire loader stage:

This picture is the most important overall picture in this article. The second line is the "distorted" scale icon in the first line. We have reduced the user space below 3GB and displayed it. The current focus is only on the kernel space above 3GB (coarse Box part). Since it is a virtual address space, our space division can be more arbitrary and "luxury", we use 4MB as a unit, starting from 0xC0000000 in the virtual space to divide the following areas:

  • The first 4MB is reserved, of which the lower 1MB of space is mapped to the lower 1MB of the physical address, which has been explained above;
  • The second 4MB (orange) is used to map all page tables kernel;
  • The third 4MB (green), starting from 0xC0800000 , is used as kernel code and data, which means that kernel starts addressing there;

I want to say here that there is no fixed way to implement an OS, the above is just my personal way of implementation. In fact, the memory planning is very flexible. Just like the name of this project, scroll , the memory is a picture scroll, and the CPU is a paintbrush. Under the premise of following certain rules, you can play freely.

Below we first start the orange part, namely the establishment of the page directory and page tables

Create kernel virtual memory

Before starting this paragraph, let's review the related principles of the page directory ) and the page table ( page table

There are some key figures to remember:

  • The size of the page ( page
  • Page directory entry pde (page directory entry) and page table entry pte (page table entry) essentially the same structure, with a size of 4 bytes ;
  • page direcotry a total of 1024 items, pointing to a total of 1024 page table , a total of 4MB ;
  • Each page table has 1024 items, pointing to 1024 pages , which manages the virtual space 1024 * 4KB = 4MB
  • So each pde manages the virtual space 4MB

Well, let's start to build the page table of the kernel space. The code link is given according to the convention: the related code of this part starts from the function setup_page for your reference.

From here on, following terminology conventions, I will use page frame for physical pages.

Create page directory

First of all, we need to take out a frame and use it as page directory . 0x100000 back to the picture of physical memory distribution, the part below 1MB is currently occupied, and the part we can use starts from 1MB, which is 060fecd90b9954.

I chose 0x100000 + 4KB , namely 0x100000 after the first 2 a frame as page directory , of course, this is entirely a personal choice; 0x100000 after the first one frame I chose it as the first page table :

Again, this is my personal choice; the choice of frame is very free, as long as it is not occupied, you can use it, of course, you have to remember which frames you have used, reasonably compact and as "beautiful" as possible Plan to use.

Map 1MB low memory space

It is worth noting that the 0th and pde points to the same page table , this page table we will use it to map the 0 ~ 1MB low memory, which is the 1MB memory space we are currently in. Of course, this page table can manage 4MB of space, we only mapped 1MB of it, and the remaining 3MB of virtual space is idle, but it doesn't matter. If it is idle, it will be idle. Anyway, this is virtual space.

The following figure shows how the lower 1MB of memory is mapped in the page table:

pde[0] manages the lowest 4MB of virtual space, of which the first 1MB is mapped to the lower 1MB of the physical. This is a one-to-one mapping. The virtual address is exactly equal to the physical address. In this way, after opening paging, we have a low 1MB The memory access is changed to use the virtual address, which is the same as the previous physical address access, and no changes will be perceived.

pde[768] manages 0xC0000000 , the first 4MB space from 3GB. Going back to the first picture at the beginning of this article, the first 1MB is also mapped to the lower 1MB of memory. After opening paging and entering the kernel, we will use 0xC0000000 ~0xC0000000 + 1MB to access the lower 1MB memory:

Map page directory and page tables themselves

Here are the key points and difficulties of this section. We know that page directory and page tables point to physical pages, and once the paging mode is turned on, all of our future memory accesses will pass through the virtual address, and we can no longer directly manipulate the physical address. So the question is, how do we access and modify page directory and page tables itself?

One way of course is to turn off paging when needed and directly access the physical address. The previously recommended tutorial JamesM's kernel development tutorials is done in many places, but this is not a good practice for the following reasons :

  • After entering the complex kernel, the execution of the code will involve a large number of memory accesses such as stack and heap, and other global variables. These are all virtual addresses in the kernel space. If paging is suddenly turned off at this time, their access will not be possible. You must arrange your code's access to memory very carefully, otherwise there will be unpredictable consequences, but this is actually very difficult to do;
  • Once multithreading is turned on, if an interrupt occurs when paging is turned off, the CPU will perform some automatic stack operations and interrupt processing, all of which are operations on virtual addresses. Obviously, the results are also disastrous;

A more reasonable approach is to page directory and page tables to the virtual space, so that they can be accessed like other normal memory. In essence, page directory and page tables nothing more than pages, which can be treated the same as other memory accesses. The question is, how should this mapping be established? Look at the picture below:

We will pde[769] pointing page directory the frame itself. In this way, page direcotry actually also serves as a page table at the same time, it manages exactly 1024 page tables themselves, a total of 4MB. Of these 1024 page tables, one of them is page direcotry itself.

Is it a bit convoluted? In other words, since pde[769] pointed page directory its own, so 0xC0400000 ~ 0xC0800000 this 4MB of virtual space, is now mapped to the 1024 page tables on, but better is that their virtual address is completely continuous, tightly arranged in this 4MB space inside.

As a result, the above problem has been solved, and the virtual address space corresponding to page tables is:

0xC0400000 ~ 0xC0800000

769 4MB space in the 4GB space (a total of 1024 4MB spaces make up 4GB).

And we also got page directory its own virtual address as:

0xC0701000

That 0xC0400000 ~ 0xC0800000 this space 4MB of 769 a page, is not very clever :)


The core idea here is that, page directory in fact, is essentially a special page table , it and other page table as manages a space of 4MB.

If it still feels a bit convoluted, you might as well verify it in reverse. Starting from the virtual address given above, deduce where the physical address actually points to. I think the logic will be sorted out soon.

If you think further, you will find that this is not the only way to achieve it. You can not choose pde[769] , using other virtual space mapped page tables, for example pde[770] may be, so that all page tables corresponding to the virtual space becomes 0xC0800000 ~ 0xC0C00000 . Using pde[769] is only my personal choice, because it is 0xC0000000 . With this arrangement, the use of virtual space can be more compact and tidy.

Map other areas of the kernel space

So far, pde 768 and 769 have been used, that is, the two 4MB spaces of 0xC0000000 ~ 0xC0400000 and 0xC0400000 ~ 0xC0800000 pde[770] ~ pde[1023] corresponding to the remaining page tables , we arrange frames for them in turn. In this way, we finally requisitioned 256 pages & frames, a total of 1MB of memory (virtual & physical), to create a kernel space (3GB ~ 4GB) of page tables to manage this 1GB of space.

virtual-to-physical memory map at the beginning of this chapter to show the memory distribution of the 256 page tables of the kernel:

Note that we only allocated page tables with a kernel space of more than 3GB, a total of 256 sheets, occupying 1MB, and they map the last quarter of the 0xC0400000 ~ 0xC0800000 0xC0700000 ~ 0xC0800000 ; and the user space below 3GB is not allocated at this time. tables, because we have not used them currently.

These 256 kernel page tables (one of which is page directory itself) is the most core page tables during our kernel compilation, and pde[768] ~ pde[1023] are created in the page directory, pointing to these page tables.

In fact, except for the first two page tables, the last 254 are currently empty and are not used. We just arranged frames for them. A full 1MB of physical memory is used here, which looks a bit extravagant. After all, the total physical memory in this project configuration is only 32 MB (see bochsrc.txt , of course, the current computer memory is far more than 32 MB, this is no longer a problem). There is a very important reason for this, that is, these 256 kernel page tables will be process ), which means that for user processes, the space below 3GB is isolated, and the kernel above 3GB The space is shared, which is also a matter of course, otherwise there will be multiple kernels running independently in memory.

Each time fork creates a new process, page directory , that is, 768-1023 items, will directly copy the page directory items of the kernel 060fecd90ba001, and point to these 256 kernel page tables . Therefore, we require the 256 page tables corresponding to frames to be fixed from the beginning, and will not change later, in order to achieve the shared effect of all processes.

Open paging

page tables is ready, you can open paging :

enable_page:
  sgdt [gdt_ptr]

  ; move the video segment to > 0xC0000000
  mov ebx, [gdt_ptr + 2]
  or dword [ebx + 0x18 + 4], 0xC0000000

  ; move gdt to > 0xC0000000
  add dword [gdt_ptr + 2], 0xC0000000

  ; move stack to > 0xC0000000
  mov eax, [esp]
  add esp, 0xc0000000
  mov [esp], eax

  ; set page directory address to cr3 register 
  mov eax, PAGE_DIR_PHYSICAL_ADDR
  mov cr3, eax
  
  ; enable paging on cr0 register
  mov eax, cr0
  or eax, 0x80000000
  mov cr0, eax

The most important thing here is to set the CR3 register to point to page directory (note the physical address), and then turn on the paging bit switch CR0

Summarize

At this point, the part about kernel virtual memory initialization in the loader phase is over. The code of this section is not long, the core is only setup_page this function, but the principle behind it is very profound and complicated. In the loader stage virtual memory is initially established, which lays a good foundation for the memory management after entering the kernel.

At the current stage, all of our virtual-to-physical memory allocation and mapping are planned in advance, allocated before use, and each physical frame is manually arranged. In fact, this did not fully play the role of virtual memory . After entering the kernel later, we will further improve the work related to virtual memory , which will include the processing of (page fault) , the process of copying page directory

virtual memory is the core work that runs through the implementation and operation of the kernel, and must be absolutely correct and stable. Once an error occurs, the system will immediately appear various unpredictable strange errors or even crashes, and debugging is very difficult.

In the next article, we will load the real kernel into the memory and go to the kernel to start the code execution. This will be the last level before entering the kernel.


navi
612 声望191 粉丝

naive