关于ucore究竟是什么的问题,在ucore1 当中已经介绍过了.这次就直接介入正题吧.
想了想还是想说一点题外话,写完ucore1之后,我把编辑器从sublime3换成了atom.只能说github新出的这个编辑器实在是卡爆了,而且在linux下各种奇怪的情况下报错不断.不过一它是免费的,而且还在测试阶段可以想象会越来越好,二是它安装插件的方式比sublime不知道友好了多少.而且该有的插件都有,咬咬牙还是继续用了.
前段时间室友给我声情并茂念了一句"高端程序员展示自己的代码,低端程序员展示自己的工具".作为天天在各种工具上纠缠不休的我只好默默蹲墙角了.
好吧.现在真的切入正题.
lab2开始前需要的一点准备工作是,把lab1的代码给merge进来.我倒是知道git不同的分支该怎么merge,但怎么从一个文件夹merge到另外一个文件夹,那是毫无概念.搜索一番,找到一个linux叫做meld的工具.gui的,相当好用,点点点就把代码挪过来了.(当然,总共也没多少要改的,不过总归比复制粘贴要舒服,也不容易弄错)
1.first-fit连续内存分配
要做的事情就是将链表当中的page分配出去.说简单也简单.一开始看着代码茫然无措,因为照着注释的要求,它似乎把什么都写好了,提示可能修改的那几个函数已经有模有样放在那里了,完全看不出修改的必要.
make qemu发现,在某个check函数当中发生了assertion failed.查看之,发现它是按照一定规则来检查分配的内存页是否合法合理.然后一开始投机取巧比较严重,我发现在default_free_pages函数中,把list_add改成list_add_before就能完美通过check.
一直到快到ddl了我都以为这样写是没什么问题的,直到有人提醒我,题目的要求是把页排序放入链表.我说它check的内容怎么这么奇怪.只好对整个程序都进行大改.
init_memmap和alloc_pages这两个函数还好,在其基础上一点小改就可以.free_pages这个函数,如果在它的基础上改,简直就是给自己制造麻烦加大工作量.由于一开始的一些失误,加上参考了那个极没有参考价值的答案,我几乎把原来的代码都删光了,后来全都是手动补上的.里面可能有一些问题,然而我暂时也不想去管它们了.
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;
p->property = 0;
ClearPageProperty(p);
set_page_ref(p, 0);
}
SetPageProperty(base);
base->property = n;
list_add_before(&free_list,&(base->page_link));
nr_free += n;
}
<span style="font-size:18px;">static struct Page *
default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {
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){
if(p->property>n){
struct Page *np = p+n;
np->property = p->property - n;
SetPageProperty(np);
list_add(&(p->page_link),&(np->page_link));
}
SetPageReserved(p);
ClearPageProperty(p);
set_page_ref(p,0);
list_del(&(p->page_link));
nr_free -= n;
return p;
}
}
return NULL;
}</span>
<span style="font-size:18px;">static void
default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
base->flags = 0;
SetPageProperty(base);
set_page_ref(base,0);
base->property = n;
list_entry_t *le = &free_list;
struct Page * p = NULL;
while((le=list_next(le)) != &free_list) {p = le2page(le, page_link);
if(p>base){
break;
}
}
le = (p>base)? (&(p->page_link)) : (&free_list);
list_add_before(le,&(base->page_link));
if (le != &free_list){
if( base+n == p ){
base->property += p->property;
p->property = 0;
list_del(&(p->page_link));
}
}
le = list_prev(&(base->page_link));
if (le!= &free_list){
p = le2page(le,page_link);
if (p+p->property == base){
p->property += base->property;
base->property = 0;
list_del(&(base->page_link));
}
}
nr_free += n;
return ;
}</span>
2.查找虚拟地址对应页表项
私以为,比起上一题,这题就简单多了,只要跟着注释走就可以了.前提是你要搞清楚如下几个概念:
虚拟地址,物理地址,线性地址,指向struct Page的指针,Page的物理地址和虚拟地址,PDE,PTE...好吧我觉得可能不止这么些,希望上面这些没有把你看乱,只要能在它们之间灵活转换来转换去,那这道题是毫无压力的.
你不需要知道手动怎么解决,你只要找到那几个定义好的宏就可以了.
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
pde_t *pdep = &pgdir[PDX(la)];
if (!( (*pdep) & PTE_P )){
if (!create) return NULL;
struct Page * page = alloc_page();
if (!page) return NULL;
set_page_ref(page, 1);
uintptr_t pa = page2pa(page);
memset(KADDR(pa),0,PGSIZE);
*pdep = pa | PTE_U | PTE_W | PTE_P;
}
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];
}
4.释放虚拟地址所在页,并取消对应二级页表映射
这道题比上一道还要简单,关键还是搞清楚上面几个概念的区别,完全是手到擒来.
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
if (*ptep & PTE_P){
struct Page* page = pte2page(*ptep);
if (page_ref_dec(page)==0){
free_page(page);
}
*ptep = 0;
tlb_invalidate(pgdir, la);
}
}
5.challenge Slub算法实现
由于并非要实现一个完整版的slub,因此我的实现只完成了低限度的任意内存分配功能.以下描述其结构:
slub部分:
包含kern/mm/slub.*头文件和实现文件
主要函数为三个:
slub_init负责整个slub系统的初始化
slub_alloc负责分配指定大小的小块内存
slub_free负责内存的回收
主要数据结构为:
struct kmem_cache
每一个对象管理一种固定大小的object的内存分配
init中初始化了从8byte到2000byte不等的11种不同大小的kmem_cache管理器.
每一个对象都包含下两个数据结构,用于辅助管理.
struct kmem_cache_cpu
管理当前正在使用的分配页,如果当前分配页还有空间,则从直接从中分配,否则将当前页与kmem_cache_node管理的
未填满的页交换.
struct kmem_cache_node
管理已经填满的页和未填满的页.
通过指定kmem_cache分配和释放内存的流程大致如下:
分配内存:
1)刚刚初始化,kmem_cache_cpu和kmem_cache_node当中都是空的,则初始化一个新page,作为kmem_cache_cpu当
前的分配页.
2)kmem_cache_cpu有分配页且未满,则直接从里面分配空间
3)kmem_cache_cpu的分配页已满,则从kmem_cache_node中调用一个未满的分配页到kmem_cache_cpu当中,将原
分配页加入到kmem_cache_node的已满分配页链表当中.如果kmem_cache_node当中没有未满的分配页,则申请并初
始化一个新页.
释放内存:
1)如果被释放的内存在kmem_cache_cpu的当前页中,则直接释放并加入空闲链表
2)如果在kmem_cache_node管理的未满页中,则同上处理,并且如果该页全空,就释放该页
3)如果在一张已满页中,则释放并加入链表,再将该页从已满页链表中删除,加入未满页链表.
上述过程中,页内部的空闲空间由单向链表维护,未满页和已满页的管理由两个双向链表维护
具体细节详见代码.
kmalloc部分
包含kern/mm/kmalloc.*头文件和实现文件
主要函数有:
kmalloc 根据传入的空间大小参数,如果较大则按页为单位分配,如果较小则在slub.c中初始化的11个不同大小的管理器
中选择足够大的最小的管理器,调用slab_alloc分配内存.
Kfree 同上,根据大小来采取不同方案释放内存.
kmalloc_init 调用slub_init初始化slub系统,并对是否使用slub分配内存的不同情况进行测试.
实现感想:整个过程中其实一直觉得很蛋疼,因为有些需要用的空间和结构,按照平时我会直接malloc一个(或是new一
个),然而放在这里就得绞尽脑汁去想把它们塞在哪里,好在最后还是搞定了.毕竟就是让我们自己实现一个malloc.非常
好的体验.
具体代码比较长(其实也就两百多行),就不贴上来了.