Linux伙伴算法(Buddy Allocator)

来源:互联网 发布:软件转omtp ctia 编辑:程序博客网 时间:2024/06/08 19:39

该算法用于管理Linux物理内存,该算法的应用决定了内存分配的单位每次必为2的N次幂个页,如1、2、4、8、32页等。内存管理器初始化好之后,内存分配很简单,我的问题是,释放内存的时候如何做到高效呢?我很sb地想:

释放一块幂(order)为k内存,首先在k对应的链表中查找是否有相邻的块,如果有合并之。合并之后,再在order为k+1的链表中继续查找是否有相邻的块(此时为新的大块了),如此往复。

如果这是一个面试题目,我这么跟面试官说,他还可能以为我真的懂了呢!其实,我根本不懂。为什么这么说呢?问题就出在上面加粗的几个字上:查找。在我脑海中,这里的查找意味着遍历链表,通过对地址的判断找相邻项,直到找到为止。在内核中,如果不得不用这么sb的行为,早就有人将它用二叉树或hash里面的算法改写了!

于是读linux源码mm/page_alloc.c中的__free_pages_ok()函数,发现它怎么跑一遍就搞定了,list_add宏只调用过一次。按照我上面的想法,list_add必须频繁调用。有点神奇!于是进一步仔细阅读,它在while中反复调用了list_del宏,其余代码没仔细看,突然就明白了,哦,我傻!我的思维局限到free_list链表的操作中了,而我忘了,页面的搜索还可以根据mem_map来呀!当我把思维放到mem_map上来的时候发现一切都很很简单了:不就是线性地搜索相邻区域么?给定一个区域,并且知道它的长度,让你在其前后找出长度一样的相邻区域来,简单吧?

之所以能如此飘逸地完成此任务,与list_head结构的精巧设计是分不开的,最让我惊叹的是INIT_LIST_HEAD,list_empty两个简单的宏:

QUOTE:struct list_head {
struct list_head *next, *prev;
};

#define INIT_LIST_HEAD(ptr) do { /
(ptr)->next = (ptr); (ptr)->prev = (ptr); /
} while (0)

static inline int list_empty(struct list_head *head)
{
return head->next == head;
}


简单吧?可别小看,简单中蕴藏着无限的飘逸啊。如果没有它们,上面的算法完全没有可行性。双向链表,好东西!

Linux还有个很值得注意的特征:同一组数据可能有多种数据结构来管理。有的管理器采用数组型的数据结构,有的管理器采用链表型数据结构,有的采用hash表的结构,有的还采用树状结构。通过综合不同管理器的特征,根据不同的应用场景,灵活选取不同的管理器,可以达到效率最优,是值得学习的好方法!

原创粉丝点击