Linux物理内存分配有三种方式:
a. per-CPU机制,分配单个页面,per-CPU的页缓存提供了更快的分配和释放机制;
b. 伙伴算法,适用于分配大块连续页面(至少一个页面),解决了外碎片问题;
c. slab机制,分配小内存,访问频率较高的内存,解决了内碎片问题。
1.伙伴算法原理及伙伴关系
为了便于页面的维护,将多个页面组成内存块,每个内存块都有 2 的方幂个页,方幂的指数被称为阶 order。order相同的内存块被组织到一个空闲链表中。伙伴系统基于2的方幂来申请释放内存页。
当申请内存页时,伙伴系统首先检查与申请大小相同的内存块链表中,检看是否有空闲页,如果有就将其分配出去,并将其从链表中删除,否则就检查上一级,即大小为申请大小的2倍的内存块空闲链表,如果该链表有空闲内存,就将其分配出去,同时将剩余的一部分(即未分配出去的一半)加入到下一级空闲链表中;如果这一级仍没有空闲内存;就检查它的上一级,依次类推,直到分配成功或者彻底失败,在成功时还要按照伙伴系统的要求,将未分配的内存块进行划分并加入到相应的空闲内存块链表.
在释放内存页时,会检查其伙伴是否也是空闲的,如果是就将它和它的伙伴合并为更大的空闲内存块,该检查会递归进行,直到发现伙伴正在被使用或者已经合并成了最大的内存块。
满足以下条件的两个块成为伙伴:
a. 两个块大小相同;
b. 两个块的物理地址连续。
伙伴算法把满足以上条件的两个块合并为一个块,为迭代算法,如果合并后的块还可以跟相邻的块进行合并,那么该算法就继续合并。
2.相关结构体
#define MAX_ORDER 11 //空闲页面分为11个块链表,每个链表中都是固定大小的2^order页块
数据结构:
struct free_area {
struct list_head free_list[MIGRATE_TYPES]; /*双向循环链表的头,集中了大小为2^order页的空闲块对应的页描述符*/
unsigned long nr_free; /*表示大小为2^order页的空闲块的个数*/
};
struct zone{
...
struct free_area free_area[MAX_ORDER];
...
};
伙伴算法的关系图如下:
3.分配页框
a.伙伴算法的入口函数是buffer_rmqueue:
首先判断要分配的页面数是否为 1,如果为1 的情况下,那么并不需要从buddy系统获取,因为per-CPU的页缓存提供了更快的分配和释放机制。per-CPU cache提供了两个链表,一个是cold page链表,另外一个是hot page链表。从hot-cold 链表获取page时要考虑迁移类型。
如果per-CPU页缓存无法满足分配,那么调用rmqueue_bulk从buddy进行分配。
static inline struct page *buffered_rmqueue(struct zone *preferred_zone,
struct zone *zone, int order, gfp_t gfp_flags,
int migratetype)
{ ...
if (likely(order ==