练习1:实现first-fit连续物理内存分配算法
1.first-fit算法原理(最先匹配)
从前往后找,找到第一个比我要分配空间大的分区
2.实现过程
实现过程中需要用到的数据结构:
第一个是每一个物理页的属性结构
代码如下(示例):
struct Page {
int ref; // page frame's reference counter
uint32_t flags; // array of flags that describe the status of the page frame
unsigned int property; // the num of free block, used in first fit pm manager
list_entry_t page_link; // free list link
};
该结构四个成员变量意义如下:
1、ref表示该页被页表的引用记数,应该就是映射此物理页的虚拟页个数。一旦某页表中有一个页表项设置了虚拟页到这个Page管理的物理页的映射关系,就会把Page的ref加一。反之,若是解除,那就减一。
2、 flags表示此物理页的状态标记,有两个标志位,第一个表示是否被保留,如果被保留了则设为1(比如内核代码占用的空间)。第二个表示此页是否是free的。如果设置为1,表示这页是free的,可以被分配;如果设置为0,表示这页已经被分配出去了,不能被再二次分配。
3、property用来记录某连续内存空闲块的大小,这里需要注意的是用到此成员变量的这个Page一定是连续内存块的开始地址(第一页的地址)。
4、page_link是便于把多个连续内存空闲块链接在一起的双向链表指针,连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块。这里需要注意的是用到此成员变量的这个Page比较特殊,是这个连续内存空闲块地址最小的一页(即头一页, Head Page)。连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块。
第二个是双向链表:
代码如下(示例):
struct list_entry {
struct list_entry *prev, *next;
};
typedef struct list_entry list_entry_t;
c中定义的双向链表
第三个是负责管理所有的连续内存空闲块的双向链表——记录空闲页
代码如下(示例):
typedef struct {
list_entry_t free_list; // the list header
unsigned int nr_free; // # of free pages in this free list
} free_area_t;
free_list是一个list_entry结构的双向链表指针,用来记录空闲页
nr_free则记录当前空闲页的个数
default_init函数
代码如下(示例):
static void
default_init(void) {
list_init(&free_list);
nr_free = 0;
}
简单的free_area_t初始化
default_init_memmap函数
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 = 0;//初始化
SetPageProperty(p);//使能该页
p->property = 0;//如果此页是空闲页,并且不是自由块的第一页,则p->property应设置为0。
set_page_ref(p, 0);//p->ref应该是0,因为现在p是空闲的,没有引用。
list_add_before(&free_list, &(p->page_link));//使用p->page_link将这个页面链接到free_list
}
nr_free += n;//该空闲块所有页的大小
//first block
base->property = n;//如果此页是空闲页,并且是空闲块的第一页,则p->property应设置为total num of block。
}
用来初始化空闲页链表,初始化每一个空闲页,然后计算空闲页的总数。
函数说明:
函数的两个参数:*base表示该空闲块的开始地址,n表示页数量
如果此页是空闲页,并且不是空闲块的第一页,则p->property应设置为0。
如果此页是空闲页,并且是空闲块的第一页,则p->property应设置为total num of block。
p->ref应该是0,因为现在p是空闲的,没有引用。
default_alloc_pages函数
static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {//如果所有的空闲页的加起来的大小都不够,那直接返回NULL
return NULL;
}
list_entry_t *le, *len;
le = &free_list;//从空闲块链表的头指针开始
while((le=list_next(le)) != &free_list) {//依次往下寻找直到回到头指针处,即已经遍历一次
struct Page *p = le2page(le, page_link);//将地址转换成页的结构
if(p->property >= n){//由于是first-fit,则遇到的第一个大于N的块就选中即可
int i;
for(i=0;i<n;i++){//递归把选中的空闲块链表中的每一个页结构初始化
len = list_next(le);
struct Page *pp = le2page(le, page_link);
SetPageReserved(pp);
ClearPageProperty(pp);
list_del(le);//从空闲页链表中删除这个双向链表指针
le = len;
}
if(p->property>n){
(le2page(le,page_link))->property = p->property - n;//如果选中的第一个连续的块大于n,只取其中的大小为n的块
}
ClearPageProperty(p);
SetPageReserved(p);
nr_free -= n;//当前空闲页的数目减n
return p;
}
}
return NULL;//没有大于等于n的连续空闲页块,返回空
}
详细说明已在代码中注释,该函数主要实现的流程:从空闲页块的链表中去遍历,找到第一块大小大于n的块,然后分配出来,把它从空闲页链表中除去,然后如果有多余的,把分完剩下的部分再次加入会空闲页链表中。
default_free_pages函数
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;
while((le=list_next(le)) != &free_list) {
p = le2page(le, page_link);
if(p>base){
break;
}
}
//list_add_before(le, base->page_link);
for(p=base;p<base+n;p++){
list_add_before(le, &(p->page_link));
}
base->flags = 0;
set_page_ref(base, 0);
ClearPageProperty(base);
SetPageProperty(base);
base->property = n;
p = le2page(le,page_link) ;
if( base+n == 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;
return ;
}
函数功能:将页面重新链接到空闲列表中,或者将小的空闲块合并到大的空闲块中。
实现流程:
(1)根据要归还分区的基址查找空闲链表,找到正确位置,(地址从低到高查),归还页面
(2)重新设置页面属性p->ref, p->flags等
(3)尝试合并相邻的空闲低地址或空闲高地址;
练习2:实现寻找虚拟地址对应的页表项
get_pte函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。
//pde_t为一级页表的表项,pte_t为二级页表的表项
pde_t *pdep = &pgdir[PDX(la)];// (1) find page directory entry查找页目录项
if (!(*pdep & PTE_P)) {// (2) check if entry is not present页目录项是否存在
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) set page reference
uintptr_t pa = page2pa(page);// (5) get linear address of page
memset(KADDR(pa), 0, PGSIZE);// (6) clear page content using memset
*pdep = pa | PTE_U | PTE_W | PTE_P;// (7) set page directory entry's permission
}
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];// (8) return page table entry
+--------10------+-------10-------+---------12----------+
| Page Directory | Page Table | Offset within Page |
| Index | Index | |
+----------------+----------------+---------------------+
|--- PDX(la) ----|---- PTX(la) ---|---- PGOFF(la) ------|
|----------- PPN(la) -------------|
uintptr_t 表示为线性地址,由于段式管理只做直接映射,所以它也是逻辑地址。
PTE_U: 位3,表示用户态的软件可以读取对应地址的物理内存页内容
PTE_W: 位2,表示物理内存页内容可写
PTE_P: 位1,表示物理内存页存在
练习3:释放某虚拟地址所在的页并取消对应的二级页表项的映射
page_remove_pte函数先判断该页被引用的次数,如果只被引用了一次,那么直接释放掉这页, 否则就删掉二级页表的该表项,即该页的入口。
if (*ptep & PTE_P) {//判断页表中该表项是否存在
struct Page *page = pte2page(*ptep);
if (page_ref_dec(page) == 0) {//判断是否只被引用了一次
free_page(page);//如果只被引用了一次,那么可以释放掉此页
}
*ptep = 0;//如果被多次引用,则不能释放此页,只用释放二级页表的表项
tlb_invalidate(pgdir, la);//更新页表
}
lab2运行结果如下所示