初级内存管理
展示如何获取物理内容信息、统计可用物理内存 页数量以及内存分配物理页等相对基础的功能。
关于可用物理内存的相关信息,已经在Loader引导加载程序中通过BIOS中断服务程序获得,同时系统采用2MB物理页。
获取物理内存信息
物理信息通过BIOS中断服务程序INT 15h AX=E820h获得,并保存在物理地址7E00h处。接下来,将会把物理地址7E00h处的信息提取出来,转换成相应结构体再加以统计。
地址7E00h出的物理地址空间信息存有若干组,他们描述计算机平台的地址空间划分情况,其数量会依据当前主板硬件配置和物理内存容量信息而定,每条物理址空间信息占20B,详细 定义如代码
struct Memory_E820_Formate
{
unsigned int address1;
unsigned int address2;
unsigned int length1;
unsigned int length2;
unsigned int type;
};
准备好数据解析结构后,视同结构体格式化物理地址7E00h处的数据。不过7E00h是物理地址,必须经过页表映射后才能配程序使用,转换后的线性地址是ffff800000007e00h,这边是程序要操作的目标地址。
分配可用物理内存页
在操作系统中,内存管理单元是操作系统最为重要的一个组成部分,而物理内存页是内存 管理单元的基础管理对象。因此,初始化内存管理单元的首要任务就是 汇总可用的物理内存也的相关信息,放可实现可用物理内存也的分配功能。
为了汇总可用物理内存信息并方便以后管理,现在特将整个内存空间(通过E820功能返回的各个内存段信息包括RAM,ROM 保留空间等),按2MB大小的物理内存页进行分割和对齐。分割后的每个物理内存页由一个struct page结构体负责管理,然后使用区域空间结构体struct zone代表各个可用物理内存区域(可用物理段),并记录和管理本区域物理内存页的分配情况。最后将struct page 和struct zone 结构体都保存到全局结构体struct Global_Memory_Descriptor内。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bp3kX7Gu-1635080641317)(C:\Users\86187\AppData\Roaming\Typora\typora-user-images\image-20211020200122002.png)]
struct结构包含所有内存页结构体,zones_struct结构体包含所有区域空间结构体,他们增强了全局结构体struct Global_Memory_Descriptor中索引出对应的区域空间结构和内存结构,并调整区域空间结构的管理信息与页结构的属性和参数。所需要的数据结构有
struct E820
{
/* data */
unsigned long address;
unsigned long length;
unsigned int type;
}__attribute__((packed));
struct Global_Memory_Descriptor
{
struct E820 e820[32]; //物理内存段结构数组
unsigned long e820_length; //物理内存段结构数组长度
unsigned long * bit_map; //物理地址空间页映射位图
unsigned long bit_size; //物理地址空间页数量
unsigned long bit_length; //物理地址空间也映射位图长度
struct Page * pages_struct; // 指向全局struct page 结构体数组的指针
unsigned long pages_size; // struct page结构体总数
unsigned long pages_length; // struct page 结构体数组长度
struct Zone * zones_struct; //指向全局struct zone 结构体数组的指针
unsigned long zones_size; // 结构体数量
unsigned long zones_length; // 结构体数组长度
// 内核程序的起始代码段地址
unsigned long start_code,
end_code, // 内核程序的结束代码段地址
end_data, //内核程序的结束数据段地址
end_brk; // 内核程序的结束地址
unsigned long end_of_struct; // 内存页管理结构的结尾地址
};
extern struct Global_memory_Descriptor memory_management_struct;
struct Global_Memory_Descriptor memory_management_struct = {{0},0};
struct Page
{
struct Zone * zone_struct;// 指向本页所属的区域结构体
unsigned long PHY_address;// 页的物理地址
unsigned long attribute; // 页的属性
unsigned long reference_count; //描述的是该页的引用次数
unsigned long age; // 描述的是该页的创建时间
};
struct Zone
{
struct Page * pages_group;//struct page 结构体数组指针
unsigned long pages_length; // 本区域包涵的struct page结构体数量
unsigned long zone_start_address;// 本区域的起始页对齐地址
unsigned long zone_end_address; // 本区域的结束页对齐 地址
unsigned long zone_length; // 本区域经过页对齐后的地址长度
unsigned long attribute; // 本区域空间的属性
struct Global_Memory_Descriptorr * GMD_struct;//指向 全局结构体 struct Global_Memory_Descriptor
unsigned long page_using_count; // 本区域已使用物理内存页数量
unsigned long page_free_count; // 本区域空闲物理内存页数量
unsigned long total_pages_link; // 本区域物理页被引用次数
};
表中的bits_***相关字段是page结构体的位图映射,他们是一一对应的关系。建立bits位图映射的目的是为了方便检索pages_struct中的空闲页表映射,而pages_xxx和zones_xxx相关变量就比较好理解,他们用来揭露page和zone结构体数组的首地址以及资源分配情况等信息。至于start_code、end_code、end_data、end_brk这4个成员变量,他们用于保存内核程序编译后的各段首尾地址(包括代码段、数据段、BSS段等),而 首尾地址则是kernel.lds文件中定义
掩码
掩码是串二进制代码对目标字段进行位与运算,屏蔽当前的输入位。
代码中追加了判断条件,来截断并提出E820数组中的脏数据。之后,根据物理地址空间划分信息计算物理地址空间 的结束地址(目前的结束地址位于最后一条物理内存段信息中,但不排除其他可能性。)。把物理地址空间的结束地址按2MB页对齐,从而统计出物理地址空间可分页数。这个物理地址空间可分页数不仅包括可用物理内存,还包括内存空洞和ROM地址空间,将物理地址空间可分页数赋值给bits_size成员变量。成员变量bits_map是映射位图的指针,他指向内核程序结束地址end_brk的4KB上边界对齐
内存布局
内存最多可从DMA区域空间、已映射区域空间或者未映射页表区域空间里,一次性申请64个连续的物理页,并设置这些物理页对应的struct page属性。
alloc_pages会根据zone_select参数来判定需要检索的内存区域空间,如果zone_select参数无法匹配到相应的区域空间,则会打印错误日志信息并使函数返回。目前的bochs虚拟机只能开辟出2GB的物理内存空间,以至于虚拟平台仅有一个可用物理内存段,因此zone_dma_index、zone_normal_index和zone_unmaped_index三个变量均代表同一内存区域空间,即使用默认值0代表的内存区域空间。
既然已经确认检测的目标内内存区域空间,接下来将从该区域空间中遍历出申请条件的struct page结构体数组。