歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux內存管理之伙伴系統(內存釋放)

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,釋放到伙伴系統中時,需要考慮和伙伴的合並情況。
 
Copyright © Linux教程網 All Rights Reserved