1. 程式人生 > >Linux記憶體管理之反向對映RMAP

Linux記憶體管理之反向對映RMAP

1. Linux反向對映有三個常用資料結構,可以簡稱AV, VMA,AVC

struct anon_vma {
    struct anon_vma *root;//指向紅黑樹最頂層AV,可以理解為祖宗程序的AV
    unsigned degree;//樹深度,表示是第幾代程序,也可以理解為在紅黑樹的層級
    struct anon_vma *parent;    //指向父程序AV
    struct rb_root rb_root;//所有子孫程序的AVC紅黑樹,有多少個子孫程序,就有多少個AVC通過rb插入到此紅黑樹
};

struct anon_vma_chain {
    struct vm_area_struct *vma;//指向same_vam鏈入的VMA,其實也就是當前程序的VMA
    struct anon_vma *anon_vma;//指向rb節點插入的AV
    struct list_head same_vma; //鏈入VMA的anon_vma_chain連結串列
    struct rb_node rb; //插入AV的rb_root紅黑樹.
};

一個VMA表示程序的一段虛擬地址空間.

struct vm_area_struct {

/*VMA的AVC連結串列,比如第三代程序(連續fork兩次),那麼就會有三個AVC在這個連結串列,表示三個程序有相同的VMA,

前兩個AVC通過rb結點依次插入父程序和父父程序AV的rb_root節點.而第三個AVC插入到當前程序AV的rb_root節點

*/
struct list_head anon_vma_chain;
struct anon_vma *anon_vma;  //VMA的AV,只有私有的匿名對映才有一個AV指標
}

結論:

  1. 假如程序有N個子孫程序,那麼VMA->AV的rb_root紅黑樹,就有N個AVC插入紅黑樹(包含自身的AVC)

  2.假如VMA的anon_vma_chain連結串列有N個AVC,那麼當前程序就屬於第N代程序(連續forkN次)

 

2.反向對映應用場景

 2.1 核心回收時,需要斷開page的所有對映

 2.2 頁面遷移時,也要斷開所有頁面的對映

 

3. 斷開對映處理流程

這裡只分析匿名對映unmap過程

try_to_unmap->rmap_walk->rmap_walk_anon

static int rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc)
{
    /*獲取page->maping的AV */
    anon_vma = rmap_walk_anon_lock(page, rwc);
    if (!anon_vma)
        return ret;
/*計算頁面在map中的偏移 */
    pgoff = page_to_pgoff(page);
/*遍歷AV的rb_root記錄著所有子程序的AVC */
    anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff, pgoff) {
        struct vm_area_struct *vma = avc->vma;/*取子程序的VMA */
        unsigned long address = vma_address(page, vma);

    /*這裡有直接呼叫rmap_one和done函式
    rmap_one: try_to_unmap_one :解除一個對映
        done:page_not_mapped 檢查page的mapcount是否大於0,

        */

        ret = rwc->rmap_one(page, vma, address, rwc->arg);
        if (ret != SWAP_AGAIN)
            break;
        /*如果mapcount為-1,則直接結束unmap流程 */
        if (rwc->done && rwc->done(page))
            break;
    }
    anon_vma_unlock_read(anon_vma);
    return ret;
}

主要做了三件事
1. 檢查address是否指向物理頁面page(PFN相同)
2. 重新設定page的pte頁表項
3. 減少page的mapcount和count計數
static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
             unsigned long address, void *arg)
{
    /*對比address和page是否指向同一個物理頁面,如果發生過COW,PTE內容肯定不一樣,這裡會返回NULL,如果沒有發生COW,這裡檢查通過 */
    pte = page_check_address(page, mm, address, &ptl, 0);
    if (!pte)
        goto out;
        /*重新設定頁表項 */
        swp_entry_t entry = { .val = page_private(page) };
        pte_t swp_pte;
        swp_pte = swp_entry_to_pte(entry);
        if (pte_soft_dirty(pteval))
            swp_pte = pte_swp_mksoft_dirty(swp_pte);
        set_pte_at(mm, address, pte, swp_pte);
    /*減少mapcount和count引用計數 */
    page_remove_rmap(page);
    page_cache_release(page);

    return ret;
}


4. mapcout計數

do_anonymous_page :缺頁異常

page->_mapcount //初始化為-1,第一次建立對映時設定為0(page_add_new_anon_rmap)


fork後
page->_mapcount++(page_dup_rmap),page->_count++(get_page),並設定PTE為防寫pte_wrprotect


發生了COW
對old page的mapcount--

unmap時
1.如果page還沒有發生COW,則會對page所有的對映進行unmap操作
try_to_unmap_one執行page->_mapcount--

2. COW發生後,page的mapcount相應的減少(page_remove_rmap)