Linux采用著名的伙伴系统(buddy system)算法来解决外碎片问题。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续的页框。对1024个页框的最大请求对应着4MB大小的连续RAM块。每个块的第一个页框的物理地址是该块大小的整数倍。例如,大小为16个页框的块,其起始地址是16*2^12的倍数。内核试图把大小为b的一对空闲伙伴块合并为一个大小为2b的单独块。满足以下条件的两个块称为伙伴:
- 两个块具有相同的大小,记作b
- 他们的物理地址是连续的
- 第一块的第一个页框的物理地址是2*b*2^12的倍数
核心数据结构
实际上,每个管理区都关系到mem_map元素的子集,子集中的第一个元素和元素个数分别由管理区描述符的zone_mem_map和size字段指定。包含有11个元素,元素类型为free_area的一个数组,每个元素对应一种块大小。该数组存放在描述符的free_area字段中。
- struct free_area {
- struct list_head free_list;
- unsigned long nr_free;
- };
我们考虑管理区描述符中free_area数组的第k个元素,它标识所有大小为2^k的空闲块。这个元素的free_list字段是双向循环链表的头,这个双向循环链表集中了大小为2^k页的空闲块对应的页描述符。更精确的说,该链表包含每个空闲页块(大小为2^k)的起始页框的页描述符;指向链表中相邻元素的指针存放在页描述符的lru字段中。除了链表头外,free_area数组的第k个元素同样包含字段nr_free,它指定了大小为2^k页的空闲块的个数。当然,如果没有大小为2^k的空闲页框块,则nr_free等于0且free_list为空(free_list的两个指针都指向它自己的free_list字段)。最后,一个2^k的空闲页块的第一个页的描述符的private字段存放了块的order,也就是数字k。正是由于这个字段,当页块被释放时,内核可以确定这个块的伙伴是否也空闲,如果是的话,它可以把两个块结合成大小为2^(k+1)页的单一块。
从buddy system分配pages
page allocator是buddy system的前端,buddy system进行实际的页管理与分配,下面分析buddy system的页分配函数:
- 458static int rmqueue_bulk(struct zone *zone, unsigned int order,
- 459 unsigned long count, struct list_head *list)
- 460{
- 461 unsigned long flags;
- 462 int i;
- 463 int allocated = 0;
- 464 struct page *page;
- 465
- 466 spin_lock_irqsave(&zone->lock, flags);
- 467 for (i = 0; i < count; ++i) {
- 468 page = __rmqueue(zone, order);
- 469 if (page == NULL)
- 470 break;
- 471 allocated++;
- 472 list_add_tail(&page->lru, list);
- 473 }
- 474 spin_unlock_irqrestore(&zone->lock, flags);
- 475 return allocated;
- 476}
- 431static struct page *__rmqueue(struct zone *zone, unsigned int order)
- 432{
- 433 struct free_area * area;
- 434 unsigned int current_order;
- 435 struct page *page;
- 436
- 437 for (current_order = order; current_order < MAX_ORDER; ++current_order) {
- 438 area = zone->free_area + current_order;
- 439 if (list_empty(&area->free_list))
- 440 continue;
- 441
- 442 page = list_entry(area->free_list.next, struct page, lru);
- 443 list_del(&page->lru);
- 444 rmv_page_order(page);
- 445 area->nr_free--;
- 446 zone->free_pages -= 1UL << order;
- 447 return expand(zone, page, order, current_order, area);
- 448 }
- 449
- 450 return NULL;
- 451}
- 因为在指定的order的free_area的链表中不一定能获得指定的page块,所以可能会去更大的order的链表里找page块
- 获得指定order的free_area数组元素
- 如果指定order的free_area的链表为空,去更大order的free_area找
- 否则,从free_area的free_list上删除page块的第一个page的描述符
- 将page块的第一个page的描述符的本来存放order的private字段清0
- 187static inline void rmv_page_order(struct page *page)
- 188{
- 189 __ClearPagePrivate(page);
- 190 page->private = 0;
- 191}
- 减少指定free_area上的空闲page块的计数
- 减少管理器的空闲page数
- 如果从比指定的order高的order的free_area分配,则会产生一些伙伴,把伙伴放到低于分配的order的free_area链表中
- 368expand(struct zone *zone, struct page *page,
- 369 int low, int high, struct free_area *area)
- 370{
- 371 unsigned long size = 1 << high;
- 372
- 373 while (high > low) {
- 374 area--;
- 375 high--;
- 376 size >>= 1;
- 377 BUG_ON(bad_range(zone, &page[size]));
- 378 list_add(&page[size].lru, &area->free_list);
- 379 area->nr_free++;
- 380 set_page_order(&page[size], high);
- 381 }
- 382 return page;
- 383}
比如之前需要从order为2的分配,但是order为2的free_area的free_list没有page块了,则去order为3的链表上找,如果此时order为3的链表上也没有空闲page块,则到order为4的链表上去找,如果此时恰巧找到则停止。这样获得了一个有16个page的page块。但是只请求了4个page,此时那些多余的page要放到order为2,order为3的链表上。看程序,首先获得size,也就是这里说的16,在我的例子里,high为4,low为2。area--也就是获得order为3的那个free_area;high--,此时为3;size>>=1,此时size为8;将刚才那个16个page的page块的后8个page组成块放到order为3的链表上,增加order为3上的块计数,
- 182static inline void set_page_order(struct page *page, int order) {
- 183 page->private = order;
- 184 __SetPagePrivate(page);
- 185}
释放pages到buddy system
- 308free_pages_bulk(struct zone *zone, int count,
- 309 struct list_head *list, unsigned int order)
- 310{
- 311 unsigned long flags;
- 312 struct page *base, *page = NULL;
- 313 int ret = 0;
- 314
- 315 base = zone->zone_mem_map;
- 316 spin_lock_irqsave(&zone->lock, flags);
- 317 zone->all_unreclaimable = 0;
- 318 zone->pages_scanned = 0;
- 319 while (!list_empty(list) && count--) {
- 320 page = list_entry(list->prev, struct page, lru);
- 321 /* have to delete it as __free_pages_bulk list manipulates */
- 322 list_del(&page->lru);
- 323 __free_pages_bulk(page, base, zone, order);
- 324 ret++;
- 325 }
- 326 spin_unlock_irqrestore(&zone->lock, flags);
- 327 return ret;
- 328}
- 236static inline void __free_pages_bulk (struct page *page, struct page *base,
- 237 struct zone *zone, unsigned int order)
- 238{
- 239 unsigned long page_idx;
- 240 struct page *coalesced;
- 241 int order_size = 1 << order;
- 242
- 243 if (unlikely(order))
- 244 destroy_compound_page(page, order);
- 245
- 246 page_idx = page - base;
- 247
- 248 BUG_ON(page_idx & (order_size - 1));
- 249 BUG_ON(bad_range(zone, page));
- 250
- 251 zone->free_pages += order_size;
- 252 while (order < MAX_ORDER-1) {
- 253 struct free_area *area;
- 254 struct page *buddy;
- 255 int buddy_idx;
- 256
- 257 buddy_idx = (page_idx ^ (1 << order));
- 258 buddy = base + buddy_idx;
- 259 if (bad_range(zone, buddy))
- 260 break;
- 261 if (!page_is_buddy(buddy, order))
- 262 break;
- 263 /* Move the buddy up one level. */
- 264 list_del(&buddy->lru);
- 265 area = zone->free_area + order;
- 266 area->nr_free--;
- 267 rmv_page_order(buddy);
- 268 page_idx &= buddy_idx;
- 269 order++;
- 270 }
- 271 coalesced = base + page_idx;
- 272 set_page_order(coalesced, order);
- 273 list_add(&coalesced->lru, &zone->free_area[order].free_list);
- 274 zone->free_area[order].nr_free++;
- 275}
- page_idx局部变量包含块中第一个页框的下标,这是相对管理区中的第一个页框而言的。zone_mem_map是指向管理区第一个页描述符的指针。buddy_idx是page_idx的伙伴的下标
- 这个是buddy_idx是怎样算出来的呢,可以举一个例子
- 例如对于0 order, (0,1), (2,3), (3,4)... , 已知2, 则伙伴就是3.
- 对于1order, (0/1, 2/3), (4/5,6/7), (8/9, 10/11), 已知2/3, 则伙伴是0/1.
如果现在order=1,并且是6/7这个块,现在要找伙伴4/5这个块,那么6为0110,与1<<1即0010异或,得0100,即4 - 所以ULK3上说使用(1<<order)掩码的异或(XOR)转换page_idx第order位的值。因此,如果这个位原先是0,buddy_idx就等于page_idx+order_size;相反,如果这个位原先是1,buddy_idx就等于page_idx-order_size
- page_idx &= buddy_idx可以通过刚才的例子看出这个是获得两个兄弟前面的那个兄弟
- 202static inline int page_is_buddy(struct page *page, int order)
- 203{
- 204 if (PagePrivate(page) &&
- 205 (page_order(page) == order) &&
- 206 !PageReserved(page) &&
- 207 page_count(page) == 0)
- 208 return 1;
- 209 return 0;
- 210}
检查两个page块是否是兄弟
注意这里是个while循环,page块释放到buddy system后,如果能与其他page块合并,则一层层向上找伙伴,然后合并成更大的,再向上找伙伴,实在找不到了就停止了!