1. 程式人生 > >夥伴算法的實現-分配頁框

夥伴算法的實現-分配頁框

set current 列表 sta ack 還要 next urn pos

內核中alloc_pages系列頁框分配函數都是基於夥伴算法實現的,這些函數最終都會調用夥伴算法的入口函數buffered_rmqueue()。

Linux內核管理物理內存有三種方式,其一就是經典的夥伴算法。但是夥伴算法分配物理內存的基本單位是頁框,因此內核又引入了slab機制,基於此機制實現的物理內存分配器可以快速有效的分配小於頁框的物理內存,並且可以有效避免內部碎片。另外,內核常常會申請單個頁框大小的物理內存,因此內核又引入了per-CPU機制,該機制專門用於快速分配單個頁框。

1.__rmqueue()

其實buffered_rmqueue()函數仍然沒有進行真正的頁框分配,該函數首先判斷分配階是否為0,如果是則啟用per-CPU機制來分配物理內存,否則調用__rmqueue()。

static struct page *__rmqueue(struct zone *zone, unsigned int order,
int migratetype)
{
struct page *page;
retry_reserve:
page = __rmqueue_smallest(zone, order, migratetype);

if (unlikely(!page) && migratetype != MIGRATE_RESERVE) {
page = __rmqueue_fallback(zone, order, migratetype);

if (!page) {
migratetype = MIGRATE_RESERVE;
goto retry_reserve;
}
}

trace_mm_page_alloc_zone_locked(page, order, migratetype);
return page;
}

傳遞到此函數中的zone表示夥伴算法將從該內存管理區中分配頁框,order即分配階,migratetype表示遷移類型。該函數首選__rmqueue_smallest()進行內存分配,如果在指定的遷移類型上分配失敗後,再選用其他備用的遷移列表進行內存分配,該過程通過__rmqueue_fallback()完成。總之內核總是在竭盡全力保證滿足分配內存的請求。

2.__rmqueue_smallest()

該函數的實現比較簡單,從當前指定的分配階到最高分配階依次進行遍歷。在每次遍歷的分配階鏈表中,根據參數migratetype選擇正確的遷移隊列。根據以上的限定條件,當選定一個頁框塊鏈表後,只要該鏈表不為空,就說明可以分配該分配階對應的頁框塊。

一旦選定在當前遍歷的分配階鏈表上分配頁框,那麽就通過list_entry()將該頁框塊從鏈表上移除。然後將頁框塊首頁框的PG_buddy標誌刪除,刪除該標誌說明當前頁框塊已經不屬於夥伴鏈表。並且將該首頁框描述符中的priveate置0,該字段中本來保存的是其所處頁框塊的分配階。以上這個過程通過rmv_page_order()完成。此外,還要更新頁框塊鏈表nr_free的值。

static inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
int migratetype)
{
unsigned int current_order;
struct free_area * area;
struct page *page;

for (current_order = order; current_order < MAX_ORDER; ++current_order) {
area = &(zone->free_area[current_order]);
if (list_empty(&area->free_list[migratetype]))
continue;

page = list_entry(area->free_list[migratetype].next, struct page, lru);
list_del(&page->lru);
rmv_page_order(page);
area->nr_free--;
expand(zone, page, order, current_order, area, migratetype);
return page;
}

return NULL;
}

static inline void rmv_page_order(struct page *page)
{
__ClearPageBuddy(page);
set_page_private(page, 0);
}

__rmqueue_smallest()內部還有一個重要的函數expand()。進入該函數的條件是當所申請的分配階order小於當前選中的分配階current_order,也就是說指定的分配階鏈表中沒有空閑的頁框塊,只能選用較大的頁框塊。因此,expand()必須按照夥伴算法的分裂原理將比較大的頁框塊分割成較小的塊。

3.expand()

分裂函數的實現也是顯而易見的,它完全遵照夥伴算法的分裂原理。這裏有兩個分配階,一個是申請頁框時指定的low,一個是在上級函數中遍歷時所選定的high。該函數從high分配階開始遞減向low遍歷,也就是從較大的頁框塊開始依次分裂。

比如high為4,而low為2。那麽第一遍歷時,將大小為16(分配階為4)的頁框塊一份為2。通過list_add()將後面的8個連續頁框塊加入下級鏈表(分配階為3),下級鏈表通過將area指針自減即可得到,後8個頁框塊的指針也通過page+size獲得,而page仍然指向最初的頁框塊首頁框。此時還要對分配階為3的鏈表更新nr_free,以及通過set_page_order()對後8個頁框塊設置一些標誌。

第二次遍歷將前面8個頁框塊繼續一分為二,將後4個頁框塊加入area所指向的下級鏈表(分配階為2)。第三次遍歷時,循環條件已經不再滿足,因此返回前4個頁框塊首頁框的描述符地址page。

static inline void expand(struct zone *zone, struct page *page,
int low, int high, struct free_area *area,
int migratetype)
{
unsigned long size = 1 << high;
while (high > low) {
area--;
high--;
size >>= 1;
VM_BUG_ON(bad_range(zone, &page[size]));
list_add(&page[size].lru, &area->free_list[migratetype]);
area->nr_free++;
set_page_order(&page[size], high);
}
}

static inline void set_page_order(struct page *page, int order)
{
set_page_private(page, order);
__SetPageBuddy(page);
}

夥伴算法的實現-分配頁框