PMM部分

总体思路

PMM部分我选择的是一个 allocator 和一个类 slab 组成的。在分配一个大小为 的内存的时候,首先去查找管理大小 slab 中有无空闲的对象,如果有,则直接利用这个对象进行分配,如果没有,这个 slab 就会向 allocator 申请一个或多个页,然后利用这些页来生成对应的对象,分配给 kalloc 接口。

在归还的时候,同样是首先查询整个分配表,查询到分配记录,然后根据分配记录,把这个指针交给对应大小的 slabslab 会把这个指针还给分配它的页。如果这一页的所有分配都被归还了,那么就把这一整页还给 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef OS_WORKBENCH_DEBUG_H
#define OS_WORKBENCH_DEBUG_H

#ifdef TRACE_F
#define TRACE_ENTRY printf("[trace][cpu:#%d] %s:entry\n", cpu_current(), __func__)
#define TRACE_EXIT printf("[trace][cpu:#%d] %s:exit\n", cpu_current(), __func__)
#define TRACE_TRAP printf("[trace][cpu:#%d] %s:hit line:%d\n", cpu_current(), __func__, __LINE__)
#else
#define TRACE_ENTRY ((void)0)
#define TRACE_EXIT ((void)0)
#define TRACE_TRAP ((void)0)
#endif

#ifdef DEBUG_LOCAL
#define debug printf
#else
#define debug fake_printf
#endif
void fake_printf(char *str, ...);
#endif // OS_WORKBENCH_DEBUG_H

在平常使用 CFLAGS += -DTRACE_F -DDEBUG_LOCAL 就可以把TRACE 系列和 DEBUG 系列的调试指令换成真正的输出,否则换成一个直接返回的空函数。而且我还使用了不同的宏来控制不同调试轮次的输出,让整个log更加有序。