物理内存(Physical Memory)是计算机上重要的硬件资源之一,用于在计算机运行过程中存储代码和数据。MaQueOS支持对128MB物理内存进行申请和释放。内核的二进制可执行代码被加载到0x200000地址处。
处理器中的内存管理单元(Memory Management Unit,MMU)将CPU访问的虚拟地址转换为物理地址。LoongArch架构中的MMU支持两种地址转换模式:直接地址翻译模式和映射地址翻译模式。其中,映射地址翻译模式又包括直接映射地址翻译模式(以下简称直接映射)和页表映射地址翻译模式(以下简称页表映射)。当MMU处于映射地址翻译模式时,会优先选择直接映射,若无法进行直接映射,则选择页表映射。
MaQueOS采用映射地址翻译模式进行虚拟地址到物理地址的转换。在内核态下采用直接映射,即虚拟地址与物理地址是一一映射关系;在用户态下采用页表映射 。在LoongArch架构中,有4个特权级,分别是PLV0~PLV3。MaQueOS只使用了特权级PLV0和PLV3,其中,内核态运行在特权级PLV0上,用户态运行在特权级PLV3上。LoongArch架构提供了4个用于为不同特权级配置直接映射的寄存器DMW0~DMW3。如前所述,因为只有在内核态下采用直接映射,所以MaQueOS通过使用DMW0寄存器将内核态下使用的地址转换模式设置为直接映射。
内存初始化由mem_init函数实现,mem_init函数在main函数中被调用,main函数(第3版)的实现详见代码清单3.1。
代码清单3.1 main函数(第3版)
在 第3行 中,本章实验code3中的main函数的第3版在第2版的基础上,通过调用mem_init函数,对内存进行初始化(mem_init函数的实现详见代码清单3.2)。
代码清单3.2 mem_init函数(第1版)
·第14~20行: 物理内存以页为单位进行申请和释放,页的大小为4KB。因此,128MB的物理内存总共可以划分为128MB÷4KB=32768个物理页(NR_PAGE)。MaQueOS使用一个char型数组mem_map记录所有32768个物理内存页的状态。若mem_map数组中某项的值为0,则表示该项对应的物理页空闲;若值为1,则表示该项对应的物理页被占用。每个物理页都有一个编号(以下简称页号),如图3.1所示,物理页的页号从左往右依次为0~32767,并且物理页的页号是该物理页在mem_map数组中对应项的索引。在 第16~17行 中,由于MaQueOS将物理地址空间0x200000~0x300000预留给内核的二进制可执行代码,因此需要将mem_map数组中对应的项设置为1,表示已被占用。在 第18~19行 中,除内核的二进制可执行代码占用的物理页外,需要将其他物理页在mem_map数组中的对应项设置为0,表示这些物理页都处于空闲状态。
图3.1 mem_map数组与物理页的对应关系
·第21行: 调用write_csr_64库函数将DMW0寄存器的值设置为0x9000000000000001,从而将内核态下使用的虚拟地址到物理地址的地址转换模式配置为直接映射。如图C.7所示,将PLV0字段设置为1,表示将特权级PLV0配置为直接映射;将VSEG字段设置为9,表示将直接映射窗口的虚拟地址的[63:60]位设置为9。这样,内核态下的虚拟地址空间0x9000000000000000~0x9000FFFFFFFFFFFF和物理地址空间0x0~0xFFFFFFFFFFFF就建立了一一映射。例如,在内核态下,CPU访问虚拟地址0x9000000000200000时,MMU将该虚拟地址转换为物理地址0x200000。由于BIOS将内核的二进制可执行代码加载到物理地址0x200000后,将PC寄存器的值设置为0x200000。因此,在直接映射模式下,需要将PC寄存器的值修改为0x9000000000200000。PC寄存器的修改在_start函数中完成,_start函数(第2版)的实现详见代码清单3.3。
代码清单3.3_start函数(第2版)
在 第3~5行 ,本章实验code3中的_start函数的第2版在第1版的基础上增加了修改PC寄存器的操作。具体地,将PC寄存器的值的[63:60]位设置为9。
mem_map数组的初始化完成后,通过调用get_page函数,可以申请到一个空闲物理页。get_page函数的实现详见代码清单3.4。
代码清单3.4 get_page函数
·第11~19行: 从最后一项开始,遍历mem_map数组。
·第13~14行: 若mem_map数组中项的值不为0,则表示该项对应的物理页已被占用,继续遍历mem_map数组。
·第15行: 若mem_map数组中项的值为0,则表示该项对应的物理页空闲。将该项的值置为1,表示该项对应的物理页被占用。
·第16行: 利用mem_map数组的索引i,计算空闲物理页在内核态下的起始虚拟地址。以0x1000号物理页为例,具体计算步骤如下:
①计算物理页的起始物理地址。将物理页在mem_map数组中对应项的索引作为该物理页的页号,左移12位,可以得到该物理页的起始物理地址。因此,0x1000号物理页的起始物理地址为0x1000000。
②计算物理页在内核态下的起始虚拟地址。通过将物理页的起始物理地址的[63:60]位设置为9,可以得到该物理页在内核态下的起始虚拟地址。因此,0x1000号物理页在内核态下的起始虚拟地址为0x9000000001000000。
·第17~18行: 调用set_mem库函数,将申请到的空闲物理页中的4096个字节清0后,返回该空闲物理页在内核态下的起始虚拟地址。
·第20~21行: 若遍历完mem_map数组后,仍然没有找到空闲物理页,则直接执行panic操作。
通过调用free_page函数,释放申请到的物理页。free_page函数的实现详见代码清单3.5。
代码清单3.5 free_page函数
·第7行: 利用内核态下的起始虚拟地址,计算待释放物理页在mem_map数组中对应项的索引。以在内核态下的起始虚拟地址为0x9000000001000000的物理页为例,具体计算步骤如下:
①计算物理页的起始物理地址。通过将物理页在内核态下的起始虚拟地址的[63:60]位设置为0,可以得到该物理页的起始物理地址。因此,该物理页的起始物理地址为0x1000000。
②计算mem_map数组的索引。通过将物理页的起始物理地址右移12位,可以得到该物理页的页号,即该物理页在mem_map数组中对应项的索引。因此,该物理页在mem_map数组中对应项的索引为0x1000。
·第8~9行: 若待释放物理页在mem_map数组中对应的项的值为0,说明正在释放一个空闲物理页,则直接执行panic操作。
·第10行: 若待释放物理页在mem_map数组中对应的项的值不为0,则将该项的值减1,表示该物理页被释放。