练习1:实现 first-fit 连续物理内存分配算法
修改default_pmm.c
中的default_init
,default_init_memmap
,
default_alloc_pages
, default_free_pages
等相关函数。
default_pmm.c
有一段很长的注释,首先应该要仔细阅读注释。
First, you should get familiar with the struct
list
in list.h. Structlist
is a simple doubly linked list implementation. You should know how to use
list_init
,list_add
(list_add_after
),list_add_before
,list_del
,
list_next
,list_prev
.
list.h
中
struct list_entry {
struct list_entry *prev, *next;//两个指针,父子节点
};
typedef struct list_entry list_entry_t;//重命名为list_entry_t
list_init
初始化一个新的list_entry
list_add
,list_add_after
和list_add_after
都是添加一个新的条目
list_del
是从列表中删除一个条目
list_del_init
是从列表中删除一个条目并重定义它
list_empty
是判断列表是否为空
list_next
和list_prev
分别是获取列表的前一项和后一项
There’s a tricky method that is to transform a general
list
struct to a special struct (such as structpage
), using the following MACROs:le2page
(in memlayout.h), (and in future labs:le2vma
(in vmm.h),le2proc
(in proc.h), etc).
在memlayout.h
中找到Page
的定义:
struct Page {
int ref; // 映射此物理页的虚拟页个数(该页被引用的次数)
uint32_t flags; // 物理页的标志
unsigned int property; // 用于记录first fit pm manager的空闲块数
//(只在地址最低页有值)
list_entry_t page_link; // 双向链接各个page的双向链表
};
le2page
的定义:
// 将列表转换成页
#define le2page(le, member) \
to_struct((le), struct Page, member)
First-Fit(首次适应算法)
首次适应算法从空闲分区表的第一个表目起查找该表,把最先能够满足要求的空闲区分配给作业,这种方法目的在于减少查找时间。
该算法倾向于优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区,这为以后到达的大作业分配大的内存空间创造了条件。
缺点是会造成外部碎片
1. default_init()
static void
default_init(void) {
list_init(&free_list);
nr_free = 0;
}
nr_free
在free_area_t
的结构体中被定义,用于表示空闲页的数量
/* free_area_t - 维护一个双向链表来记录空闲(未使用的)页*/
typedef struct {
list_entry_t free_list; // 链表头部
unsigned int nr_free; // 表示空闲页的数量
} free_area_t;
这个函数可以直接使用。
2. default_init_memmap()
注释中有这一部分提示:
CALL GRAPH:
kern_init
-->pmm_init
-->page_init
-->init_memmap
-->pmm_manager
-->init_memmap
.This function is used to initialize a free block (with parameter
addr_base
,page_number
). In order to initialize a free block, firstly, you should initialize each page (defined in memlayout.h) in this free block. This procedure includes:
Setting the bit
PG_property
ofp->flags
, which means this page is valid.P.S: In function
pmm_init
(in pmm.c), the bitPG_reserved
ofp->flags
is already set.If this page is free and is not the first page of a free block,
p->property
should be set to 0.If this page is free and is the first page of a free block,
p->property
should be set to be the total number of pages in the block.
p->ref
should be 0, because nowp
is free and has no reference.After that, We can use
p->page_link
to link this page intofree_list
.(e.g. :
list_add_before(&free_list, &(p->page_link));
)Finally, we should update the sum of the free memory blocks:
nr_free += n
.
找到相关的一些函数和定义:
/* Flags describing the status of a page frame */
#define PG_reserved 0
// if this bit=1: the Page is reserved for kernel, cannot be used in alloc/free_pages; otherwise, this bit=0
//如果为1,这一页为内核保留,否则为0
#define PG_property 1
// if this bit=1: the Page is the head page of a free memory block(contains some continuous_addrress pages), and can be used in alloc_pages; if this bit=0: if the Page is the the head page of a free memory block, then this Page and the memory block is alloced. Or this Page isn't the head page.
#define PageReserved(page) test_bit(PG_reserved, &((page)->flags))
//检查是否为保留页
#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)))
//设置为保留页
static inline void
list_add_before(list_entry_t *listelm, list_entry_t *elm) {
__list_add(elm, listelm->prev, listelm);
}
static inline void
__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) {
prev->next = next->prev = elm;
elm->next = next;
elm->prev = prev;
}
static inline void
set_page_ref(struct Page *page, int val) {
page->ref = val;
}//在pmm.h中
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 = p->property = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
nr_free += n;
list_add(&free_list, &(base->page_link));
}
由pmm.c
中的注释:
set_page_ref(page,1) : means the page be referenced by one time
可以知道set_page_ref(p,0)的作用是清除引用此页的虚拟页的个数。
assert()
函数的作用:
assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
改写为:
static void
default_init_memmap(struct Page *base, size_t n) {
assert(n > 0); //使用assert宏,当为假时中止程序
struct Page *p = base;//声明一个base的Page,随后生成起始地址为base的n个连续页
for (; p != base + n; p ++) {
//初始化n块物理页
assert(PageReserved(p)); //确保此页不是保留页,如果是,中止程序
p->flags = p->property= 0; //标志位置为0
SetPageProperty(p); //设置标志位为1
set_page_ref(p, 0);
list_add_before(&free_list, &(p->page_link)); //加入空闲链表
}
nr_free += n; //空闲页总数置为n
base->property = n; //修改base的连续空页值为n
}
相对于改写前的函数,SetPageProperty()
和list_add_before()
这两个函数被移动到循环里,并对每个页都进行修改,因为生成以base为起始地址的连续空页时,后续的连续空页要被设为保留页然后链接成一个双向链表。
这一个函数是传入base页的地址和生成物理页的个数n,然后把物理页初始化后设为保留页与base页连接。并将修改base页的property为n,修改nr_free为n,记录空闲页的个数。
3. default_alloc_pages()
注释:
default_alloc_pages
:
Search for the first free block (block size >= n) in the free list and reszie the block found, returning the address of this block as the address required bymalloc
.
- So you should search the free list like this:
list_entry_t le = &free_list;
while((le=list_next(le)) != &free_list) {
…
(4.1.1)
In the while loop, get the structpage
and check ifp->property
(recording the num of free pages in this block) >= n.
struct Page p = le2page(le, page_link);
if(p->property >= n){ …
(4.1.2)
If we find thisp
, it means we’ve found a free block with its size
>= n, whose firstn
pages can be malloced. Some flag bits of this page
should be set as the following:PG_reserved = 1
,PG_property = 0
.
Then, unlink the pages fromfree_list
.
(4.1.2.1)
Ifp->property > n
, we should re-calculate number of the rest
pages of this free block. (e.g.:le2page(le,page_link))->property = p->property - n;
)
(4.1.3)
Re-caluclatenr_free
(number of the the rest of all free block).
(4.1.4)
returnp
- If we can not find a free block with its size >=n, then return NULL.
原default_alloc_pages()
函数:
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;
}
注释和原函数中使用的定义:
// convert list entry to page
#define le2page(le, member) \
to_struct((le), struct Page, member)
#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags))
to_struct()
函数:
/* *
* to_struct - get the struct from a ptr
* @ptr: a struct pointer of member
* @type: the type of the struct this is embedded in
* @member: the name of the member within the struct
* */
#define to_struct(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
offsetof()
函数:
/* Return the offset of 'member' relative to the beginning of a struct type */
#define offsetof(type, member) \
((size_t)(&((type *)0)->member))
改写后的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){
//找到页(whose first `n` pages can be malloced)
int i;
for(i=0;i<n;i++){
//对前n页进行操作
len = list_next(le);
struct Page *pp = le2page(le, page_link); //转换为页
SetPageReserved(pp); //PG_reserved = '1'
ClearPageProperty(pp);//PG_property = '0'
list_del(le); //将此页从free_list中清除
le = len;
}
if(p->property>n){
//如果页块大小大于所需大小,分割页块
(le2page(le,page_link))->property = p->property-n;
}
ClearPageProperty(p);
SetPageReserved(p);
nr_free -= n; //减去已经分配的页块大小
return p;
}
}
return NULL;
}
这个函数用于分配空闲页,根据注释的说明,先判断p->property的大小,如果是>=n,则对标志位进行一些修改,如果是>n,则要分割页块后进行操作,如果都不符合则返回NULL退出。(根据注释一步一步写即可,注释讲的很详细。)
4. default_free_pages()
default_free_pages
: re-link the pages into the free list, and may merge small free blocks into the big ones.
(5.1)
According to the base address of the withdrawed blocks, search the free list for its correct position (with address from low to high), and insert the pages. (May uselist_next
,le2page
,list_add_before
)
(5.2)
Reset the fields of the pages, such asp->ref
andp->flags
(PageProperty)
(5.3)
Try to merge blocks at lower or higher addresses. Notice: This should
change some pages’p->property
correctly.
原default_free_pages()
函数:
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));
}
修改后:
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;
}
}
for(p=base;p<base+n;p++){
list_add_before(le, &(p->page_link)); //在这个位置开始,插入释放数量的空页
}
base->flags = 0; //修改标志位
set_page_ref(base, 0); //修改引用次数为0
ClearPageProperty(base);
SetPageProperty(base);
base->property = n;