物理内存分配器: 管理内存分配出去的页 表以及有多少个进程分享每一个已经被分配的页表. 如何收回与释放内存页表.
虚拟内存分配器: 将内核与用户程序的虚拟地址与物理地址进行对应, 实现这一功能的就是 memory management unit (MUM), 使用的是页表
memlayout.h
是虚拟内存的描述, 之前的笔记中已经记录了用法, 下面是一个十分重要的结构, 用来表示一个物理页的状态:
/*
* Page descriptor structures, mapped at UPAGES.
* Read/write to the kernel, read-only to user programs.
* 存出来一个物理页的基本信息, 并不是物理页本身, 而且我们也不必对物理页的所有信息进行描述
* 这个结构体与物理页一一对应, 可以通过 page2pa() 获得一个物理页描述符的物理地址
*/
// 描述了一个物理页的状态
struct PageInfo {
// 这是一个虚拟地址, 指向一个物理页描述符结构体
// Next page on the free list.
struct PageInfo *pp_link;
// 表示有多少个虚拟地址指向该页
uint16_t pp_ref;
};
物理页的状态可以是空闲的, 此时 pp_ref = 0, 也可以是被占用, 此时 pp_ref = 1, 可以这么理解, PageInfo 的物理地址就是该页的物理地址.
内存管理的部分与前面的内核启动的部分不一样, 内核启动的部分代码需要在启动的时候执行, 而内存管理的部分编译后存储在内核文件中(ELF 文件, 代码段与数据段), 所以而我们写的部分是编译之前的代码, 相当于对内核进行内存管理的功能描述. 这些代码的执行是在启动之后.
首先我们需要明确一些变量的定义:
size_t npages; // Amount of physical memory (in pages), 表示物理内存的大小
pde_t *kern_pgdir; // Kernel‘s initial page directory, 内核的页目录
struct PageInfo *pages; // Physical page state array, 物理页状态数组, 记录了物理页的状态,
// 问题: 如何获得物理页的物理地址, 根据下面的内容我们直到, 直接使用 pages 数组的下标就可以访问物理地址了, 因为物理页以及以 PGSIZE 为单位划分
static struct PageInfo *page_free_list; // Free list of physical pages, 空闲物理页链表, 将空闲的物理页连成一个链表
当计算机刚开始启动虚拟地址机制的时候, 因为要构造一个二级页表, 首先分配的是一个页目录, 内核文件执行的时候的页目录是一个静态的目录, Lab1 描述的, 已经存在在内核文件的数据段中, 可以直接访问, 那么在构建页表机制的时候, 构建页目录的方法是, 在内核空闲数据段的最末端分配一个页(4KB)大小的空间, 作为页目录, 分配这样一个空间的函数是:
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
// Initialize nextfree if this is the first time.
// ‘end‘ is a magic symbol automatically generated by the linker,
// which points to the end of the kernel‘s bss segment: 根据 ELF 文件的格式, 这里就是内核数据段的末尾
// 也可以根据前面所使用的查看内核 ELF 文件的格式来查看, 得打 bss 段的内容如下:
// Idx Name Size VMA LMA File off Algn
// 9 .bss 00000648 f0113060 00113060 00014060 2**5
// 内核文件在虚拟内存中, 数据段在代码段的上面
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
extern char end[];
// 将地址 end 向上以页面大小对齐
nextfree = ROUNDUP((char *) end, PGSIZE);
}
// Allocate a chunk large enough to hold ‘n‘ bytes, then update
// nextfree. Make sure nextfree is kept aligned to a multiple of PGSIZE.
//
// LAB 2: Your code here.
// 这里的 free memory 是在 KERNBASE 上面的虚拟地址空间中的 free memory, 也就是内核的数据段
result = nextfree;
// 这里是一个虚拟地址
if(n > 0)
{
nextfree = ROUNDUP(result+n, PGSIZE);
// 这里相当于在 free memory 分出一部分内存, 以 PGSIZE 对齐
}
cprintf("boot_alloc memory at %x, next memory allocate at %x\n", result, nextfree);
return result;
}
分配页目录之后就是建立二级页表, 这里需要先初始化一下内存, 以及虚拟内存中的一些数据:
// create initial page directory.
// 分配一个物理页大小的虚拟空间, 对于页目录来说, 这里使用 boot_alloc, kern_pgdir 就是页目录的虚拟地址
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);
// 下面对页目录的内容进行初始化
// Permissions: kernel R, user R
// UVPT 是页表的虚拟地址, 下面式子的左边是查找页目录, 找出页表的物理地址
// 右边是页目录的物理地址, 也就是说页表的开头与页目录地址相同, 这是由于页目录自映射, 页目录本身也是页表的一部分,
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
// 下面是描述物理内存的状态
// 物理页描述符的数组,
pages = (struct PageInfo*)boot_alloc(sizeof(struct PageInfo) * npages);
// pages 是物理内存状态数组
memset(pages, 0, sizeof(struct PageInfo) * npages);
// 这一部分是确定物理内存的状态以及页目录初始信息
接下来对物理内存的描述进行初始化, 在分配物理内存, 设置页目录机制之前, 物理内存中有很多地方已经不是空闲的了, 所以对 pages 的描述就会有所改变, 比如说物理内存中分配给 IO 段的内存, 直到内核的数据段的末尾都是已经已经分配过的物理内存, 这一部分已经被分配, 所以不在空闲链表中. 下面就初始化 pages 数组,
void page_init(void)
{
// 获取 IO 数据段的物理地址
size_t io_hole_start_page = (size_t)IOPHYSMEM / PGSIZE;
// If n==0, returns the address of the next free page without allocating anything.
// 使用 boot_alloc(0) 找出未被分配的物理地址, 就是内核分配的末尾的物理地址
size_t kernel_end_page = PADDR(boot_alloc(0)) / PGSIZE;
size_t i;
for (i = 0; i < npages; i++) {
if(i == 0){
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
if(io_hole_start_page <= i && i < kernel_end_page)
{
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
else
{
pages[i].pp_ref = 0;
// 这一步是形成空闲链表
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
}
}
分配了物理页空闲链表就实现了对物理内存的更好的管理, 我们不仅知道了物理内存的状态, 还知道了哪些物理页是空闲的, 注意: page_free_list 空闲链表构成的方法是通过 pages[i] 的 pp_link 构造的, page_free_list 是空闲链表的开头, 内容是物理页描述符, 而不是真正的物理页. 描述了物理页之后, 分配一个物理页就很简单了,
struct PageInfo * page_alloc(int alloc_flags)
{
// Fill this function in
struct PageInfo *new_alloc = page_free_list;
if(new_alloc == NULL)
{
// 分配失败, 没有空闲的空间
cprintf("page_alloc: out of free memory\n");
}
// 将 page_free_list 向后移动一步, 表示一个物理页被占用
page_free_list = new_alloc->pp_link;
// 这一页已经被分配了, 所以 pp_link == NULL
new_alloc->pp_link = NULL;
if(alloc_flags & ALLOC_ZERO)
{
memset(page2kva(new_alloc), 0, sizeof(struct PageInfo));
}
return new_alloc;
}
我们是重要注意的是, 物理内存的描述就是 pages, 所以释放与分配的过程对 pages 的操作相反:
void page_free(struct PageInfo *pp)
{
// Fill this function in
// Hint: You may want to panic if pp->pp_ref is nonzero or
// pp->pp_link is not NULL.
if(pp->pp_ref == 0 && pp->pp_link == NULL)
{
pp->pp_link = page_free_list;
page_free_list = pp;
}
else
{
// 释放一个页之前, 要判断是否被使用
panic("page_free: pp->pp_ref is nonzero or pp->pp_link is not NULL\n");
}
}
原文:https://www.cnblogs.com/wevolf/p/12634262.html