Linux內核伙伴系統中頁面釋放,主函數為free_pages()
一、上層操作
www.2cto.com
/*用虛擬地址進行釋放*/
void free_pages(unsigned long addr, unsigned int order)
{
if (addr != 0) {
VM_BUG_ON(!virt_addr_valid((void *)addr));
__free_pages(virt_to_page((void *)addr), order);/*具體的釋放函數*/
}
}
www.2cto.com
/*釋放頁面*/
void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page)) {/*count值減一為0時釋放*/
/*調試*/
trace_mm_page_free_direct(page, order);
if (order == 0)
free_hot_page(page);/*釋放單個頁面*/
else
__free_pages_ok(page, order);
}
}
二、釋放單個頁面
釋放單個頁面free_hot_page()調用free_hot_cold_page()函數
www.2cto.com
static void free_hot_cold_page(struct page *page, int cold)
{
struct zone *zone = page_zone(page);
struct per_cpu_pages *pcp;
unsigned long flags;
int migratetype;
int wasMlocked = __TestClearPageMlocked(page);
/*調試代碼*/
kmemcheck_free_shadow(page, 0);
if (PageAnon(page))
page->mapping = NULL;
if (free_pages_check(page))
return;
if (!PageHighMem(page)) {
debug_check_no_locks_freed(page_address(page), PAGE_SIZE);
debug_check_no_obj_freed(page_address(page), PAGE_SIZE);
}
/*x86下為空*/
arch_free_page(page, 0);
/*調試用*/
kernel_map_pages(page, 1, 0);
/*獲得zone對應cpu的pcp*/
pcp = &zone_pcp(zone, get_cpu())->pcp;
/*獲得頁面的migratetype*/
migratetype = get_pageblock_migratetype(page);
set_page_private(page, migratetype);/*設置私有位為參數*/
local_irq_save(flags);/*保存中斷*/
if (unlikely(wasMlocked))
free_page_mlock(page);
__count_vm_event(PGFREE);
/*
* We only track unmovable, reclaimable and movable on pcp lists.
* Free ISOLATE pages back to the allocator because they are being
* offlined but treat RESERVE as movable pages so we can get those
* areas back if necessary. Otherwise, we may have to free
* excessively into the page allocator
*/
if (migratetype >= MIGRATE_PCPTYPES) {
if (unlikely(migratetype == MIGRATE_ISOLATE)) {
/*釋放到伙伴系統*/
free_one_page(zone, page, 0, migratetype);
goto out;
}
migratetype = MIGRATE_MOVABLE;
}
if (cold)/*加入到pcp鏈表尾部*/
list_add_tail(&page->lru, &pcp->lists[migratetype]);
else/*加入到pcp鏈表頭部*/
list_add(&page->lru, &pcp->lists[migratetype]);
pcp->count++;/*pcp計數加一*/
if (pcp->count >= pcp->high) {/*當pcp中頁面數量超過他的最高值時,
釋放pcp->batch個頁面到伙伴系統中*/
free_pcppages_bulk(zone, pcp->batch, pcp);
pcp->count -= pcp->batch;/*頁面數減去釋放的頁面數量*/
}
out:
local_irq_restore(flags);/*回復中斷*/
put_cpu();
}
從pcp中釋放頁面到伙伴系統中
free_pcppages_bulk()
www.2cto.com
are in same zone, and of same order.
* count is the number of pages to free.
*
* If the zone was previously in an "all pages pinned" state then look to
* see if this freeing clears that state.
*
* And clear the zone's pages_scanned counter, to hold off the "all pages are
* pinned" detection logic.
*/
/*從PCP中釋放count個頁面到伙伴系統中*/
static void free_pcppages_bulk(struct zone *zone, int count,
struct per_cpu_pages *pcp)
{
int migratetype = 0;
int batch_free = 0;
/*
* 雖然管理區可以按照CPU節點分類,但是也可以跨CPU節點進行內存分配,
* 因此這裡需要用自旋鎖保護管理區
* 使用每CPU緩存的目的,也是為了減少使用這把鎖。
*/
spin_lock(&zone->lock);
/* all_unreclaimable代表了內存緊張程度,釋放內存後,將此標志清除*/
zone_clear_flag(zone, ZONE_ALL_UNRECLAIMABLE);
zone->pages_scanned = 0;/* pages_scanned代表最後一次內存緊張以來,頁面回收過程已經掃描的頁數。
目前正在釋放內存,將此清0,待回收過程隨後回收時重新計數*/
/*增加管理區空閒頁數*/
__mod_zone_page_state(zone, NR_FREE_PAGES, count);
while (count) {
struct page *page;
struct list_head *list;
/*
* Remove pages from lists in a round-robin fashion. A
* batch_free count is maintained that is incremented when an
* empty list is encountered. This is so more pages are freed
* off fuller lists instead of spinning excessively around empty
* lists
*/
do {/*從pcp的三類鏈表中找出不空的一個,釋放*/
batch_free++;/*參看英文注釋*/
if (++migratetype == MIGRATE_PCPTYPES)
migratetype = 0;
list = &pcp->lists[migratetype];
} while (list_empty(list));
do {
page = list_entry(list->prev, struct page, lru);
/* must delete as __free_one_page list manipulates */
list_del(&page->lru);
/*釋放單個頁面到伙伴系統,注意這裡的分類回收*/
__free_one_page(page, zone, 0, migratetype);
trace_mm_page_pcpu_drain(page, 0, migratetype);
} while (--count && --batch_free && !list_empty(list));
}
spin_unlock(&zone->lock);
}
三、釋放多個頁面
釋放多個頁面__free_pages_ok()函數
www.2cto.com
int i;
int bad = 0;
int wasMlocked = __TestClearPageMlocked(page);
/*調試用,和相關宏有關*/
kmemcheck_free_shadow(page, order);
for (i = 0 ; i < (1 << order) ; ++i)/*頁面相關檢查*/
bad += free_pages_check(page + i);
if (bad)
return;
if (!PageHighMem(page)) {
debug_check_no_locks_freed(page_address(page),PAGE_SIZE<<order);
debug_check_no_obj_freed(page_address(page),
PAGE_SIZE << order);
}
/*X86體系下為空*/
arch_free_page(page, order);
/*調試,相關宏定義*/
kernel_map_pages(page, 1 << order, 0);
local_irq_save(flags);/*flags的暫存,關中斷*/
if (unlikely(wasMlocked))
free_page_mlock(page);
__count_vm_events(PGFREE, 1 << order);
/*傳入參數,進行具體的釋放工作,將1<<order的頁面釋放
到伙伴系統中*/
free_one_page(page_zone(page), page, order,
get_pageblock_migratetype(page));/*獲得內存塊的類型*/
local_irq_restore(flags);/*恢復中斷*/
www.2cto.com
static void free_one_page(struct zone *zone, struct page *page, int order,
int migratetype)
{
spin_lock(&zone->lock);/*獲得管理區的自旋鎖*/
zone_clear_flag(zone, ZONE_ALL_UNRECLAIMABLE);/* 只要是釋放了頁面,都需要將此兩個標志清0,表明內存不再緊張的事實*/
zone->pages_scanned = 0;
/*管理區空閒頁面計數*/
__mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);
/*釋放到指定的伙伴系統類型鏈表*/
__free_one_page(page, zone, order, migratetype);
spin_unlock(&zone->lock);/*釋放鎖*/
}
www.2cto.com
etype)
{
unsigned long page_idx;
if (unlikely(PageCompound(page)))/*要釋放的頁是巨頁的一部分*/
/* 解決巨頁標志,如果巨頁標志有問題,則退出*/
if (unlikely(destroy_compound_page(page, order)))
return;
VM_BUG_ON(migratetype == -1);
/*將頁面轉化為全局頁面數組的下標*/
page_idx = page_to_pfn(page) & ((1 << MAX_ORDER) - 1);
/* 如果被釋放的頁不是所釋放階的第一個頁,則說明參數有誤*/
VM_BUG_ON(page_idx & ((1 << order) - 1));
/* 校驗頁塊是否有效,檢查是否有空洞,頁塊中的頁面是否都在同一個zone中 */
VM_BUG_ON(bad_range(zone, page));
while (order < MAX_ORDER-1) {
unsigned long combined_idx;
struct page *buddy;
/*找出page頁面的伙伴
查找待釋放頁塊的伙伴,其中伙伴系統中每個塊
都有一個對應同樣大小的"伙伴塊"(由2的指數原理知)*/
buddy = __page_find_buddy(page, page_idx, order);
if (!page_is_buddy(page, buddy, order))/*檢查頁面是否為伙伴*/
break;
/* Our buddy is free, merge with it and move up one order. */
list_del(&buddy->lru);
zone->free_area[order].nr_free--;
/* 為下面的合並做准備,清除伙伴的PG_buddy標志位,並設置伙伴的private成員為
0 */
rmv_page_order(buddy);
/* 利用伙伴算法公式計算合並後父節點的頁塊索引*/
/*實際上是這樣的,這個要配合上面的找buddy
*算法一起看,找出的buddy時在原來的下標中往上或向下走
*(1<<order個位置
*而這裡的函數是找出合並後的新塊的首地址
*也就是說如果上面是加,這裡的基地址不變
*而如果上面是減,這裡往下減1<<order
*其中這裡的標號不是物理地址,也不是和mem_map
*直接關聯,而是運用於buddy算法,也就是這裡的
*構造出來的特定的表示,他使得這裡的伙伴的尋找
*和伙伴的合並實現的很巧妙
*/
combined_idx = __find_combined_index(page_idx, order);
page = page + (combined_idx - page_idx);
page_idx = combined_idx;
order++;
}
set_page_order(page, order);/*設置page的私有屬性,伙伴系統通過這個值來
確定頁面所屬的order*/
list_add(&page->lru,
&zone->free_area[order].free_list[migratetype]);/*伙伴系統中每一種order有5中空閒鏈表*/
zone->free_area[order].nr_free++;/*對應order的空閒塊加一*/
}
四、關於伙伴的操作
4.1,查找伙伴
www.2cto.com
static inline struct page *
__page_find_buddy(struct page *page, unsigned long page_idx, unsigned int order)
{
/*伙伴的計算原理,
*實際上,使用(1<<order)掩碼的異或(XOR)轉換page_idx第order位
*的值。因此,如果這個位原來是0,buddy_idx就等於page_idx+order
*相反,如果這個位原先是1,buddy_idx就等於page_idx-order
*此計算出來的伙伴為在mem_map中的下標
*/
unsigned long buddy_idx = page_idx ^ (1 << order);
/*返回伙伴塊的頁面基址*/
return page + (buddy_idx - page_idx);
}
4.2,檢查是否為伙伴
www.2cto.com
static inline int page_is_buddy(struct page *page, struct page *buddy,
int order)
{
if (!pfn_valid_within(page_to_pfn(buddy)))/*驗證此buddy的有效性*/
return 0;
if (page_zone_id(page) != page_zone_id(buddy))/*驗證page和他的buddy是否在同一個zone中*/
return 0;
/*驗證相關位和buddy的order值*/
/* 通過檢查PG_buddy 標志位判斷buddy是否在伙伴系統中,並且buddy是否在order級的
鏈表中,page的private成員存放頁塊所在鏈表的order。*/
if (PageBuddy(buddy) && page_order(buddy) == order) {
VM_BUG_ON(page_count(buddy) != 0);
return 1;
}
return 0;
}
總結:伙伴系統內存釋放或稱主要流程
1,如果釋放的是單個頁面,需要根據頁面類型考慮是否釋放到伙伴系統中,同時,將其加入到pcp鏈表中。如果pcp鏈表中內存過多,調用free_pcppages_bulk()函數將大塊內存放回伙伴系統中;
2,如果釋放的是多個頁面,直接調用__free_one_page()釋放到伙伴系統中。
3,釋放到伙伴系統中時,需要考慮和伙伴的合並情況。