linux 逆向映射機制淺析

2017-05-20node

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


 

閒話很少說,以前一個問題是想要根據物理頁框號獲得映射的虛擬地址,一時間不知道如何下手了,在羣裏和一個朋友討論了一番,記得以前看swap機制的交換緩存時,記載說系統當要換出一個頁面時,能夠很容易找到使用該頁面的全部進程,而後撤銷映射。這一點也就成了個人突破口。通過對源碼的一番研究結合相關書籍,便有了今天這篇文章。重點就是逆向映射機制。數據庫

顧名思義,有一個虛擬地址通過頁面轉換獲得物理地址的過程爲正向映射,那麼根據物理地址推導虛擬地址呢?天然成了逆向映射。衆所周知,Linux下每一個物理頁面對應一個page結構,物理頁框號能夠很容易的轉化到page結構,不妨看下內核是怎麼轉化的。windows

#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數量。簡單走一下流程app

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在另外一篇文章已經介紹。less

若是是匿名映射頁面,進入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內核架構》

相關文章
相關標籤/搜索