4

Series catalog

Open up virtual space

In Virtual Memory Preliminary Study , a virtual memory framework has been initially established for the kernel in the loader stage, including page directory, page table, etc. In that article, we have opened up the first three 4MB of the kernel space above 0xC0000000, and manually specified their functions:

  • 0xC0000000 ~ 0xC0400000 : Map the initial low 1MB memory;
  • 0xC0400000 ~ 0xC0800000 : page table;
  • 0xC0800000 ~ 0xC0C00000 : kernel loading;

At this stage, all the memory is manually planned by us, and the virtual-to-physical mapping is also manually allocated, which of course is not a long-term solution. Subsequent virtual memory will be dynamically allocated in a more flexible way. The mapped physical memory is no longer allocated in advance, but is used on demand, which requires the processing of page fault

Page fault exception

page fault will not be repeated here. We interrupt processing , but its processing function is just a demo, and it did not really solve the problem of page fault. Now we will solve it. .

There are two core issues handled by page fault

  • Determine the virtual address where the page fault occurred and the type of exception;
  • Allocate a physical frame and establish a mapping;

page fault details

The first question is relatively simple, let's look at the code directly:

void page_fault_handler(isr_params_t params) {
  // The faulting address is stored in the CR2 register
  uint32 faulting_address;
  asm volatile("mov %%cr2, %0" : "=r" (faulting_address));

  // The error code gives us details of what happened.
  // page not present?
  int present = params.err_code & 0x1;
  // write operation?
  int rw = params.err_code & 0x2;
  // processor was in user-mode?
  int user_mode = params.err_code & 0x4;
  // overwritten CPU-reserved bits of page entry?
  int reserved = params.err_code & 0x8;
  // caused by an instruction fetch?
  int id = params.err_code & 0x10;
  
  // ...
}
  • The address where the page fault occurred is stored in the cr2 register;
  • The type of page fault and other information are stored in error code ;

Remember error code ? As mentioned in the last interrupt handling , for some exceptions, the CPU will automatically push the error code into the stack and record some information about the exception. page fault is one such:

The error code is easy to get to, it's in page_fault_handler parameters isr_params_t in, remember this struct it? It corresponds to the interrupt context in the green part of the figure that is pushed onto the stack and passed as a parameter to the pink interrupt handler:

typedef struct isr_params {
  uint32 ds;
  uint32 edi, esi, ebp, esp, ebx, edx, ecx, eax;
  uint32 int_num;
  uint32 err_code;
  uint32 eip, cs, eflags, user_esp, user_ss;
} isr_params_t;

page_fault_handler error code is parsed in 060fecdcd6297c, which records a lot of useful information. For us, there are two fields that are more important:

  • present : Is it the page fault caused by the page not being allocated?
  • rw : The instruction that triggers the page fault, is the memory access operation write?

They correspond to the two bits page table

  • present easy to understand. If it is 0, it means that the page is not mapped to the physical frame, then a page fault will be caused, which is the most common cause of page fault;
  • But even if the present bit is 1, but the rw bit is 0, if you write to the memory at this time, a page fault will be triggered. We need to deal with this situation specially; this will be used in the later process fork copy-on-write Detailed explanation in technology;

Assign physical frame

Before the allocation of physical memory, we planned it manually, neatly, and currently used up 0 ~ 3MB of space. But starting from the back, we need to establish a data structure for the remaining frames to manage them. The requirements are nothing more than two:

  • Assign frame;
  • Return frame

Therefore, a data structure is needed to record which frames have been allocated and which are still available. Here, bitmap used to complete this work. bitmap hope you are not unfamiliar with it. Its principle is very simple and simple. It uses a series of bits, and each bit represents a true/false. We will use it here to indicate whether the frame has been used.

Of course, as a poor and white kernel project, bitmap needs to be implemented by ourselves. My simple implementation code is src/utils/bitmap.c . You can see src/utils . There are various data structures that I have implemented. This will be used later.

typedef struct bit_map {
  uint32* array;
  int array_size;  // size of the array
  int total_bits;
} bitmap_t;

My bitmap is very simple, using an int array as storage:

The allocation is also very simple and rude, that is, starting from 0 to find one by one, until found. Of course, it has the worst O(N) time complexity, but performance is not a factor that our project needs to consider for the time being. Our current goal is simple and correct.

And this is a layer problem: our bitmap is used to solve the page fault. It has not been solved page fault heap has not been implemented. It is very troublesome to implement a complex data structure. . Complex data structures are bound to involve dynamic allocation of memory, and once dynamic allocation, page fault will be triggered again at any time, then we are back to the original point.

So a simple data structure that can allocate static memory in advance is the simplest and most efficient way for us to implement. Here bitmap and its internal array array are src/mem/paging.c . They have been compiled inside the kernel and belong to the data or bss segment. Of course, there is no need to consider the issue of memory allocation.

static bitmap_t phy_frames_map;
static uint32 bitarray[PHYSICAL_MEM_SIZE / PAGE_SIZE / 32];

Note that the length of the array is PHYSICAL_MEM_SIZE / PAGE_SIZE / 32 , which should not be difficult to understand.

Therefore, the problem of frame allocation is very simple, it is just about the operation of bitmap:

int32 allocate_phy_frame() {
  uint32 frame;
  if (!bitmap_allocate_first_free(&phy_frames_map, &frame)) {
    return -1;
  }
  return (int32)frame;
}

void release_phy_frame(uint32 frame) {
  bitmap_clear_bit(&phy_frames_map, frame);
}

Handling page fault

Everything is in place, and the next issue of page fault is actually a matter of course. page_fault_handler will call the map_page function:

page_fault_handler
  --> map_page
    --> map_page_with_frame
      --> map_page_with_frame_impl

Finally came to map_page_with_frame_impl this function, this function is slightly longer, but the logic is very simple, here is a pseudo code for its annotation:

find pde (page directory entry)
if pde.present == false:
    allocate page table

find pte (page table entry)
if frame not allocated:
    allocate physical frame

map virtual-to-physical in page table

The data structure of pde and pte src/mem/paging, h . With the help of the C language, everything becomes very convenient. You don’t need to be dazzled by each bit like in the loader before:)

Note that in the code, our access to page direcotry and page tables all use virtual addresses, which was virtual memory in the article:

  • page directory address is 0xC0701000 ;
  • page tables total of 1024 060fecdcd62f3d sheets are arranged in the address space 0xC0400000 ~ 0xC0800000

Virtual memory handling of process fork

The code also involves rw of copy-on-write and the copying of the entire virtual memory of the current process, which are used later when the process system calls fork . Simply put, when a process forks, several things need to be done to virtual memory:

  • Copy the entire page directory and page tables , so that the memory space of the new process is actually a mirror image of the old process;
  • The kernel space of the new and old processes is shared, and the memory of the user space is copy-on-write mechanism;

This article will not expand, and fill in this hole when the system calls fork in the later process.


navi
612 声望191 粉丝

naive