分页开启后内核内部函数地址解析及高半区内核相关疑问
关于高半区内核的核心问题解答
1. 链接后的地址是虚拟地址还是物理地址?
链接完成后,内核的函数调用/跳转地址都是虚拟地址,并非物理地址。高半区内核在链接阶段就直接按照预设的虚拟地址空间布局生成可执行文件,所有符号(函数、全局变量)的地址都是硬编码的虚拟地址,而非物理内存地址。
2. 链接脚本如何确定函数的虚拟地址?
链接脚本由开发者自行编写,用来明确指定内核虚拟地址空间的布局规则:
- 首先定义内核的虚拟基地址(比如x86架构下常用
0xC0000000作为高半区起始地址) - 然后将代码段(
.text)、数据段(.data)、BSS段等分别映射到虚拟地址空间的对应位置 - 链接器会严格遵循脚本中的地址规则,为每个符号分配对应的虚拟地址,最终生成符合预期布局的内核镜像
举个简化的链接脚本示例:
ENTRY(_start) KERNEL_VIRT_BASE = 0xC0000000; KERNEL_PHYS_BASE = 0x100000; SECTIONS { . = KERNEL_VIRT_BASE; .text ALIGN(4K) : AT(KERNEL_PHYS_BASE) { *(.text) } .data ALIGN(4K) : AT(KERNEL_PHYS_BASE + SIZEOF(.text)) { *(.data) } .bss ALIGN(4K) : { *(.bss) } }
其中AT()指定段在物理镜像中的偏移,.设置的是虚拟地址起始点,链接器会以此为基准给符号分配虚拟地址。
3. 虚拟地址与物理地址不同时,内核如何被加载?
整个过程分为两个关键阶段:
- 加载阶段:bootloader(如GRUB)会把内核镜像加载到物理内存的指定位置(比如
0x100000,即1MB处),这个位置由链接脚本中AT()指定的物理偏移决定,和虚拟地址无关。 - 分页开启前的过渡:分页开启前内核仍在物理地址空间运行,需处理虚拟地址与物理地址的差值(即偏移量,比如
0xC0000000 - 0x100000 = 0xBFF00000),步骤通常为:- 预先设置好页表,将高半区虚拟地址映射到对应的物理内存(比如
0xC0000000映射到0x100000) - 通过一段位置无关代码(PIC)完成从物理地址到虚拟地址的跳转——因为此时指令中的地址是虚拟地址,必须等分页开启后才能正确解析,跳转前要确保页表生效,并将CPU指令指针切换到虚拟地址空间
- 预先设置好页表,将高半区虚拟地址映射到对应的物理内存(比如
x86架构下的过渡代码示例:
; 假设页表已加载到CR3寄存器 mov eax, cr0 or eax, 0x80000000 ; 开启分页 mov cr0, eax ; 跳转到虚拟地址的后续执行逻辑 jmp 0xC0000000 + _start_after_paging
分页开启后,所有硬编码的虚拟地址就能被页表正确解析为对应的物理地址。
4. 内核如何解析指向物理内存的地址?
如果内核需要访问物理地址(比如操作硬件寄存器、管理页表),通常通过虚拟地址映射实现:
- 要么预先在页表中把目标物理地址范围映射到虚拟地址空间的固定区域(比如将物理内存前1MB映射到
0xC0000000起始的区域) - 要么动态创建临时映射,使用完毕后销毁
分页开启后,CPU仅识别虚拟地址,所有物理地址必须通过页表映射为虚拟地址才能被访问,内核不会直接使用物理地址操作内存。
内容的提问来源于stack exchange,提问作者Hououin_kyouma
相关产品推荐
相关产品推荐

