linux 逆向映射機制淺析


2017-05-20

聚會回來一如既往的看了會羽毛球比賽,然后想到前幾天和朋友討論的逆向映射的問題,還是簡要總結下,免得以后再忘記了!可是當我添加時間……這就有點尷尬了……520還在寫技術博客……


 

閑話不多說,之前一個問題是想要根據物理頁框號得到映射的虛擬地址,一時間不知道如何下手了,在群里和一個朋友討論了一番,記得之前看swap機制的交換緩存時,記載說系統當要換出一個頁面時,可以很容易找到使用該頁面的所有進程,然后撤銷映射。這一點也就成了我的突破口。經過對源碼的一番研究結合相關書籍,便有了今天這篇文章。重點就是逆向映射機制。

顧名思義,有一個虛擬地址經過頁面轉換得到物理地址的過程為正向映射,那么根據物理地址推導虛擬地址呢?自然成了逆向映射。眾所周知,Linux下每個物理頁面對應一個page結構,物理頁框號可以很容易的轉化到page結構,不妨看下內核是怎么轉化的。

#define __pfn_to_page(pfn)    (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define __page_to_pfn(page)    ((unsigned long)((page) - mem_map)+ ARCH_PFN_OFFSET)

 

這里有點像windows 的pfn數據庫了,mem_map是一個page指針,作為pfn數據庫(實際上是一個大的數組的起始),ARCH_PFN_OFFSET是物理起始地址的pfn。所以差值實際就是有效pfn。通過page轉化成pfn也是同樣的思路。那么這和逆向映射什么關系呢?下面要說的就是至關重要的page結構,該結構比較龐大,我們只說和逆向映射有關系的部分。

page結構中有兩個字段:

struct page{
        struct address_space *mapping;
        union {
            pgoff_t index;        /* Our offset within mapping. */
            void *freelist;        /* slub/slob first free object */
            bool pfmemalloc;    /* If set by the page allocator,
                         * ALLOC_NO_WATERMARKS was set
                         * and the low watermark was not
                         * met implying that the system
                         * is under some pressure. The
                         * caller should try ensure
                         * this page is only used to
                         * free other pages.
                         */
        };
        struct {

                union {
                    /*
                     * Count of ptes mapped in
                     * mms, to show when page is
                     * mapped & limit reverse map
                     * searches.
                     *
                     * Used also for tail pages
                     * refcounting instead of
                     * _count. Tail pages cannot
                     * be mapped and keeping the
                     * tail page _count zero at
                     * all times guarantees
                     * get_page_unless_zero() will
                     * never succeed on tail
                     * pages.
                     */
                    atomic_t _mapcount;

                    struct { /* SLUB */
                        unsigned inuse:16;
                        unsigned objects:15;
                        unsigned frozen:1;
                    };
                    int units;    /* SLOB */
                };
                atomic_t _count;        /* Usage count, see below. */
            };
        };
    };

}              

 

其實這里想說的就三個字段,mapping,在映射匿名頁面的時候指向一個anon_vma結構,在映射文件頁面的時候指向inode節點的address-space;index,表示對應的虛擬頁面在vma中的線性索引;_mapcount,共享該頁面的進程的數目;注意該值默認是-1,當有一個進程使用時為0,所以其值表明除了當前進程還有多少進程在使用,便於撤銷。了解了這三個字段,接下來就好解釋多了。通過一個函數page_referenced來解釋。

int page_referenced(struct page *page, int is_locked,struct mem_cgroup *memcg, unsigned long *vm_flags)

 

原版解釋如下:Quick test_and_clear_referenced for all mappings to a page,returns the number of ptes which referenced the page.就是快速的檢查並清除一個頁面的所有引用(不同頁表當中),返回引用這個page頁面的pte數量。簡單走一下流程

int page_referenced(struct page *page,
            int is_locked,
            struct mem_cgroup *memcg,
            unsigned long *vm_flags)
{
    int referenced = 0;
    int we_locked = 0;

    *vm_flags = 0;
    if (page_mapped(page) && page_rmapping(page)) {
        if (!is_locked && (!PageAnon(page) || PageKsm(page))) {
            we_locked = trylock_page(page);
            if (!we_locked) {
                referenced++;
                goto out;
            }
        }
        if (unlikely(PageKsm(page)))
            referenced += page_referenced_ksm(page, memcg,
                                vm_flags);
        else if (PageAnon(page))
            referenced += page_referenced_anon(page, memcg,
                                vm_flags);
        else if (page->mapping)
            referenced += page_referenced_file(page, memcg,
                                vm_flags);
        if (we_locked)
            unlock_page(page);

        if (page_test_and_clear_young(page_to_pfn(page)))
            referenced++;
    }
out:
    return referenced;
}

 

首先檢查正向和逆向映射是否都存在,如果沒有鎖定該頁面並且頁面是KSM 頁面或者文件映射頁面,則需要trylock,如果加鎖失敗,則直接out.接下來就是對不同情況的處理。如果是KSM頁面走page_referenced_ksm。如果是匿名映射頁,走page_referenced_anon,如果是文件映射頁,走page_referenced_file。KSM是內核頁面共享的一種機制,主要用在KVM中,但是其他地方也可以引用,由於其需要計算頁面是否相同,所以在重復率不高的場合,大部分選擇關掉KSM,關於KSM在另一篇文章已經介紹。

如果是匿名映射頁面,進入page_referenced_anonstatic int page_referenced_anon(struct page *page,struct mem_cgroup *memcg,unsigned long *vm_flags)函數

static int page_referenced_anon(struct page *page,
                struct mem_cgroup *memcg,
                unsigned long *vm_flags)
{
    unsigned int mapcount;
    struct anon_vma *anon_vma;
    pgoff_t pgoff;
    struct anon_vma_chain *avc;
    int referenced = 0;

    anon_vma = page_lock_anon_vma_read(page);
    if (!anon_vma)
        return referenced;

    mapcount = page_mapcount(page);
    pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);
    anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff, pgoff) {
        struct vm_area_struct *vma = avc->vma;
        unsigned long address = vma_address(page, vma);
        /*
         * If we are reclaiming on behalf of a cgroup, skip
         * counting on behalf of references from different
         * cgroups
         */
        if (memcg && !mm_match_cgroup(vma->vm_mm, memcg))
            continue;
        referenced += page_referenced_one(page, vma, address,
                          &mapcount, vm_flags);
        if (!mapcount)
            break;
    }

    page_unlock_anon_vma_read(anon_vma);
    return referenced;
}

要查看頁面的訪問情況,肯定要定位到具體的PTE,而PTE只能根據虛擬地址查找頁表獲得,所以當務之急還是找到虛擬地址和頁表。這里首先獲得page對應的anon_vma,前面提到,在匿名映射情況下,page->mapping指向anon_vma結構。然后獲取了page的共享計數mapcount,獲取page對應的虛擬頁框在vma中對應的線性索引index,接下來就開始遍歷interval-tree了。每個anon_vma_chain關聯一個進程的vma,通過vma_address(page, vma)便可以獲取在當前vma對應的進程的虛擬地址。暫且忽略cgroup相關的內容。接下來調用page_referenced_one解除映射。前面已經提到,目前已經有了虛擬地址,有了vma,根據vma可以獲取對應的mm_struct,進而獲取頁基址,OK,流程走通了。該函數就不在列舉了,函數中有兩種情況,如果是大頁面(2M頁面),需要獲得是pmd;如果是普通頁面,需要獲取pte;之后檢查_PAGE_ACCESSED位。如果被設置,則清除,然后++引用計數器,否則,不變。所以經常訪問的頁面,引用計數器高,就更容易被定義成活躍頁面,常駐活躍LRU鏈表,就不容易被換出。

回顧下最初的問題,通過物理地址找到虛擬地址,在獲取了vma和index后,一個函數就解決問題,但是筆者這里有一個疑問,代碼顯示這里根據page結構中的index對所有的vma進行索引,這點令我很困惑,理論上將不能保證page映射的虛擬頁框在所有的vma中都是同樣的偏移吧?如果有知道的老師,還請告知!!

static inline unsigned long
__vma_address(struct page *page, struct vm_area_struct *vma)
{
    pgoff_t pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);

    if (unlikely(is_vm_hugetlb_page(vma)))
        pgoff = page->index << huge_page_order(page_hstate(page));

    return vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
}

 

代碼到這里就不需要多解釋了吧,關於anon_vma結構的組織,以后湊空在分析;

感謝主!

參考:

   linux 3.10.1源碼

《深入linux內核架構》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM