PMM实验报告
PMM部分
总体思路
PMM部分我选择的是一个 allocator
和一个类 slab
组成的。在分配一个大小为 slab
中有无空闲的对象,如果有,则直接利用这个对象进行分配,如果没有,这个 slab
就会向 allocator
申请一个或多个页,然后利用这些页来生成对应的对象,分配给 kalloc
接口。
在归还的时候,同样是首先查询整个分配表,查询到分配记录,然后根据分配记录,把这个指针交给对应大小的 slab
, slab
会把这个指针还给分配它的页。如果这一页的所有分配都被归还了,那么就把这一整页还给 allocator
,并交由 allocator
归还到堆区。
分配逻辑
小内存分配的逻辑
小内存分配的时候,首先会向对应的 slab
申请分配,下面以 2B
大小的空间申请为例进行介绍。首先,2B-slab
会检查自己名下有没有空闲的页,如果所有的页都被分配出去了,那么就会向 allocator
申请一个新的页,并且把第一个 Byte
分配给这个申请,并且在自己的bitmap
里面进行标记。如果当前页仍有空闲,那么就会查找bitmap,找到第一个有空的地方,进行分配和标记。如果当前的 2B-slab
仍有空闲页,那么就查找bitmap找出一个合适的位置,分配并标记。
这里把小于一个
Byte
的申请都当做一个Byte
来进行
大内存分配的逻辑
遇到大于一个页的内存分配的时候,首先同样会查找对应的slab,看看有没有这个大小的对象空闲,如果有,直接从slab分配一个出来,如果没有,向 allocator
申请一个这么多页的对象,并且直接分配出来。
回收逻辑
在进行回收的时候,所有的指针都会先进行一次查表工作,查询这个分配记录对应的大小是多少,如果这个大小是大于 4k
的,即这个分配的对象是大于一个页的大内存,那么就把这个指针交给slab
,在slab
中缓存。如果是小内存,即小于一个页的大小,那么就交给小内存的slab
,小内存的slab
会查看到底是属于哪个页的,把这个页对应的bitmap
的地方改掉。如果bitmap
显示,这个页已经完全空闲了,那么slab就会把这个页还给allocator
。
遇到的问题
并发bug
在第一版的时候,我的链表操作上锁不正确,出现了循环拿锁的死锁现象。并且死锁的产生严重依赖调度顺序,所以我调试了半天都没有成功。
链表的大小问题
第一次做的时候,我的分配记录是给每一个bit建立bitmap,导致能用的空间只有几k
KMT部分
总体思路
总体思路和老师给的思路是一样的,就是用一个 task_t
的结构体来记录这个线程的所有信息,然后注册一个 kmt_context_save
来保存上下文,注册一个 kmt_schedule
来进行上下文调度。需要注意的是,这里只有在 kmt_schedule
中才会进行上下文调度,而其他地方,例如 sem_wait
系列的函数只会标记当前的task为不可调度状态。
遇到的问题
首先是,对于这个 os_trap
的返回值我没有理解清楚,最开始我以为同一个线程的上下文的地址是不会发生变化的,所以我在context_save
里面直接assert前后是一样的,然后调了半天
然后最重要的bug是,我忘了给全局的空闲任务列表上锁,调了一周,因为我的调度是随机调度,而且我设的种子正好前面调度都是对的,在将近50次的调度都没有出现一个任务调个两个cpu的情况,所以我也没考虑过这里有数据竞争的问题。
精巧的设计
这里面我认为我最精巧的设计是,抽象出了一套用于调试的宏。我在老师提供的思路上面进行了改进,具体如下:
1 |
|
在平常使用 CFLAGS += -DTRACE_F -DDEBUG_LOCAL
就可以把TRACE
系列和 DEBUG
系列的调试指令换成真正的输出,否则换成一个直接返回的空函数。而且我还使用了不同的宏来控制不同调试轮次的输出,让整个log更加有序。