Series catalog
- Preface
- Preparation
- BIOS boot to real mode
- GDT and protected mode
- Virtual Memory
- load and enter the kernel
- Display and print
- Global Descriptor Table GDT
- Interrupt handling
- Perfect virtual memory
- implements heap and malloc
- first kernel thread
- Multi-thread switching
- lock and multi-thread synchronization
- enter user mode
- process
- system call
- simple file system
- Load executable program
- keyboard driver
- Run shell
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 is0xC0701000
;page tables
total of 1024 060fecdcd62f3d sheets are arranged in the address space0xC0400000 ~ 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
andpage 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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。