20.1 64位ELF格式

在64位模式下,由于内存地址变宽,ELF格式中的内存地址也要跟着变宽。这并不是一个麻烦的问题,因为ELF格式的整体结构没有发生变化,仍然由一个文件头,加上若干程序头表组成。

对于64位ELF格式的文件头,我们需要关注的信息如下表所示:

偏移量字节数含义
0x188程序入口地址
0x208程序头表在ELF文件中的偏移量
0x362程序头表中每个表项的大小
0x382程序头表中表项的数量

对于64位ELF格式的程序头表项,我们需要关注的信息如下表所示:

偏移量字节数含义
0x04表项类型,1为可加载的段
0x88当前段在ELF文件中的偏移量
0x108当前段需要被加载到的虚拟地址
0x208当前段在ELF文件中的大小
0x288当前段在内存中的大小

20.2 加载内核

请看本章代码20/Mbr.s

第78\~111行,读取硬盘的2\~97号扇区,并加载到0x80000处。2\~97号扇区存放的是内核ELF文件,1号扇区与98\~99号扇区保留给后续章节使用。[0x80000, 0x90000)为内核缓冲区。

第113\~116行,从ELF文件头中读取并计算程序头表的地址,表项的大小与数量。这些立即数都没有超过32位,因此是可以直接使用的。

第118\~138行,读取每个程序头表项,并将其中的段展开到目标位置。这部分的实现思路与32位操作系统一致,这里不再赘述。

第140行,跳转至内核。同样的,这个立即数没有超过32位,因此是可以直接使用的。

20.3 64位显卡驱动

请看本章代码20/Util.h

第13\~16行,定义了va_xxx系列宏。64位GCC使用的是System V AMD64 ABI,该ABI混合使用寄存器与栈传参,笔者不清楚如何在不借助编译器的前提下实现这套功能,因此,直接将这些宏转发到GCC内建的对应功能上。

20/Util.h的剩余部分,以及本章代码20/Util.hpp的实现思路与32位操作系统一致,这里不再赘述。

64位显卡驱动的实现位于本章代码20/Print.h20/Print.hpp中,其实现思路与32位操作系统一致,但有以下区别:

  • 0xb8xxx的虚拟地址是0xffff8000000b8xxx
  • 64位模式下可以使用movsq/stosq指令,一次操作8字节
  • printHex函数,以及printf%x用于打印64位无符号整数,可用于打印指针

20.4 64位内存管理系统

想要实现内存管理系统,就需要先实现位图。位图的实现位于本章代码20/Bitmap.h20/Bitmap.hpp中,其实现思路与32位操作系统一致,这里不再赘述。

接下来,请看本章代码20/Memory.h

第5\~9行,声明了内存管理系统的各个接口。

接下来,请看本章代码20/Memory.hpp

第7行,定义了内核虚拟地址的分配起点。

第8行,定义了物理地址的分配起点。

第10行,定义了内核虚拟内存池位图,以及物理内存池位图。

第11行,定义了上述两个位图的缓冲区,每个位图使用一页内存。缓冲区位于BSS段,因此无需手动清零。

memoryInit函数用于初始化两个内存池位图。

__allocateAddr函数用于从内存池位图中分配地址。

__installPage函数用于在虚拟地址与物理地址之间安装映射关系。这个函数的实现原理与二级分页模式一致:反复利用最后一个PML4E进行空兜。只是在四级分页模式下,虚拟地址转换的步骤更多。建议读者使用带二进制功能的计算器构造与检查函数中用到的各种掩码。

allocateKernelPage函数用于在内核地址空间分配连续的pageCount页虚拟地址,这些内存在返回前会被清零。

installTaskPage函数用于在任务地址空间安装虚拟地址。这个函数有两个用途:

  1. 解析任务的ELF文件时,按ELF文件中的要求预先安装虚拟地址
  2. 安装任务的3特权级栈

其中,第二个用途是一定会超出任务的内存池位图范围的,因此,在操作位图时应注意边界。

虚拟地址的安装是以页为单位的,但startAddrstartAddr + memorySize均不保证对齐到页边界,因此,应将startAddr向下对齐到页边界,并将startAddr + memorySize向上对齐到页边界,再进行地址安装。

__deallocateAddr函数用于在内存池位图中回收地址。

deallocateKernelPage函数用于回收内核虚拟页地址。回收地址时,只需要操作PTE即可。

deallocateTaskCR3函数用于回收cr3中前256个PML4E。这个函数的实现原理与二级分页模式一致:反复利用最后一个PML4E进行空兜。只是在四级分页模式下,虚拟地址转换的步骤更多。建议读者使用带二进制功能的计算器构造与检查函数中用到的各种掩码。

20.5 编译与测试

请看本章代码20/Makefile

第4行,编译内核。-fPIC选项用于打开RIP相对寻址。

第5行,链接内核。-N --no-relax均为与-fPIC配合使用的选项,代码段的起始地址为0x0

第7行,将内核从2号扇区开始写入。

本章代码20/Kernel.c测试了显卡驱动与内存管理系统的一些功能。


樱雨楼
26 声望1 粉丝

Stay Gold