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內核架構》
