如何在自定义32位内核分页中重映射内核到虚拟地址0xC0000000
32位内核分页重映射解决方案
1. 修改链接脚本linker.ld实现虚拟地址链接与物理地址加载
你需要在链接脚本中明确区分虚拟内存地址(VMA)和加载内存地址(LMA),让内核代码段、数据段等链接到0xC0000000起始的虚拟地址,但实际加载到物理地址0x100000。
修改示例如下:
/* 定义内核虚拟基地址 */ KERNEL_VMA = 0xC0000000; /* 内核物理加载地址 */ KERNEL_LMA = 0x100000; ENTRY(_start) SECTIONS { /* 虚拟地址从KERNEL_VMA开始 */ . = KERNEL_VMA; .text ALIGN(4K) : AT(ADDR(.text) - KERNEL_VMA + KERNEL_LMA) { *(.text) *(.rodata) } .data ALIGN(4K) : AT(ADDR(.data) - KERNEL_VMA + KERNEL_LMA) { *(.data) } .bss ALIGN(4K) : AT(ADDR(.bss) - KERNEL_VMA + KERNEL_LMA) { *(.bss) *(COMMON) } /* 定义内核结束的虚拟地址,方便后续内存管理 */ kernel_end = .; }
- 核心逻辑:
AT(ADDR(段) - KERNEL_VMA + KERNEL_LMA)计算段的物理加载地址,即虚拟地址减去内核虚拟基址,再加物理加载基址。 _start的链接地址会变为0xC0000000 + 偏移,但实际加载到0x100000 + 偏移。
2. 修改分页设置映射物理地址0x100000到虚拟地址0xC0000000
基于你已有的页目录、页表结构,需在页目录中添加对应映射项,同时暂时保留身份映射(直到完成分页切换)。
假设使用4KB页(若用4MB大页可调整偏移位):
// 页目录/页表项属性:存在(P)=1,可读可写(RW)=1,用户态不可访问(US)=0 #define PAGE_ATTR 0x3 // 虚拟地址0xC0000000对应的页目录索引(4MB/项,右移22位) #define KERNEL_VMA_DIR_INDEX (0xC0000000 >> 22) // 结果为0x300 void remap_kernel() { // 计算物理地址0x100000对应的页帧号(4KB页,右移12位) uint32_t kernel_phys_frame = 0x100000 >> 12; // 虚拟地址0xC0000000对应的页表项索引 uint32_t kernel_virt_table_idx = (0xC0000000 >> 12) % 1024; // 向页表写入映射项:物理页帧号 + 属性 uint32_t* page_table = (uint32_t*)first_page_table; page_table[kernel_virt_table_idx] = (kernel_phys_frame << 12) | PAGE_ATTR; // 将页表地址写入页目录对应项 uint32_t* page_dir = (uint32_t*)page_directory; page_dir[KERNEL_VMA_DIR_INDEX] = (uint32_t)first_page_table | PAGE_ATTR; // 刷新TLB,确保映射生效 __asm__ volatile("movl %%cr3, %%eax; movl %%eax, %%cr3" ::: "eax"); }
- 若使用4MB大页:无需页表,直接将物理地址
0x100000右移22位得到页帧号,写入页目录项,属性添加0x80(PS位,大页标志)。 - 保留身份映射:暂不删除物理地址
0x100000对应的页目录项,直到完成物理地址到虚拟地址的跳转。
3. 重映射后确保内核正常运行的关键步骤与注意事项
- 强制跳转到虚拟地址入口:启用分页后,CPU的EIP仍指向物理地址指令,需立即跳转到虚拟地址的入口点:
; 启用分页后执行 mov eax, 0xC0000000 + _start_offset ; _start_offset是_start相对于物理基址的偏移 jmp eax - 段寄存器与GDT设置:确保GDT中所有段的基址为
0,让逻辑地址等于线性地址(分页后线性地址会转换为物理地址),无需修改段寄存器,切换分页后自动生效。 - 栈地址更新:若栈原在物理地址,需将栈指针(ESP)更新为对应虚拟地址:
mov esp, 0xC0000000 + original_physical_stack_offset - 全局变量与指针修正:链接后的全局变量、函数指针均为虚拟地址,启用分页前不要访问这些变量,必须跳转至虚拟地址后再使用。
- 中断向量表(IDT)更新:将IDT中所有中断处理程序的入口地址替换为虚拟地址,因为分页启用后IDT存储的是线性地址(虚拟地址)。
- 页目录自身映射:若后续需修改页目录,需将页目录映射到虚拟地址空间(如
0xFFC00000),避免用物理地址访问导致错误。 - 清理身份映射:确认内核完全运行在虚拟地址后,可删除物理地址
0x100000对应的身份映射项,释放页表空间(可选)。
内容的提问来源于stack exchange,提问作者Ilan Vinograd
相关产品推荐
相关产品推荐

