Linux内核笔记——内存管理之块内存分配
内核版本:linux-2.6.11
-
伙伴系统
伙伴系统是linux用于满足对不同大小块物理内存分配和释放请求的解决方案。
-
内存管理区
linux将物理内存分成三个内存管理区,分别为ZONE_DMA
ZONE_NORMAL
ZONE_HIGHMEM
,并使用三个管理区描述符管理这三个ZONE。
管理区描述符里,有一个元素数为11的free_area
数组,分别对应1、2、4、8、16.....不同块的大小,其中的每个元素的类型都是一个名为free_area的结构体,代码位置mm/mmzone.h
struct free_area {
struct list_head free_list;
unsigned long nr_free;
};
例如,该数组第三个元素的free_list
保存了2的4次方即16个页框大小的空闲块链表,nr_free
保存了空闲块的数量。
-
块内存分配函数
代码位置 mm/page_alloc.c
__rmqueue,传入参数为order和管理区zone的描述符指针。
static struct page *__rmqueue(struct zone *zone, usigned int order);
该函数会遍历2的order次方
个页框大小的块空闲链表,若该链表空,则将order+1继续遍历,找到非空的空闲块链表后,取得它的第一个页框描述符。
page = list_entry(area->free_list.next, struct page, lru);
list_entry是一个宏,功能是取得一个成员变量的父结构体的指针,这里的意思是获得第一个参数area->free_list.next
所在的page结构体的指针。
接着从链表中删除它的这一个页框描述符,并减少管理区描述符中的free_pages
的值。
从上述过程可以知道,在获取order对应大小的空闲块时,如果没有正好大小的空闲块,那么将会从更大的空闲块中分离出order对应大小的空闲块,分离后剩下的部分要按照伙伴系统的规则正确的记录到管理区描述符的free_area数组中,expand
函数在做这件事的同时,将我们需要的空闲块的第一个页框描述符的private字段设置成order(因为这个private仍然是空闲块未分割时的order值)。
return expand(zone, page, order, current_order, area);
至此,已经找到了需要的空闲块,返回其中的第一个页框描述符指针。
-
块内存释放函数
代码位置 mm/page_alloc.c
__free_pages_bulk,传入参数依次为要释放块的第一个页框描述符的指针,管理区的zone_mem_map
字段(即管理区中的第一个页框描述符指针),管理区描述符,释放块的order值
static inline void __free_pages_bulk (struct page *page, struct page *base,
struct zone *zone, unsigned int order)
除去一些报错的代码后,主要过程就是寻找这个释放块的伙伴,并将二者合并。
函数实现部分,寻找伙伴这部分的代码理解起来比较晦涩,但十分聪明
buddy_idx = (page_idx ^ (1 << order));
buddy = base + buddy_idx;
page_idx是释放块在zone_mem_map
中的下标,为了寻找它的伙伴块在zone_mem_map
中的下标,将page_idx与块大小进行异或操作,该步骤可以将page_idx的第order位取反,即可以得到比page_idx高一个块大小的下标或者比page_idx低一个块大小的下标,这个下标值就是释放块的伙伴块在zone_mem_map
中的下标。接着调用函数page_is_buddy
来检查该伙伴块是否可以合并,若不行,退出循环,若行,将order+1并继续循环寻找是否有更大的伙伴块可以合并。
coalesced = base + page_idx;
set_page_order(coalesced, order);
list_add(&coalesced->lru, &zone->free_area[order].free_list);
zone->free_area[order].nr_free++;
循环结束后,page_idx记录了合并块的第一个页框描述符在zone_mem_map
中的下标,加上base即zone_mem_map
本身的地址后,得到合并块的第一个页框描述符的指针coalesced(这单词也够隐晦),该合并块成为最终的释放块,更新第一个页框描述符的private字段,并将其插入到适当链表,最后再增加该链表空闲块数量。
至此,块内存释放结束。