这一节主要主要是分页和内存管理。
所需要的文件在Github:https://github.com/yongkangluo/Ubuntu20.04OS/tree/main/Files/Lec5-Memory
1. 基础知识
多级页表:
在 x8086 里面使用的二级页表。其中第一级页表称作 PDE(Page Directiory)二级页表是 PTE(Page Table)具体设置情况如图。
2. 内存布局
由于内核代码在0x10000,所以可以将内核代码重映射到高半区空间——0xC0000000。
而对于前 1 MiB 的代码,使用对等映射。
3. 实现
// boot.S
#define KPG_SIZE 6*4096 //一个PD,五个PT
.section .kpg
.global _k_ptd
_k_ptd:
.skip KPG_SIZE, 0
//这里是将multiboot信息挪个位置
movl $mb_info, 4(%esp)
movl %ebx, (%esp) // ebx 基地址寄存器(base)
call _save_multiboot_info
// 高半核初始化,将KPG的地址和大小入栈作为参数
movl $(KPG_SIZE), 4(%esp)
movl $(_k_ptd - 0xC0000000), (%esp) /* 将页表的基地址作为参数传递给初始化函数 */
call _hhk_init
/* 加载PTD基地址 */
movl (%esp), %eax
andl $0xfffff000, %eax //这里就是将低12位置置零
movl %eax, %cr3
movl %cr0, %eax
orl $0x80000000, %eax /* 开启分页与地址转换 (CR0.PG=1, CR0.WP=1) */
movl %eax, %cr0
addl $16, %esp
/* 进入高半核! */
pushl $hhk_entry_
其中_hhk_init 收到 ptd 即 $(_k_ptd - 0xC0000000) 作为页目录的基地址。
// hhk.c
void _hhk_init(ptd_t* ptd, uint32_t kpg_size)
{
// 初始化 kpg 全为0
uint8_t* kpg = (uint8_t*)ptd;
for (uint32_t i = 0; i < kpg_size; i++) {
*(kpg + i) = 0;
}
_init_page(ptd);
/*其中主要完成的工作就是将 1 Mib 之前的对等映射;
将内核代码映射到高半区;
*/
}
// hhk.c
#define PT_ADDR(ptd, pt_index) ((ptd_t*)ptd + (pt_index + 1) * 1024)
/* 因为是连续的,所以可以直接加和计算*/
#define SET_PDE(ptd, pde_index, pde) *((ptd_t*)ptd + pde_index) = pde;
#define SET_PTE(ptd, pt_index, pte_index, pte) \
*(PT_ADDR(ptd, pt_index) + pte_index) = pte;
#define sym_val(sym) (uintptr_t)(&sym)
当 _hhk_init 执行结束,并开启分页后,就进入了 hhk_entry_ 高半核代码:
// prologue.S
.section .text
.global hhk_entry_
hhk_entry_:
/* 加载GDT */
...
movl $mb_info, (%esp)
call _kernel_init
mov $0xffbffff0, %esp
call _kernel_post_init
subl $6, %esp
...
/* IDT 初始化各寄存器*/
...
其中 _kernel_init 主要做的是内存地图的初始化,并分配虚拟页和物理页之间的映射。还有一些初始化操作。
具体的实现映射关系在pmm.c 和 cmm.c 文件中,不赘述了。
注意细节:
- 利用 128k bitmap 用来标记物理页是否被指向;
- 跳过 0x0 页;
- 采用循环映射,即最后一个PDE指向第一个PDE。
方法主要跟着B站Up主做的,B站视频链接在:https://www.bilibili.com/video/BV1jL4y1s7X6/?spm_id_from=333.788&vd_source=72ce864f895f9fbf22b81450817f2875