填写已有实验
本实验依赖实验1.请把要做的实验1的代码填入本实验中代码有lab1的注释相应部分
发现缺失的是kdebug.c、trap.c
两个文件的相关代码,补全后进行下一练习
首先运行
报错,看来就是需要进行实验的所有编程才能完整的运行
实现firstfit连续物理内存分配算法(需要编程)
在实现firstfit内存分配算法的回首函数时,要考虑地址连续的空闲块之间的合并操作
在实验前先仔细学习一下firstfit算法
要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的分区为止;然后再按照作业的大小,从该分取中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直到链尾都不能找到一个能满足要求的分区,则此次内存分配失败,返回。该算法倾向于优先利用内存中低地址部分的空闲分区,从而保留了高址部分的大空闲区。这给为以后到达的大作业分配大的内存空间创造了条件,其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,而每次查找又都是从低址部分开始,这无疑会增加查找可用空闲分区时的开销。
大致流程图
为了与以后的分页机制配合,首先需要建立对整个计算机的每一个物理页的属性,用结构体Page来表示,它包含了映射此物理页的虚拟页个数,描述物理页属性的flags和双向链接各个Page结构的page_link双向链表。
struct Page{
int ref;
uint32_t flags;
unsigned int property;
list_entry_t page_link;
}
ref
表示该页被页表的引用记数。如果这个页被页表引用了,即在某页表中有一个页表项设置一个虚拟页到这个Page管理的物理页的映射关系,就会把Page的ref加一。反之,若页表项取消,即映射关系解除,就减一。
flags
表示此物理页的状态标记,有两种属性,bit 0表示是否被保留,如果被保留了则设为1,且不能放到空闲页链表中,即这样的页不是空闲页,不能动态分配与释放。比如内核代码占用的空间。bit 1表示此页是否是空闲的。如果设置为1,表示这页是空闲的,可以被分配;如果设置为0,表示这页已经被分配出去了,不能被再二次分配。
property
用来记录某连续内存空闲块的大小(即地址连续的空闲页的个数)。这里需要注意的是用到此成员变量的这个Page比较特殊,是连续内存空闲地址最小的一夜(即第一页)。
page_link
是便于把多个连续内存空闲块链接在一起的双向链表指针,连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块
为了有效的管理这些小连续内存空闲块,所有的连续内存空闲块可用一个双向链表来管理,便于分配和释放,为此定义一个free_area_t
typedef struct {
list_entry_t free_list; // the list header
unsigned int nr_free; // number of free pages in this free list
} free_area_t;
free_list
是一个list_entry结构的双向链表指针nr_free
则记录当前空闲页的个数有了这两个数据结构,就可以管理起来整个以页尾单位的物理内存空间
理解完原理,开始进行实验
首先根据实验指导书,我们第一个实验需要完成的主要是default_pmm.c
中的default_init
,default_init_memmap
,default_alloc_pages
, default_free_pages
几个函数的修改。
static void
default_init(void) {
list_init(&free_list);
nr_free = 0;
}
看下注释
(2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0.
free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks.
根据注释代码已经完成无需改动
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p));
p->flags = p->property = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
nr_free += n;
list_add(&free_list, &(base->page_link));
}
查看一下注释
* (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap
* This fun is used to init a free block (with parameter: addr_base, page_number).
* First you should init each page (in memlayout.h) in this free block, include:
* p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c),
* the bit PG_reserved is setted in p->flags)
* if this page is free and is not the first page of free block, p->property should be set to 0.
* if this page is free and is the first page of free block, p->property should be set to total num of block.
* p->ref should be 0, because now p is free and no reference.
* We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); )
* Finally, we should sum the number of free mem block: nr_free+=n
根据注释修改代码
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p));//判断是否是保留页,断言手法
p->flags = p->property = 0;//设置标志位
SetPageProperty(p);//设置bit
set_page_ref(p, 0);//清空被引用的次数
list_add(&free_list, &(p->page_link));//将此页插入到空闲页的链表里面
}
base->property = n;//连续内存空闲块的大小为n
//SetPageProperty(base);
nr_free += n;//说明有n个连续空闲块
//list_add(&free_list, &(base->page_link));
}
此函数是用于为进程分配空闲页。其分配的步骤如下:
- 寻找足够大的空闲块,如果找到了,重新设置标志位
- 从空闲链表中删除此页
- 判断空闲块大小是否合适 ,如果不合适,分割页块 ,如果合适则不进行操作
- 计算剩余空闲页个数
- 返回分配的页块地址
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {
return NULL;
}
struct Page *page = NULL;
list_entry_t *le = &free_list;
while ((le = list_next(le)) != &free_list) {
struct Page *p = le2page(le, page_link);
if (p->property >= n) {
page = p;
break;
}
}
if (page != NULL) {
list_del(&(page->page_link));
if (page->property > n) {
struct Page *p = page + n;
p->property = page->property - n;
list_add(&free_list, &(p->page_link));
}
nr_free -= n;
ClearPageProperty(page);
}
return page;
}
查看一下注释
* (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr
* of malloced block.
* (4.1) So you should search freelist like this:
* list_entry_t le = &free_list;
* while((le=list_next(le)) != &free_list) {
* ....
* (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n?
* struct Page *p = le2page(le, page_link);
* if(p->property >= n){ ...
* (4.1.2) If we find this p, then it means we find a free block(block size >=n), and the first n pages can be malloced.
* Some flag bits of this page should be setted: PG_reserved =1, PG_property =0
* unlink the pages from free_list
* (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block,
* (such as: le2page(le,page_link))->property = p->property - n;)
* (4.1.3) re-caluclate nr_free (number of the the rest of all free block)
* (4.1.4) return p
* (4.2) If we can not find a free block (block size >=n), then return NULL
根据注释修改代码
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {//当空闲页不够时,返回NULL
return NULL;
}
list_entry_t *le = &free_list;
while ((le = list_next(le)) != &free_list) {//遍历所有指针
struct Page *p = le2page(le, page_link);//转换为页结构
if (p->property >= n) {//如果找到空闲页大小大于等于n时选中
int i;
list_entry_t *len;
for(i = 0; i < n; i++)//初始化分配内存
{
len = list_next(le);
struct Page *p2 = le2page(temp_le, page_link); //转换页结构
SetPageReserved(p2); //初始化标志位
ClearPageProperty(p2);
list_del(le); //清除双向链表指针
le = len;
}
if(p->property > n)
{
//若大小>n,只取大小为n的块
(le2page(le,page_link))->property = p->property - n;
}
ClearPageProperty(p); //初始化标志位
SetPageReserved(p);
nr_free -= n; //空闲页大小-n
return p;
}
}
return NULL;//分配失败
}
这个函数的作用是释放已经使用完的页,把他们合并到free_list
中。 具体步骤如下:
- 在free_list
中查找合适的位置以供插入
- 改变被释放页的标志位,以及头部的计数器
- 尝试在free_list
中向高地址或低地址合并
static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(!PageReserved(p) && !PageProperty(p));
p->flags = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
list_entry_t *le = list_next(&free_list);
while (le != &free_list) {
p = le2page(le, page_link);
le = list_next(le);
if (base + base->property == p) {
base->property += p->property;
ClearPageProperty(p);
list_del(&(p->page_link));
}
else if (p + p->property == base) {
p->property += base->property;
ClearPageProperty(base);
base = p;
list_del(&(p->page_link));
}
}
nr_free += n;
list_add(&free_list, &(base->page_link));
}
查看注释
* (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks.
* (5.1) according the base addr of withdrawed blocks, search free list, find the correct position
* (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before)
* (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty)
* (5.3) try to merge low addr or high addr blocks. Notice: should change some pages\'s p->property correctly.
根据注释修改代码
static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
assert(PageReserved(base));
list_entry_t *le = &free_list;//找合适的位置
struct Page *p = base;
while((le=list_next(le)) != &free_list)
{
p = le2page(le, page_link);
if(p > base)
{
break;
}
}
for(p = base; p < base + n; p++)//在之前插入n个空闲页
{
list_add_before(le, &(p->page_link));
p->flags = 0;//设置标志
set_page_ref(p, 0);
ClearPageProperty(p);
SetPageProperty(p);
}
base->property = n;//设置连续大小为n
//如果是高位,则向高地址合并
p = le2page(le,page_link);
if(base + base->property == p )
{
base->property += p->property;
p->property = 0;
}
//如果是低位且在范围内,则向低地址合并
le = list_prev(&(base->page_link));
p = le2page(le, page_link);
if(le != &free_list && p == base-1)//满足条件,未分配则合并
{
while(le != &free_list)
{
if(p->property)//当连续时
{
p->property += base->property;
base->property = 0;
break;
}
le = list_prev(le);
p = le2page(le,page_link);
}
}
nr_free += n;
}
实现寻找虚拟地址对应的页表项(需要编程)
通过设置页表和对应的页表项,可建立虚拟内存地址和物理内存地址的对应关系。其中的get_pte函数是设置页表项缓解中的一个重要步骤。此函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。
相关定义
根据注释完成代码
//get_pte - get pte and return the kernel virtual address of this pte for la
// - if the PT contians this pte didn't exist, alloc a page for PT
// parameter:
// pgdir: the kernel virtual base address of PDT
// la: the linear address need to map
// create: a logical value to decide if alloc a page for PT
// return vaule: the kernel virtual address of this pte
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
/* LAB2 EXERCISE 2: YOUR CODE
*
* If you need to visit a physical address, please use KADDR()
* please read pmm.h for useful macros
*
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la.
* KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address.
* set_page_ref(page,1) : means the page be referenced by one time
* page2pa(page): get the physical address of memory which this (struct Page *) page manages
* struct Page * alloc_page() : allocation a page
* memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s
* to the specified value c.
* DEFINEs:
* PTE_P 0x001 // page table/directory entry flags bit : Present
* PTE_W 0x002 // page table/directory entry flags bit : Writeable
* PTE_U 0x004 // page table/directory entry flags bit : User can access
*/
//typedef uintptr_t pde_t;
pde_t *pdep = &pgdir[PDX(la)]; // (1)获取页表
if (!(*pdep & PTE_P)) // (2)假设页目录项不存在
{
struct Page *page;
if (!create || (page = alloc_page()) == NULL) // (3) check if creating is needed, then alloc page for page table
{ //假如不需要分配或是分配失败
return NULL;
}
set_page_ref(page, 1); // (4)设置被引用1次
uintptr_t pa = page2pa(page); // (5)得到该页物理地址
memset(KADDR(pa), 0, PGSIZE); // (6)物理地址转虚拟地址,并初始化
*pdep = pa | PTE_U | PTE_W | PTE_P; // (7)设置可读,可写,存在位
}
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)]; // (8) return page table entry
//KADDR(PDE_ADDR(*pdep)):这部分是由页目录项地址得到关联的页表物理地址,再转成虚拟地址
//PTX(la):返回虚拟地址la的页表项索引
//最后返回的是虚拟地址la对应的页表项入口地址
}
释放某虚拟地址所在的页并取消对应的二级页表项的映射(需要编程)
当释放一个包含某虚地址的物理内存页时,需要让对应此物理内存页的管理数据结构Page做相关的清除处理,使得次物理内存页成为空闲;另外还需把表示虚地址与物理地址对应关系的二级页表项清除
//page_remove_pte - free an Page sturct which is related linear address la
// - and clean(invalidate) pte which is related linear address la
//note: PT is changed, so the TLB need to be invalidate
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
/* LAB2 EXERCISE 3: YOUR CODE
*
* Please check if ptep is valid, and tlb must be manually updated if mapping is updated
*
* Maybe you want help comment, BELOW comments can help you finish the code
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* struct Page *page pte2page(*ptep): get the according page from the value of a ptep
* free_page : free a page
* page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free.
* tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being
* edited are the ones currently in use by the processor.
* DEFINEs:
* PTE_P 0x001 // page table/directory entry flags bit : Present
*/
if (*ptep & PTE_P) //(1) check if this page table entry is present
{ //假如页表项存在
struct Page *page = pte2page(*ptep);//(2)找到页表项的那一页信息
if (page_ref_dec(page) == 0)//(3)如果没有被引用
{
free_page(page);//(4)释放该页
}
*ptep = 0; //(5)该页目录项清零
tlb_invalidate(pgdir, la); //(6) flush tlb当修改的页表是进程正在使用的那些页表,使之无效
}
}
通过本次实验,我了解如何发现系统中的物理内存,了解如何建立对物理内存的初步管理,了解了页表的相关的操作,即如何建立页表来实现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解。基本上在试验中学习,根据注释以及函数定义可以动手完成一个简单的物理内存管理系统。完成后发现运行错误,通过一遍一遍的核对代码,查阅资料,比对正确答案,终于得以修改,成功运行。