https://en.wikipedia.org/wiki/Buddy_memory_allocation
原文地址:http://blog.csdn.net/vanbreaker/article/details/7605367
伙伴系統的概述
Linux內核內存管理的一項重要工作就是如何在頻繁申請釋放內存的情況下,避免碎片的產生。Linux采用伙伴系統解決外部碎片的問題,采用slab解決內部碎片的問題,在這里我們先討論外部碎片問題。避免外部碎片的方法有兩種:一種是之前介紹過的利用非連續內存的分配;另外一種則是用一種有效的方法來監視內存,保證在內核只要申請一小塊內存的情況下,不會從大塊的連續空閑內存中截取一段過來,從而保證了大塊內存的連續性和完整性。顯然,前者不能成為解決問題的普遍方法,一來用來映射非連續內存線性地址空間有限,二來每次映射都要改寫內核的頁表,進而就要刷新TLB,這使得分配的速度大打折扣,這對於要頻繁申請內存的內核顯然是無法忍受的。因此Linux采用后者來解決外部碎片的問題,也就是著名的伙伴系統。
什么是伙伴系統?
伙伴系統的宗旨就是用最小的內存塊來滿足內核的對於內存的請求。在最初,只有一個塊,也就是整個內存,假如為1M大小,而允許的最小塊為64K,那么當我們申請一塊200K大小的內存時,就要先將1M的塊分裂成兩等分,各為512K,這兩分之間的關系就稱為伙伴,然后再將第一個512K的內存塊分裂成兩等分,各位256K,將第一個256K的內存塊分配給內存,這樣就是一個分配的過程。下面我們結合示意圖來了解伙伴系統分配和回收內存塊的過程。
1 初始化時,系統擁有1M的連續內存,允許的最小的內存塊為64K,圖中白色的部分為空閑的內存塊,着色的代表分配出去了得內存塊。
2 程序A申請一塊大小為34K的內存,對應的order為0,即2^0=1個最小內存塊
2.1 系統中不存在order 0(64K)的內存塊,因此order 4(1M)的內存塊分裂成兩個order 3的內存塊(512K)
2.2 仍然沒有order 0的內存塊,因此order 3的內存塊分裂成兩個order 2的內存塊(256K)
2.3 仍然沒有order 0的內存塊,因此order 2的內存塊分裂成兩個order 1的內存塊(128K)
2.4 仍然沒有order 0的內存塊,因此order 1的內存塊分裂成兩個order 0的內存塊(64K)
2.5 找到了order 0的內存塊,將其中的一個分配給程序A,現在伙伴系統的內存為一個order 0的內存塊,一個order
1的內存塊,一個order 2的內存塊以及一個order 3的內存塊
3 程序B申請一塊大小為66K的內存,對應的order為1,即2^1=2個最小內存塊,由於系統中正好存在一個order 1的內
存塊,所以直接用來分配
4 程序C申請一塊大小為35K的內存,對應的order為0,同樣由於系統中正好存在一個order 0的內存塊,直接用來分
配
5 程序D申請一塊大小為67K的內存,對應的order為1
5.1 系統中不存在order 1的內存塊,於是將order 2的內存塊分裂成兩塊order 1的內存塊
5.2 找到order 1的內存塊,進行分配
6 程序B釋放了它申請的內存,即一個order 1的內存塊
7 程序D釋放了它申請的內存
7.1 一個order 1的內存塊回收到內存當中
7.2由於該內存塊的伙伴也是空閑的,因此兩個order 1的內存塊合並成一個order 2的內存塊
8 程序A釋放了它申請的內存,即一個order 0的內存塊
9 程序C釋放了它申請的內存
9.1 一個order 0的內存塊被釋放
9.2 兩個order 0伙伴塊都是空閑的,進行合並,生成一個order 1的內存塊m
9.3 兩個order 1伙伴塊都是空閑的,進行合並,生成一個order 2的內存塊
9.4 兩個order 2伙伴塊都是空閑的,進行合並,生成一個order 3的內存塊
9.5 兩個order 3伙伴塊都是空閑的,進行合並,生成一個order 4的內存塊
相關的數據結構
在前面的文章中已經簡單的介紹過struct zone這個結構,對於每個管理區都有自己的struct zone,而struct zone中的struct free_area則是用來描述該管理區伙伴系統的空閑內存塊的
-
[cpp] view plaincopy
-
-
struct zone {
-
...
-
...
-
struct free_area free_area[MAX_ORDER];
-
...
-
...
-
}
-
[cpp] view plaincopy
-
-
struct free_area {
-
struct list_head free_list[MIGRATE_TYPES];
-
unsigned long nr_free;
-
};
free_area共有MAX_ORDER個元素,其中第order個元素記錄了2^order的空閑塊,這些空閑塊在free_list中以雙向鏈表的形式組織起來,對於同等大小的空閑塊,其類型不同,將組織在不同的free_list中,nr_free記錄了該free_area中總共的空閑內存塊的數量。MAX_ORDER的默認值為11,這意味着最大內存塊的大小為2^10=1024個頁框。對於同等大小的內存塊,每個內存塊的起始頁框用於鏈表的節點進行相連,這些節點對應的着struct page中的lru域
-
[cpp] view plaincopy
-
-
struct page {
-
-
...
-
...
-
struct list_head lru; /* Pageout list, eg. active_list
-
* protected by zone->lru_lock !
-
*/
-
...
-
}
連接示意圖如下:
在2.6.24之前的內核版本中,free_area結構中只有一個free_list數組,而從2.6.24開始,free_area結構中存有MIGRATE_TYPES個free_list,這些數組是根據頁框的移動性來划分的,為什么要進行這樣的划分呢?實際上也是為了減少碎片而提出的,我們考慮下面的情況:
圖中一共有32個頁,只分配出了4個頁框,但是能夠分配的最大連續內存也只有8個頁框(因為伙伴系統分配出去的內存必須是2的整數次冪個頁框),內核解決這種問題的辦法就是將不同類型的頁進行分組。分配出去的頁面可分為三種類型:
-
不可移動頁(Non-movable pages):這類頁在內存當中有固定的位置,不能移動。內核的核心分配的內存大多屬於這種類型
-
可回收頁(Reclaimable pages):這類頁不能直接移動,但可以刪除,其內容頁可以從其他地方重新生成,例如,映射自文件的數據屬於這種類型,針對這種頁,內核有專門的頁面回收處理
-
可移動頁:這類頁可以隨意移動,用戶空間應用程序所用到的頁屬於該類別。它們通過頁表來映射,如果他們復制到新的位置,頁表項也會相應的更新,應用程序不會注意到任何改變。
假如上圖中大部分頁都是可移動頁,而分配出去的四個頁都是不可移動頁,由於不可移動頁插在了其他類型頁的中間,就導致了無法從原本空閑的連續內存區中分配較大的內存塊。考慮下圖的情況:
將可回收頁和不可移動頁分開,這樣雖然在不可移動頁的區域當中無法分配大塊的連續內存,但是可回收頁的區域卻沒有受其影響,可以分配大塊的連續內存。
內核對於遷移類型的定義如下:
-
[cpp] view plaincopy
-
-
-
-
-
-
-
-
前三種類型已經介紹過
MIGRATE_PCPTYPES是per_cpu_pageset,即用來表示每CPU頁框高速緩存的數據結構中的鏈表的遷移類型數目
MIGRATE_RESERVE是在前三種的列表中都沒用可滿足分配的內存塊時,就可以從MIGRATE_RESERVE分配
MIGRATE_ISOLATE用於跨越NUMA節點移動物理內存頁,在大型系統上,它有益於將物理內存頁移動到接近於是用該頁最頻繁地CPU
MIGRATE_TYPES表示遷移類型的數目
當一個指定的遷移類型所對應的鏈表中沒有空閑塊時,將會按以下定義的順序到其他遷移類型的鏈表中尋找
-
[cpp] view plaincopy
-
-
static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
-
[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE },
-
[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE },
-
[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
-
[MIGRATE_RESERVE] = { MIGRATE_RESERVE, MIGRATE_RESERVE, MIGRATE_RESERVE }, /* Never used */
-
};
Linux伙伴系統原理-內存分配和釋放
主要分析Linux伙伴系統算法,內存的分配和釋放
1.伙伴系統簡介
Linux內核內存管理的一項重要工作就是如何在頻繁申請釋放內存的情況下,避免碎片的產生,
Linux采用伙伴系統解決外部碎片的問題,采用slab解決內 部碎片的問題.
伙伴算法(Buddy system)把所有的空閑頁框分為11個塊鏈表,每塊鏈表中分布包含特定的連續頁框地址空間,比如第0個塊鏈表包含大小為2^0個連續的頁框,第1個塊鏈表中,每個鏈表元素包含2個頁框大小的連續地址空間,….,第10個塊鏈表中,每個鏈表元素代表4M的連續地址空間。每個鏈表中元素的個數在系統初始化時決定,在執行過程中,動態變化。伙伴算法每次只能分配2的冪次頁的空間,比如一次分配1頁,2頁,4頁,8頁,…,1024頁(2^10)等等,每頁大小一般為4K,因此,伙伴算法最多一次能夠分配4M的內存空間。
1.1.1 關鍵數據結構
struct zone {
struct free_area free_area[MAX_ORDER];
}
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
free_area共有MAX_ORDER個元素,其中第order個元素記錄了2^order的空閑塊,這些空閑塊在free_list中以雙向鏈表的形式組織起來,對於同等大小的空閑塊,其類型不同,將組織在不同的free_list中,nr_free記錄了該free_area中總共的空閑內存塊的數量。MAX_ORDER的默認值為11,這意味着最大內存塊的大小為2^10=1024個頁框。對於同等大小的內存塊,每個內存塊的起始頁框用於鏈表的節點進行相連,這些節點對應的着struct page中的lru域
struct page {
struct list_head lru; /* Pageout list, eg. active_list
* protected by zone->lru_lock !
*/
}
1.1.2 遷移類型
不可移動頁(Non-movable pages):這類頁在內存當中有固定的位置,不能移動。內核的核心分配的內存大多屬於這種類型
可回收頁(Reclaimable pages):這類頁不能直接移動,但可以刪除,其內容頁可以從其他地方重新生成,例如,映射自文件的數據屬於這種類型,針對這種頁,內核有專門的頁面回收處理
可移動頁:這類頁可以隨意移動,用戶空間應用程序所用到的頁屬於該類別。它們通過頁表來映射,如果他們復制到新的位置,頁表項也會相應的更新,應用程序不會注意到任何改變。
當一個指定的遷移類型所對應的鏈表中沒有空閑塊時,將會按以下定義的順序到其他遷移類型的鏈表中尋找
static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE },
[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE },
[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
[MIGRATE_RESERVE] = { MIGRATE_RESERVE, MIGRATE_RESERVE, MIGRATE_RESERVE }, /* Never used */
};
通過cat /proc/pagetypeinfo可以看到遷移類型和order的關系
1.2 內存的分配
分配函數為alloc_pages,最終調用到__alloc_pages_nodemask
-
struct page *
-
__alloc_pages_nodemask( gfp_t gfp_mask, unsigned int order,
-
struct zonelist *zonelist, nodemask_t *nodemask)
-
{
-
struct zoneref *preferred_zoneref;
-
struct page *page = NULL;
-
unsigned int cpuset_mems_cookie;
-
int alloc_flags = ALLOC_WMARK_LOW|ALLOC_CPUSET|ALLOC_FAIR;
-
gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
-
struct alloc_context ac = {
-
/*根據gfp_mask,找到合適的zone idx和migrate類型 */
-
.high_zoneidx = gfp_zone(gfp_mask),
-
.nodemask = nodemask,
-
.migratetype = gfpflags_to_migratetype(gfp_mask),
-
};
-
-
retry_cpuset:
-
cpuset_mems_cookie = read_mems_allowed_begin();
-
-
/* We set it here, as __alloc_pages_slowpath might have changed it */
-
ac.zonelist = zonelist;
-
-
/* Dirty zone balancing only done in the fast path */
-
ac.spread_dirty_pages = (gfp_mask & __GFP_WRITE);
-
-
/*根據分配掩碼,確認先從哪個zone分配 */
-
/* The preferred zone is used for statistics later */
-
preferred_zoneref = first_zones_zonelist(ac.zonelist, ac.high_zoneidx,
-
ac.nodemask ? : &cpuset_current_mems_allowed,
-
&ac.preferred_zone);
-
if (!ac.preferred_zone)
-
goto out;
-
ac.classzone_idx = zonelist_zone_idx(preferred_zoneref);
-
-
/* First allocation attempt */
-
alloc_mask = gfp_mask|__GFP_HARDWALL;
-
/*快速分配 */
-
page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
-
if (unlikely(!page)) {
-
/*慢速分配,涉及到內存回收,暫不分析 */
-
page = __alloc_pages_slowpath(alloc_mask, order, &ac);
-
}
-
-
out:
-
if (unlikely(!page && read_mems_allowed_retry(cpuset_mems_cookie)))
-
goto retry_cpuset;
-
-
return page;
-
}
1.2.1 水位控制
每個zone有三個水位(watermark),用以標識系統內存存量,由數組 watermark[NR_WMARK]表示.
WMARK_MIN,WMARK_LOW,WMARK_HIGH,當內存存量低於對應水位時,就會調用zone_reclaim()進行內存回收.
mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
if (!zone_watermark_ok(zone, order, mark,//判斷水位是否正常
ac->classzone_idx, alloc_flags)) {
ret = zone_reclaim(zone, gfp_mask, order);
}
1.2.2 單個頁面的分配
get_page_from_freelist->buffered_rmqueue:
order=0時,單個頁面直接從per cpu的free list分配,這樣效率最高.
-
if (likely(order == 0)) {
-
struct per_cpu_pages *pcp;
-
struct list_head * list;
-
local_irq_save(flags);
-
pcp = &this_cpu_ptr(zone->pageset)->pcp;
-
list = &pcp->lists[migratetype];
-
/*如果對應list為空,則從伙伴系統拿內存*/
-
if (list_empty(list)) {
-
pcp->count += rmqueue_bulk(zone, 0,
-
pcp->batch, list,
-
migratetype, gfp_flags);
-
if (unlikely(list_empty(list)))
-
goto failed;
-
}
-
-
/*分配一個頁面 */
-
if ((gfp_flags & __GFP_COLD) != 0)
-
page = list_entry( list->prev, struct page, lru);
-
else
-
page = list_entry( list->next, struct page, lru);
-
-
if (!(gfp_flags & __GFP_CMA) &&
-
is_migrate_cma(get_pcppage_migratetype(page))) {
-
page = NULL;
-
local_irq_restore(flags);
-
} else {
-
list_del(&page->lru);
-
pcp->count--;
-
}
-
}
1.2.3 多個頁面的分配(order>1)
get_page_from_freelist->buffered_rmqueue->__rmqueue:
-
static struct page *__rmqueue(struct zone *zone, unsigned int order,
-
int migratetype, gfp_t gfp_flags)
-
{
-
struct page *page = NULL;
-
/*CMA內存的分配 */
-
if ((migratetype == MIGRATE_MOVABLE) && (gfp_flags & __GFP_CMA))
-
page = __rmqueue_cma_fallback(zone, order);
-
-
if (!page)/*根據order和migrate type找到對應的freelist分配內存 */
-
page = __rmqueue_smallest(zone, order, migratetype);
-
-
if (unlikely(!page))/*當對應的migrate type無法滿足order分配時,進行fallback規則分配
-
,分配規則定義在fallbacks數組中。 */
-
page = __rmqueue_fallback(zone, order, migratetype);
-
-
-
return page;
-
}
這里分析migrate type能夠滿足內存分配的情況
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;
/* 根據order,逐級向上查找*/
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--;
/* 切蛋糕,如果current_order大於目標order,則要把多余的內存掛到對應的order鏈表.*/
expand(zone, page, order, current_order, area, migratetype);
set_pcppage_migratetype(page, migratetype);
return page;
}
return NULL;
}
-
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;//先一分為2
-
-
while (high > low) {
-
area--; /* 回退到下一級鏈表*/
-
high--;
-
size >>= 1;/*Size減半 */
-
/*接入下一級空閑鏈表 */
-
list_add(&page[size].lru, &area->free_list[migratetype]);
-
area->nr_free++;
-
set_page_order(&page[size], high);
-
}
-
}
1.2.4 fallback分配
當前migrate type不能滿足內存分配需求時,需要到其他migrate type空閑鏈表分配內存.
規則如下:
static int fallbacks[MIGRATE_TYPES][4] = {
[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },
[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },
[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
#ifdef CONFIG_CMA
[MIGRATE_CMA] = { MIGRATE_TYPES }, /* Never used */
#endif
#ifdef CONFIG_MEMORY_ISOLATION
[MIGRATE_ISOLATE] = { MIGRATE_TYPES }, /* Never used */
#endif
};
-
__rmqueue_fallback( struct zone *zone, unsigned int order, int start_migratetype)
-
{
-
struct free_area *area;
-
unsigned int current_order;
-
struct page *page;
-
int fallback_mt;
-
bool can_steal;
-
-
/* 先從最大的order list中找到合適的塊 */
-
for (current_order = MAX_ORDER-1;
-
current_order >= order && current_order <= MAX_ORDER -1;
-
--current_order) {
-
area = &(zone->free_area[current_order]);
-
/*從fallbacks數組中找到合適的migrate type鏈表 */
-
fallback_mt = find_suitable_fallback(area, current_order,
-
start_migratetype, false, &can_steal);
-
if (fallback_mt == -1)
-
continue;
-
-
page = list_entry(area->free_list[fallback_mt].next,
-
struct page, lru);
-
if (can_steal &&/*改變pageblock的migrate type */
-
get_pageblock_migratetype(page) != MIGRATE_HIGHATOMIC)
-
steal_suitable_fallback(zone, page, start_migratetype);
-
-
/* Remove the page from the freelists */
-
area->nr_free--;
-
list_del(&page->lru);
-
rmv_page_order(page);
-
/*切蛋糕 */
-
expand(zone, page, order, current_order, area,
-
start_migratetype);
-
-
set_pcppage_migratetype(page, start_migratetype);
-
-
trace_mm_page_alloc_extfrag(page, order, current_order,
-
start_migratetype, fallback_mt);
-
-
return page;
-
}
-
-
return NULL;
-
}
如UNMOVABLE鏈表內存不足時,優先從RECLAIMABLE鏈表分配,再從MOVABLE鏈表分配
1.3 內存的釋放
頁面的釋放,最終調用到__free_one_page函數.
首先兩塊內存是伙伴塊,必須滿足以下條件:
1. 伙伴不能在空洞頁面中,要有實實在在的物理頁面/the buddy is not in a hole/
2. 伙伴塊在伙伴系統中,也就是伙伴塊要是空閑的,沒有被分配出去的內存塊/the buddy is in the buddy system/
3. 要有相同的order /a page and its buddy have the same order/
4. 位於同一個zone./a page and its buddy are in the same zone/
5. 物理上要相連.且兩個page的起始pfn號一定相差2^order,則有計算公式B2 = B1 ^ (1 << O)
static inline unsigned long
__find_buddy_index(unsigned long page_idx, unsigned int order)
{
return page_idx ^ (1 << order);//計算buddy page的index
}
-
static inline void __free_one_page(struct page *page,
-
unsigned long pfn,
-
struct zone *zone, unsigned int order,
-
int migratetype)
-
{
-
unsigned long page_idx;
-
unsigned long combined_idx;
-
unsigned long uninitialized_var(buddy_idx);
-
struct page *buddy;
-
unsigned int max_order;
-
-
max_order = min_t( unsigned int, MAX_ORDER, pageblock_order + 1);
-
-
/*計算page_idx, */
-
page_idx = pfn & (( 1 << MAX_ORDER) - 1);
-
-
VM_BUG_ON_PAGE(page_idx & (( 1 << order) - 1), page);
-
VM_BUG_ON_PAGE(bad_range(zone, page), page);
-
-
continue_merging:
-
while (order < max_order - 1) {
-
/*找到buddy ix */
-
buddy_idx = __find_buddy_index(page_idx, order);
-
buddy = page + (buddy_idx - page_idx);
-
/*判斷是否是伙伴塊 */
-
if (!page_is_buddy(page, buddy, order))
-
goto done_merging;
-
/*
-
* Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
-
* merge with it and move up one order.
-
*/
-
if (page_is_guard(buddy)) {
-
clear_page_guard(zone, buddy, order, migratetype);
-
} else {
-
list_del(&buddy->lru);
-
zone->free_area[order].nr_free--;
-
rmv_page_order(buddy);
-
}
-
/*繼續向上合並 */
-
combined_idx = buddy_idx & page_idx;
-
page = page + (combined_idx - page_idx);
-
page_idx = combined_idx;
-
order++;
-
}
-
-
done_merging:
-
set_page_order(page, order);
-
-
/*
-
* If this is not the largest possible page, check if the buddy
-
* of the next-highest order is free. If it is, it's possible
-
* that pages are being freed that will coalesce soon. In case,
-
* that is happening, add the free page to the tail of the list
-
* so it's less likely to be used soon and more likely to be merged
-
* as a higher order page
-
*/
-
if ((order < MAX_ORDER-2) && pfn_valid_within(page_to_pfn(buddy))) {
-
struct page *higher_page, *higher_buddy;
-
/*這里檢查更上一級是否存在伙伴關系,如果是的,則把page添加到鏈表末尾,這樣有利於頁面回收. */
-
combined_idx = buddy_idx & page_idx;
-
higher_page = page + (combined_idx - page_idx);
-
buddy_idx = __find_buddy_index(combined_idx, order + 1);
-
higher_buddy = higher_page + (buddy_idx - combined_idx);
-
if (page_is_buddy(higher_page, higher_buddy, order + 1)) {
-
list_add_tail(&page->lru,
-
&zone->free_area[order].free_list[migratetype]);
-
goto out;
-
}
-
}
-
/*鏈接到對應的free list 鏈表 */
-
list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
-
out:
-
zone->free_area[order].nr_free++;
-
}