本文轉載自:http://blog.csdn.net/vanbreaker/article/details/7626670
從2.6.32.25開始,linux在夥伴管理系統中引入遷移型別(migrate type)這麼一個概念,用於避免系統在長期執行過程中產生碎片。關於遷移型別的一些概念在介紹夥伴系統的資料結構的時候有提到過(見<
在分析具體的程式碼前,再來說一下為什麼要引入遷移型別。我們都知道夥伴系統是針對於解決外碎片的問題而提出的,那麼為什麼還要引入這樣一個概念來避免碎片呢?我們註意到,碎片一般是指散佈在記憶體中的小塊記憶體,由於它們已經被分配並且插入在大塊記憶體中,而導致無法分配大塊的連續記憶體。而夥伴系統把記憶體分配出去後,要再回收回來並且重新組成大塊記憶體,這樣一個過程必須建立兩個夥伴塊必須都是空閑的這樣一個基礎之上,如果其中有一個夥伴不是空閑的,甚至是其中的一個頁不是空閑的,都會導致無法分配一塊連續的大塊記憶體。
我們再取用之前上過的一個例子來看這個問題:
圖中,如果15對應的頁是空閑的,那麼夥伴系統可以分配出連續的16個頁框,而由於15這一個頁框被分配出去了,導致最多隻能分配出8個連續的頁框。假如這個頁還會被回收回夥伴系統,那麼至少在這段時間內產生了碎片,而如果更糟的,如果這個頁用來儲存一些核心永久性的資料而不會被回收回來,那麼碎片將永遠無法消除,這意味著15這個頁所處的最大記憶體塊永遠無法被連續的分配出去了。假如上圖中被分配出去的頁都是不可移動的頁,那麼就可以拿出一段記憶體,專門用於分配不可移動頁,雖然在這段記憶體中有碎片,但是避免了碎片散佈到其他型別的記憶體中。在系統中所有的記憶體都被標識為可移動的!也就是說一開始其他型別都沒有屬於自己的記憶體,而當要分配這些型別的記憶體時,就要從可移動型別的記憶體中奪取一部分過來,這樣可以根據實際情況來分配其他型別的記憶體。現在我們就來看看夥伴系統分配頁時,當指定的遷移型別的記憶體不足時,系統是如何做的
static inline struct page *
__rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
{
struct free_area * area;
int current_order;
struct page *page;
int migratetype, i;
/* Find the largest possible block of pages in the other list */
/*從最大的記憶體塊連結串列開始遍歷階數,核心傾向於儘量找到大的記憶體塊來滿足分配*/
for (current_order = MAX_ORDER-1; current_order >= order;
–current_order) {
for (i = 0; i
migratetype = fallbacks[start_migratetype][i];
/* MIGRATE_RESERVE handled later if necessary */
if (migratetype == MIGRATE_RESERVE)
continue;
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);
area->nr_free–;
/*
* If breaking a large block of pages, move all free
* pages to the preferred allocation list. If falling
* back for a reclaimable kernel allocation, be more
* agressive about taking ownership of free pages
*/
/*pageblock_order定義著核心認為的大塊記憶體究竟是多大*/
/*如果 1.當前得到的塊是一個比較大的塊,即階數大於pageblock_order/2=5
或者 2.之前指定的遷移型別為可回收頁
或者 3.沒有啟用遷移分組機制*/
if (unlikely(current_order >= (pageblock_order >> 1)) ||
start_migratetype == MIGRATE_RECLAIMABLE ||
page_group_by_mobility_disabled) {
unsigned long pages;
/*試圖將當前頁所處的最大記憶體塊移到之前指定的遷移型別對應的連結串列中,
只有空閑頁才會移動,所以真正可移動的頁數pages可能小於2^pageblock_order*/
pages = move_freepages_block(zone, page,
start_migratetype);
/* Claim the whole block if over half of it is free */
/*移動的頁面數大於大記憶體塊的一半,則修改整個塊的遷移型別*/
if (pages >= (1 <
page_group_by_mobility_disabled)
set_pageblock_migratetype(page,
start_migratetype);
migratetype = start_migratetype;
}
/* Remove the page from the freelists */
list_del(&page-;>lru);
rmv_page_order(page);
/* Take ownership for orders >= pageblock_order */
/*如果current_order大於等於10,則將超出的部分的遷移型別設為start_migratetype*/
if (current_order >= pageblock_order)
change_pageblock_range(page, current_order,
start_migratetype);
/*拆分,這裡註意的是如果之前改變了migratetype,則會將拆分的夥伴塊新增到新的遷移型別連結串列中*/
expand(zone, page, order, current_order, area, migratetype);
trace_mm_page_alloc_extfrag(page, order, current_order,
start_migratetype, migratetype);
return page;
}
}
return NULL;
}
-
首先我們看到的不尋常的一點就是,for迴圈優先遍歷大的記憶體塊,也就是說優先分配大記憶體塊,這似乎和夥伴系統優先分配小記憶體塊的原則相違背,但是仔細想想這樣做其實就是為了避免在新的遷移型別中引入碎片。如何說?現在假如A型別記憶體不足,向B型別求援,假設只從B中分配一塊最適合的小塊,OK,那麼過會兒又請求分配A型別記憶體,又得向B型別求援,這樣來來回回從B型別中一點一點的分配記憶體將會導致B型別的記憶體四處都散佈碎片,如果這些記憶體一直得不到釋放……核心已經不敢想象了……B型別可能會因為A型別而引入的碎片導致其再也分配不出大的記憶體塊了。出於這種原因,核心選擇直接分配一塊最大的記憶體塊給A,你們愛怎麼打怎麼鬧隨便你們,反正都是在A型別中進行,只要不拖累我B型別就可以了
-
當請求分配的記憶體比較大時或者最初的請求型別為可回收型別時,會表現得更加積極,也就是我前面所說的將對應的最大記憶體塊搬到最初的請求型別對應的連結串列中。我的理解是,這裡判斷一個比較大的記憶體型別的條件是用來和前面優先遍歷大記憶體塊的for迴圈相呼應的,也就是說假如得到的記憶體塊是一個比較小的記憶體塊,那就說明該記憶體型別自己也沒有大塊可分配的連續記憶體了,因此就不執行搬遷工作。而對於可回收型別的記憶體要執行搬遷是因為在一些時候,內核可能會非常頻繁地申請小塊可回收型別的記憶體。
-
當搬遷的記憶體的大於大塊記憶體的一半時,將徹底將這塊記憶體化為己有,也就是說將這片大塊記憶體區對應的頁型別標識點陣圖區域標識成最初申請的記憶體型別。
用<>上的一段話作為總結,“實際上,在啟動期間分配可移動記憶體區的情況較少,那麼分配器有很高的機率分配長度最大的記憶體區,並將其從可移動串列轉換到不可移動串列。由於分配的記憶體區長度是最大的,因此不會向可移動記憶體中引入碎片。總而言之,這種做法避免了啟動期間核心分配的記憶體(經常在系統的整個執行時間都不釋放)散佈到物理記憶體各處,從而使其他型別的記憶體分配免受碎片的幹擾,這也是頁可移動性分組框架的最重要標的之一”