内存管理实现高度依赖硬件,比如MMU:处理CPU存储访问请求的硬件。目前大多数系统,如linux,均采用按需虚拟页式存储,物理地址空间是指硬件支持的地址空间,逻辑地址空间是指CPU运行的进程看到的地址
0x00 bootloader探测真实物理内存大小(bootasm.S)
bootloader通过INT 15h中断,中断号为e820h,获取空闲物理内存信息,将数据按struct e820map的格式保存在虚拟空间的0xc0000000+0x8000处
0x01 使能段页式内存管理机制的设计方案(entry.S)
想要最终实现的段页式的映射关系是:
Virtual Address = Linear Address = Pythal Address + 0xC0000000
实模式情况下,是没有页机制的,ld在链接阶段生成了ucore的虚拟地址,是0xc0100000,但是ucore需要运行在低物理内存空间,所以,从计算机加电到启动段页式管理,映射关系发生了多次的变化,我们通过以下三个阶段去实现这个映射关系:
第一阶段(开启保护模式,创建启动段表)
该阶段是bootloader阶段,此时cpu是实模式,所以地址映射关系如下:
Virtual Address = Linear Address = Pythal Address
虽然ld链接文件说明了ucore内核的装载空间是0xC0100000,但是bootloader会将ucore装载到0x100000中
1 | readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); |
第二阶段(创建初始页目录表,开启分页模式)
在entry.S的kern_entry函数中,初始化了一个临时页目录表和页表:
1 | kern_entry: |
解释下临时映射的建立:第9到第13行
第一句,将页表的物理地址填入页目录表的第一项,实现map va 0 ~ 4M to pa 0 ~ 4M.space 空字节数,后面跟数量 .space 4
第二句,填充页目录表,填到0xc0000000那项,KERNBASE = 0xc0000000,PGSHIFT = 12,向右移12位,接着向右移10位,相当于被4K*1k(4M)除,因为页目录表项一项映射的地址空间是4M,然后左移两位,相当于乘4,因为每一项占4字节,然后减去(当前地址减页目录表的地址),相当于减去页目录表第一项的地址
第三句,完成map va KERNBASE + (0 ~ 4M) to pa 0 ~ 4M
第四句,将页目录4K对齐
映射关系为:
Virtual Address = Linear Address = Pythal Address(0~4M的线性地址空间)
Virtual Address = Linear Address = Pythal Address + 0xC0000000(0xC0000000~0xC0000000+4M的线性地址空间)
为什么要这样映射呢?
ucore此时在0~4M的低虚拟地址区域运行,为了保证使能段页机制后ucore能够正常运行,所以添加了Virtual Address = Linear Address = Pythal Address(0~4M的线性地址空间)
的映射关系
接下来调整内核的EIP到高虚拟地址
1 | # update eip |
然后清除(0~4M的映射关系)
1 | # unmap va 0 ~ 4M, it's temporary mapping |
此时的映射关系就只有:
Virtual Address = Linear Address = Pythal Address + 0xC0000000(0xC0000000~0xC0000000+4M的线性地址空间)
第三阶段(完善段表和页表)
将页目录表从(0~4M扩充到0~KMEMSIZE)
最后的映射关系为:
Virtual Address = Linear Address = Pythal Address + 0xC0000000
0x02初始化物理内存管理(pmm.c:page_init)
从物理内存获取空闲内存布局struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE);
通过物理布局获得最大的物理地址空间,但是最大不大于0x38000000,从而计算得到需要多少物理页去管理物理内存
1 | if (maxpa < end && begin < KMEMSIZE) { |
因为加载ucore的结束地址(用end全局指针变量记录)以上的高地址空间没有被使用,所以设为管理物理内存的结构体Page的内存空间起始地址为 pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);
为了简化起见,已使用物理空间设为0~pages,空闲物理空间设为pages~0x38000000。
结尾调用init_memmap函数初始化空闲物理地址空间。每一页物理帧都对应一个Page结构体
1 | struct Page { |
接着对所有物理页进行占用标记
1 | for (i = 0; i < npage; i ++) { |
0x03 初始化空闲物理内存(default_pmm.c:default_init_memmap)
清除标志空闲物理内存的结构体Page的标志位,表示pages~KMEMSIZE为空闲内存,
1 | for (; p != base + n; p ++) { |
设置管理连续内存空闲块的结构体
1 | typedef struct { |
TIPS:pages~0x38000000的每一个物理页都对应一个Page结构体,ucore通过Page结构体去管理物理内存,free_area_t结构体记录空闲的连续物理内存块。
0x04 first-fit连续物理内存分配算法(实验)
思路
first-fit算法的思路就是按序遍历,当大小满足即分配,具体来说:当申请内存时first-fit的思路是从空闲链表的表头开始寻找地址最小的物理页,通过nr_free->next或者通过定义好的函数list_next去寻找,找到空闲物理页后,需要判断大小是不是满足,这主要通过Page结构体的property成员判断,所以需要通过le2page宏,通过链表元素指针nr_free计算出相应的Page指针p,当property>=n时,大小满足,即分配出去。
实现
1 | static struct Page * |