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