Skip to content

内存分配

我们实现的内存分配比较简单,主要是伙伴算法和一个简单(陋)的Slab

1. 伙伴算法

ini
+---------------------------------------------------------------------+
|                          Buddy System (Free Lists)                  |
+---------------------------------------------------------------------+
|                                                                     |
| Level 0: [2^0]  → □ ↔ □ ↔ □ ↔ ... (双向循环链表,块大小= PGSIZE * 1)   |
|                                                                     |
| Level 1: [2^1]  → ■ ↔ ■ ↔ ...     (块大小= PGSIZE * 2)               |
|                                                                     |
| Level 2: [2^2]  → ▣ ↔ ▣ ↔ ...     (块大小= PGSIZE * 4)              |
|                                                                     |
| Level 3: [2^3]  → ▩ ↔ ▩ ↔ ...     (块大小= PGSIZE * 8)              |
|                                                                     |
| Level 4: [2^4]  → ▢ ↔ ▢ ↔ ...     (块大小= PGSIZE * 16)             |
|                                                                     |
| Level 5: [2^5]  → □□ ↔ □□ ↔ ...  (块大小= PGSIZE * 32)               |
|                                                                     |
| Level 6: [2^6]  → ■■ ↔ ■■ ↔ ...  (块大小= PGSIZE * 64)               |
|                                                                     |
| Level 7: [2^7]  → ▣▣ ↔ ▣▣ ↔ ...  (块大小= PGSIZE * 128)            |
|                                                                     |
| Level 8: [2^8]  → ▩▩ ↔ ▩▩ ↔ ...  (块大小= PGSIZE * 256)            |
|                                                                     |
| Level 9: [2^9]  → ▢▢ ↔ ▢▢ ↔ ...  (块大小= PGSIZE * 512)            |
|                                                                     |
| Level 10: [2^10]  → □□□□ ↔ □□□□ ↔ ...  (块大小= PGSIZE * 1024)       |
|                                                                     |
+---------------------------------------------------------------------+

1.1 核心算法

c
// 寻找当前 page 的伙伴
static struct page *find_buddy(const struct page *page, const int order)
{
    uint64_t index = get_pg_index(page);
    uint64_t buddy_index = index ^ (1UL << order);
  	...
}

2. Slab

我们实现了一个简单的分配对象的方法。该方法的架构如下图:

image-20250620161635285

我们仅仅实现了用于分配小内存对象,而没有其他任何的缓存对齐等一堆其他的复杂机制。

每一个热门的对象都可以为其建立专用的 kmem_cache缓存,该缓存管理了该专门对象的分配与回收。

  • kmem_cache->full_slabs:连接分配完的 slab;

  • kmem_cache->part_slabs:连接还有空的 slab;

  • kmem_cache->cpu_slabs:每个CPU的专用 slab;

  • kmem_cache->listkmem_cache自身全局的链条;

在我们的实现中,严格来说,Slab只是那个小的结构体,但是我们实际使用的时候,是直接使用了一个完整的物理页面(图中的mem_page),页面的底部放置这个结构体,该结构体上方放置空闲对象的地址链表free_list(使用堆栈实现),因为总共一个页面,也就是说,这个free_list其实是有数量限制的,我们允许的最小对象大小为 16B,slab结构体大小只有一点点。我们姑且就算剩余4000B(此处PGSIZE为4096,64位上指针为8B),那么4000/8 = 500,也就是说可以放置500个指针,管理500个对象,这数量对于我们来说已经完全够了,若不够,可以另外创建slab挂到part_list,而不是大家都挤在一起。

2.1 初始化

每个slab,都会指向一个**连续分配(我们上文基于伙伴算法实现)**的区域,该区域由1到若干个页面组成,每个页面(page)被标记的时候会有指针指向该 slab。初始化的时候,slab会把该空间根据所需对象的大小进行连续等分,然后把每一个的地址都压入free_list中,备好待用。

2.2 申请

申请的时候,由于我们只看part_slabs中的slab们,这里的都是表示有空的 slab,直接从第一个slab的空闲列表弹出一个即可。

如果part_slabs为空,则会新申请一个 slab 挂到part_slabs上,再分配。

2.3 回收

还记得初始化的时候每个页面(page)都有slab指针标记吗,那么根据当前地址,可以顺藤摸瓜找到page,然后找到 slab,然后压回free_list就好。

2.4销毁

这个就释放对应的资源就好。

kmallockfree基于 Slab 实现,本质上就是分配一堆 size = 16,32,64,128......的对象然后进行申请。申请某个大小S,就让S向上对齐2的幂。