背景介绍
内存管理实验执行流程概述¶
本次实验主要完成ucore内核对物理内存的管理工作。
kern_init函数在完成一些输出并对lab1实验结果的检查后,将进入物理内存管理初始化的工作,即调用pmm_init函数完成物理内存的管理,这也是我们lab2的内容。接着执行intr_enable函数开启中断,这些工作与lab1的中断异常工作的内容是相同的。
为了完成物理内存管理,这里首先需要探测可用的物理内存资源;了解到物理内存位于什么地方,有多大。
这里细心的读者一定会有疑问,在翻阅uCore(LoongArch32版本)代码后发现内存大小似乎是写死的32M,并没有像x86等架构一样通过e820去得到一个可用内存的大小,从而操作系统似乎没有完成物理内存探测的操作。确实是这样,在许多RISC架构下,通常启动系统内核会读取一个叫做设备树(Device Tree)的文件,来描述硬件上各个设备的地址和特性,设备树中也包括了内存大小,而并不是交给系统内核自己去探测。我们也可以见到许多ARM和RISC-V架构的Linux开发板,不同内存容量的版本要使用不同的镜像,也是因为设备树不同导致的。而为了简化系统的设计,我们并没有实现设备树的读取,因此采用了写死的32M。
在确定了物理内存大小后,就以固定页面大小来划分整个物理内存空间,并准备以此为最小内存分配单位来管理整个物理内存,管理在内核运行过程中每页内存,设定其可用状态(free的,used的,还是reserved的),这其实就对应了我们在课本上讲到的连续内存分配概念和原理的具体实现;接着ucore kernel就要建立页表,启动分页机制,当缺页异常发生时,就会跳转到内核的异常处理地址上,由内核完成TLB填充,根据页表项描述的虚拟页(Page)与物理页帧(Page Frame)的对应关系完成CPU对内存的读、写和执行操作。这一部分其实就对应了我们在课本上讲到内存映射、页表、多级页表等概念和原理的具体实现。
在代码分析上,建议根据执行流程来直接看源代码,并可采用GDB源码调试的手段来动态地分析ucore的执行过程。内存管理相关的总体控制函数是pmm_init函数,它完成的主要工作包括:
1. 初始化物理内存页管理器框架pmm_manager;
2. 建立空闲的page链表,这样就可以分配以页(4KB)为单位的空闲内存了;
3. 检查物理内存页分配算法;
4. 建立一一映射关系的二级页表;
5. 使能分页机制;
6. 检查页表建立是否正确;
另外,主要注意的相关代码内容包括:
* 管理每个物理页的Page数据结构(在mm/memlayout.h中),这个数据结构也是实现连续物理内存分配算法的关键数据结构,可通过此数据结构来完成空闲块的链接和信息存储,而基于这个数据结构的管理物理页数组起始地址就是全局变量pages,具体初始化此数组的函数位于page_init函数中;
* 用于实现连续物理内存分配算法的物理内存页管理器框架pmm_manager,这个数据结构定义了实现内存分配算法的关键函数指针,而同学们需要完成这些函数的具体实现;
* 设定二级页表和建立页表项以完成虚实地址映射关系,这与硬件相关,且用到不少内联函数,源代码相对难懂一些。具体完成页表和页表项建立的重要函数是boot_map_segment函数,而get_pte函数是完成虚实映射关键的关键。