os_kernel_lab/related_info/lab2/buddy_system.md

42 lines
4.1 KiB
Markdown
Raw Permalink Normal View History

# buddy system example code
From
- https://github.com/wuwenbin/buddy2
- [伙伴分配器的一个极简实现 by 我的上铺叫路遥 酷 壳 CoolShell.cn](http://coolshell.cn/articles/10427.html)
## run & debug
try to
```
gcc -g -O0 -o buddy_system buddy_system.c
gdb buddy_system
```
## background
> 下述内容直接来源于 [伙伴分配器的一个极简实现 by 我的上铺叫路遥 酷 壳 CoolShell.cn](http://coolshell.cn/articles/10427.html)
### 数据结构总体思路
通过一个数组形式的完全二叉树来监控管理内存二叉树的节点用于标记相应内存块的使用状态高层节点对应大的块低层节点对应小的块在分配和释放中我们就通过这些节点的标记属性来进行块的分离合并。如图所示假设总大小为16单位的内存我们就建立一个深度为5的满二叉树根节点从数组下标[0]开始监控大小16的块它的左右孩子节点下标[1~2]监控大小8的块第三层节点下标[3~6]监控大小4的块……依此类推。
```
struct buddy2 {
unsigned size;
unsigned longest[1];
};
```
这里的成员size表明管理内存的总单元数目测试用例中是32成员longest就是二叉树的节点标记表明所对应的内存块的空闲单位。
### 初始化
整个分配器的大小就是满二叉树节点数目即所需管理内存单元数目的2倍。一个节点对应4个字节longest记录了节点所对应的的内存块大小。
### 分配阶段
首先要搜索大小适配的块假设第一次分配3转换成2的幂是4我们先要对整个内存进行对半切割从16切割到4需要两步那么从下标[0]节点开始深度搜索到下标[3]的节点并将其标记为已分配。第二次再分配3那么就标记下标[4]的节点。第三次分配6即大小为8那么搜索下标[2]的节点,因为下标[1]所对应的块被下标[3~4]占用了。
具体而言内存分配的alloc中入参是分配器指针和需要分配的大小返回值是内存块索引。alloc函数首先将size调整到2的幂大小并检查是否超过最大限度。然后进行适配搜索深度优先遍历当找到对应节点后将其longest标记为0即分离适配的块出来并转换为内存块索引offset返回依据二叉树排列序号比如内存总体大小32我们找到节点下标[8]内存块对应大小是4则offset = (8+1)*4-32 = 4那么分配内存块就从索引4开始往后4个单位。在函数返回之前需要回溯因为小块内存被占用大块就不能分配了比如下标[8]标记为0分离出来那么其父节点下标[0]、[1]、[3]也需要相应大小的分离。将它们的longest进行折扣计算取左右子树较大值下标[3]取4下标[1]取8下标[0]取16表明其对应的最大空闲值。
### 释放阶段
我们依次释放上述第一次和第二次分配的块,即先释放[3]再释放[4],当释放下标[4]节点后,我们发现之前释放的[3]是相邻的于是我们立马将这两个节点进行合并这样一来下次分配大小8的时候我们就可以搜索到下标[1]适配了。若进一步释放下标[2],同[1]合并后整个内存就回归到初始状态。
具体而言在内存释放的free接口我们只要传入之前分配的内存地址索引并确保它是有效值。之后就跟alloc做反向回溯从最后的节点开始一直往上找到longest为0的节点即当初分配块所适配的大小和位置。我们将longest恢复到原来满状态的值。继续向上回溯检查是否存在合并的块依据就是左右子树longest的值相加是否等于原空闲块满状态的大小如果能够合并就将父节点longest标记为相加的和。
### 小结
上面两个成对alloc/free接口的时间复杂度都是O(logN)保证了程序运行性能。然而这段程序设计的独特之处就在于使用加权来标记内存空闲状态而不是一般的有限状态机实际上longest既可以表示权重又可以表示状态状态机就毫无必要了