linux記憶體管理(八)- 反向對映RMAP

半山随笔發表於2024-06-15

這裡有一篇部落格講的不錯。linux記憶體管理筆記(三十八)----反向對映_linux 反向對映-CSDN部落格

頁表是把虛擬地址對映到物理頁面,但是如何根據一個物理頁找到所有對映它的pte呢?答案是用反向對映Reverse Mapping(RMAP)。這在頁面回收中很有用。回收頁面需要將到物理頁的對映斷開(改一下pte),前提是找到所有對映的pte,那就必須在page結構中留下線索,這就是mapping欄位。

在講page結構時我們提到過mapping可以指向匿名頁的anon_vma結構,這個結構是反向對映的關鍵之一。

struct anon_vma {
    struct anon_vma *root;        /* Root of this anon_vma tree */
    struct rw_semaphore rwsem;    /* W: modification, R: walking the list */
    atomic_t refcount;
    unsigned long num_children;
    /* Count of VMAs whose ->anon_vma pointer points to this object. */
    unsigned long num_active_vmas;
    struct anon_vma *parent;    /* Parent of this anon_vma */
    struct rb_root_cached rb_root;  //avc結構會連結到這棵rbtree上
};

除了anon_vma(AV)還有一個AVC(anon_vma_chain)結構。

/*
 * The copy-on-write semantics of fork mean that an anon_vma
 * can become associated with multiple processes. Furthermore,
 * each child process will have its own anon_vma, where new
 * pages for that process are instantiated.
 *
 * This structure allows us to find the anon_vmas associated
 * with a VMA, or the VMAs associated with an anon_vma.
 * The "same_vma" list contains the anon_vma_chains linking
 * all the anon_vmas associated with this VMA.
 * The "rb" field indexes on an interval tree the anon_vma_chains
 * which link all the VMAs associated with this anon_vma.
 */
struct anon_vma_chain {
    struct vm_area_struct *vma;
    struct anon_vma *anon_vma;
    struct list_head same_vma;   /* locked by mmap_lock & page_table_lock */
    struct rb_node rb;            /* locked by anon_vma->rwsem */
    unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
    unsigned long cached_vma_start, cached_vma_last;
#endif
};

看註釋可知AVC可以透過rb_node連結到anon_vma的rbtree上,這個rbtree會連結所有與AV相關的VMA。也可以透過same_vma連結串列連結所有與該VMA相關的AV。AVC是anon_vma和vma的樞紐,可以讓三者相互找到另外兩方。

上圖是一個簡單的情形,描述三者之間的關係,這對理解後面反向對映的應用很重要。

反向對映的應用。

使用反向對映最常見的是在回收頁面,try_to_unmap是其重要函式。

void try_to_unmap(struct folio *folio, enum ttu_flags flags)
{
    struct rmap_walk_control rwc = {
        .rmap_one = try_to_unmap_one,
        .arg = (void *)flags,
        .done = folio_not_mapped,
        .anon_lock = folio_lock_anon_vma_read,
    };

    if (flags & TTU_RMAP_LOCKED)
        rmap_walk_locked(folio, &rwc);
    else
        rmap_walk(folio, &rwc);
}

rmap_walk

void rmap_walk(struct folio *folio, struct rmap_walk_control *rwc)
{
    if (unlikely(folio_test_ksm(folio)))
        rmap_walk_ksm(folio, rwc);
    else if (folio_test_anon(folio))
        rmap_walk_anon(folio, rwc, false);
    else
        rmap_walk_file(folio, rwc, false);
}

只看rmap_walk_anon

static void rmap_walk_anon(struct folio *folio,
        struct rmap_walk_control *rwc, bool locked)
{
    struct anon_vma *anon_vma;
    pgoff_t pgoff_start, pgoff_end;
    struct anon_vma_chain *avc;

    if (locked) {
        anon_vma = folio_anon_vma(folio);
        /* anon_vma disappear under us? */
        VM_BUG_ON_FOLIO(!anon_vma, folio);
    } else {
//從folio中獲取anon_vma anon_vma
= rmap_walk_anon_lock(folio, rwc); } if (!anon_vma) return; //page->index是page在file或記憶體區域中的偏移,單位是page pgoff_start = folio_pgoff(folio);
//得到複合頁的末尾index pgoff_end
= pgoff_start + folio_nr_pages(folio) - 1;
//遍歷avc anon_vma_interval_tree_foreach(avc,
&anon_vma->rb_root, pgoff_start, pgoff_end) { struct vm_area_struct *vma = avc->vma;
//得到page的虛擬地址 unsigned
long address = vma_address(&folio->page, vma); VM_BUG_ON_VMA(address == -EFAULT, vma); cond_resched(); if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg)) continue; //使用回撥函式rmap_one斷開虛擬地址對應的pte if (!rwc->rmap_one(folio, vma, address, rwc->arg)) break; if (rwc->done && rwc->done(folio)) break; } if (!locked) anon_vma_unlock_read(anon_vma); }

rmap_walk_anon遍歷anon_vma對應的rbtree,找到所有對映該頁的vma,然後斷開物理頁對應虛擬地址的pte。

這裡要理解page->index和vma->pgoff和vma->vm_start的關係。

由上圖可以看到page->index指的是在整個檔案或者程序地址空間(不一定是vma)上的偏移,要想得到它在當前vma內的偏移公式是vma->vm_start + (page->index - vma->vm_pgoff) << PAGE_SHIFT.

相關文章