MIT_xv6_Lab2

内存管理

内存管理的内容

物理内存分配器: 管理内存分配出去的页 表以及有多少个进程分享每一个已经被分配的页表. 如何收回与释放内存页表.

虚拟内存分配器: 将内核与用户程序的虚拟地址与物理地址进行对应, 实现这一功能的就是 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");
	}
	
}

猜你喜欢

转载自www.cnblogs.com/wevolf/p/12634262.html