1、為什么要使用反向映射
物理內(nèi)存的分頁機(jī)制,一個(gè)PTE(Page Table Entry)對(duì)應(yīng)一個(gè)物理頁,但一個(gè)物理頁可以由多個(gè)PTE與之相對(duì)應(yīng),當(dāng)該頁要被回收時(shí),Linux2.4的做法是遍歷每個(gè)進(jìn)程的所有PTE判斷該P(yáng)TE是否與該頁建立了映射,如果建立則取消該映射,最后無PTE與該相關(guān)聯(lián)后才回收該頁。該方法顯而易見效率極低,因?yàn)槠錇榱瞬檎夷硞€(gè)頁的關(guān)聯(lián)PTE遍歷了所有的PTE,我們不禁想:如果把每個(gè)頁關(guān)聯(lián)的PTE保存在頁結(jié)構(gòu)里面,每次只需要訪問那些與之相關(guān)聯(lián)的PTE不很方便嗎?確實(shí),2.4之后確實(shí)采用過此方法,為每個(gè)頁結(jié)構(gòu)(Page)維護(hù)一個(gè)鏈表,這樣確實(shí)節(jié)省了時(shí)間,但此鏈表所占用的空間及維護(hù)此鏈表的代價(jià)很大,在2.6中棄之不用,但反向映射機(jī)制的思想不過如此,所以還是有參考價(jià)值的,
2.6內(nèi)核新引入的反向映射
反向映射是2.6內(nèi)核中新引入的一個(gè)機(jī)制,主要是為了加速頁面置換的時(shí)候的效率,由于內(nèi)核中的頁面是不區(qū)分進(jìn)程的,多個(gè)進(jìn)程很有可能會(huì)共享一個(gè)頁面,內(nèi)核只管每個(gè)頁面必須和一個(gè)或者多個(gè)pte對(duì)應(yīng),反過來,每一個(gè)present位為1的pte必須和一個(gè)頁面相對(duì)應(yīng),這個(gè)反過來的對(duì)應(yīng)是個(gè)一一映射關(guān)系,但是前面的卻不然,也就是說頁面到pte的映射卻不是一一映射的關(guān)系,而在一個(gè)頁面將要被換出物理內(nèi)存的時(shí)候必須實(shí)時(shí)更新與之相關(guān)的各個(gè)pte,由此得出的問題就是必須掃描所有的進(jìn)程的所有的pte,只要找到pte所對(duì)應(yīng)的頁面是將要被換出的頁面就更新之,這樣效率未免太低下,為什么呢?因?yàn)轫撁姹粨Q出本應(yīng)該只涉及頁面和與該頁面相關(guān)的實(shí)體,如果為了找到這些所謂的相關(guān)實(shí)體而消耗大量的時(shí)間和空間資源,那么這必然是一個(gè)瓶頸,并且這個(gè)缺陷是一定可以彌補(bǔ)的,為什么可以彌補(bǔ)呢?因?yàn)槲覀冃枰龅膬H僅是記錄下和此頁面相關(guān)的實(shí)體就可以了,而不是通過遍歷尋找的方式,這樣可以濾去很多無關(guān)的查找,必然的一種可能是浪費(fèi)了空間來存儲(chǔ)額外的信息,帶來的優(yōu)惠就是節(jié)省了大量的時(shí)間,這就是反向映射的設(shè)計(jì)初衷,那么反向映射是怎么實(shí)現(xiàn)的呢?最簡(jiǎn)單的實(shí)現(xiàn)就是在page數(shù)據(jù)結(jié)構(gòu)中擴(kuò)展一個(gè)字段,實(shí)際上是一個(gè)鏈表,里面鏈接所有指向這個(gè)page的pte,換出該頁面的時(shí)候遍歷這個(gè)鏈表就會(huì)得到所有的需要更新的pte,這也是2.6的早期版本中使用的方式:
struct page {
...//別的字段就此略過
union {
struct pte_chain *chain;
pte_addr_t direct;
} pte; //這個(gè)聯(lián)合式新增的
...
};
struct pte_chain {
unsigned long next_and_idx;
pte_addr_t ptes[NRPTE]; //一切為了效率,采用了緩存對(duì)齊的方式可以最小化緩存缺失
} ____cacheline_aligned;
我已經(jīng)不能用除了藝術(shù)之外的其他詞匯來形容以下這個(gè)函數(shù)了,當(dāng)然linux內(nèi)核不管藝術(shù)不藝術(shù),最終美麗的代碼終成泡影,換來的是高效,linux就是這樣,下面的page_add_rmap函數(shù)好在巧妙的使用了pte_chain結(jié)構(gòu):
struct pte_chain * page_add_rmap(struct page *page, pte_t *ptep, struct pte_chain *pte_chain)
{
pte_addr_t pte_paddr = ptep_to_paddr(ptep); //得到pte的地址
struct pte_chain *cur_pte_chain;
if (!pfn_valid(page_to_pfn(page)) || PageReserved(page))
return pte_chain;
pte_chain_lock(page);
if (page->pte.direct == 0) { //新分配的頁面肯定只有自己擁有pte指向,新分配的page的pte.direct肯定為0
page->pte.direct = pte_paddr; //下一次該page的pte.direct字段就不為0了
SetPageDirect(page); //設(shè)置Direct標(biāo)志
inc_page_state(nr_mapped);
goto out; //第一次分配的頁面不需要什么反向映射,直接返回,實(shí)際上第一次分配這個(gè)page時(shí),待這個(gè)函數(shù)返回后,pte_chain將會(huì)被釋放掉,因?yàn)樗鼪]有用
}
if (PageDirect(page)) { //如果第二次該page被引用,那么就需要反向映射了,第二次引用該頁面時(shí)一共有兩個(gè)pte引用之,第一個(gè)就是該頁面剛剛被分配時(shí)的page->pte.direc,第二個(gè)就是當(dāng)前調(diào)用的pte_paddr
ClearPageDirect(page); //清楚掉Direct標(biāo)志,表明它開始使用反向映射了
pte_chain->ptes[NRPTE-1] = page->pte.direct; //從后向前設(shè)置
pte_chain->ptes[NRPTE-2] = pte_paddr;
pte_chain->next_and_idx = pte_chain_encode(NULL, NRPTE-2);
page->pte.direct = 0; //這里設(shè)置為0豈不是下次又要到上面的if (page->pte.direct == 0)里面去了,哈哈,注意page的pte是個(gè)聯(lián)合體而不是結(jié)構(gòu)體
page->pte.chain = pte_chain; //這個(gè)設(shè)置將使得下次上面的if (page->pte.direct == 0)通不過!
pte_chain = NULL; /* We consumed it */
goto out;
}
cur_pte_chain = page->pte.chain; //如果該page第三次被引用,那么就要從這里開始了
if (cur_pte_chain->ptes[0]) { //已經(jīng)到了第一個(gè),說明一個(gè)pte_chain已經(jīng)滿了,因?yàn)楦鱾€(gè)pte是從pte_chain的后面向前面推進(jìn)的
pte_chain->next_and_idx = pte_chain_encode(cur_pte_chain, NRPTE - 1);
page->pte.chain = pte_chain; //下次將使用新的pte_chain
pte_chain->ptes[NRPTE-1] = pte_paddr;
pte_chain = NULL; //將pte_chain設(shè)置為NULL,目的是在外面不被釋放,因?yàn)槲覀円呀?jīng)使用了
goto out;
}
cur_pte_chain->ptes[pte_chain_idx(cur_pte_chain) - 1] = pte_paddr;
cur_pte_chain->next_and_idx--; //向前推進(jìn)
out:
pte_chain_unlock(page);
return pte_chain; //如果沒有使用參數(shù)傳進(jìn)來的pte_chain,那么返回它,調(diào)用者負(fù)責(zé)釋放它,只要返回一個(gè)非NULL的pte_chain就說明傳進(jìn)來的pte_chain沒有被使用,外面的調(diào)用這需要釋放之,這是本著誰申請(qǐng)誰釋放這一基本的編程原則來的
}
上面的函數(shù)其實(shí)一點(diǎn)也不復(fù)雜,只要仔細(xì)閱讀一定可以理解的,看完了上面的add,那么這個(gè)函數(shù)所做的一切在什么地方使用呢?答案當(dāng)然是在unmap的時(shí)候,那么看一下try_to_unmap吧:
int try_to_unmap(struct page * page)
{
struct pte_chain *pc, *next_pc, *start;
int ret = SWAP_SUCCESS;
int victim_i = -1;
...
if (PageDirect(page)) { //如果是第一個(gè)頁面,那么說明只有一個(gè)引用,更新之即可
ret = try_to_unmap_one(page, page->pte.direct);
if (ret == SWAP_SUCCESS) {
page->pte.direct = 0;
ClearPageDirect(page);
}
goto out;
}
start = page->pte.chain; //否則就需要遍歷pte.chain了
for (pc = start; pc; pc = next_pc) { //遍歷所有的pte_chain
int i;
next_pc = pte_chain_next(pc);
if (next_pc)
prefetch(next_pc);
for (i = pte_chain_idx(pc); i < NRPTE; i++) { //遍歷一個(gè)pte_chain數(shù)組的ptes
pte_addr_t pte_paddr = pc->ptes[i]; //這樣就找到了一個(gè)pte
...
switch (try_to_unmap_one(page, pte_paddr)) {
...//結(jié)果碼處理
}
}
}
...
}
如果linux和微軟一樣,那么代碼就到此為止了,事實(shí)證明這樣已經(jīng)很不錯(cuò)了,是的,代碼優(yōu)美,效率又高,一切都不錯(cuò),但是linux開發(fā)中沒有最好只有更好,所有的物理內(nèi)存都有page結(jié)構(gòu)與之對(duì)應(yīng),每個(gè)page結(jié)構(gòu)中保存一個(gè)pte聯(lián)合實(shí)在不是什么明智之舉,畢竟很多page根本就不需要pte反向映射,比如內(nèi)核使用的page以及很多只有一個(gè)進(jìn)程使用的匿名頁面,那么就必須想一個(gè)辦法,一個(gè)懶惰的辦法將這個(gè)反向映射的相關(guān)信息保存到一個(gè)用戶空間使用的結(jié)構(gòu)體之內(nèi),就是說只有在使用反向映射的實(shí)體中才保存反向映射信息,否則不保存,這樣算法的時(shí)間復(fù)雜度不變,同時(shí)可以節(jié)省更多的空間,這樣一來2.6后來的內(nèi)核中就廢棄了以上的優(yōu)雅方式,使用了一種更加高效的方法,將反向映射信息保存到vm_area_struct結(jié)構(gòu)中,因?yàn)橹挥杏脩艨臻g的頁面才會(huì)有反向映射,而vm_area_struct是只有用戶空間進(jìn)程才有的數(shù)據(jù)結(jié)構(gòu)
void page_add_anon_rmap(struct page *page, struct vm_area_struct *vma, unsigned long address)
{
struct anon_vma *anon_vma = vma->anon_vma; //這個(gè)anon_vma是一定要有的,如果在fork的時(shí)候有兩個(gè)vma公用了一個(gè)page,那么page顯然影響了兩個(gè)pte,這兩個(gè)pte可以通過這兩個(gè)vma得到
pgoff_t index;
anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;
index = (address - vma->vm_start) >> PAGE_SHIFT;
index += vma->vm_pgoff;
index >>= PAGE_CACHE_SHIFT - PAGE_SHIFT;
if (atomic_inc_and_test(&page->_mapcount)) {
page->index = index;
page->mapping = (struct address_space *) anon_vma; //2.6的稍微后期的版本中巧妙使用page的mapping字段存儲(chǔ)了反向映射的信息,當(dāng)然光有page的字段不行,必須要有有個(gè)地方將pte鏈接在一起才行,這個(gè)結(jié)構(gòu)就是上面的anon_vma,這個(gè)anon_vma是vma中多出來的字段,可能浪費(fèi)了一些空間,但是說實(shí)話vma中頁面在物理內(nèi)存的數(shù)量與page的數(shù)量相比還是要少啊,因此相比前一個(gè)解決方案還是節(jié)省了空間。
inc_page_state(nr_mapped);
}
}
2.6后期的方案利用了mapping的低位沒有用的特征從而使用了這些位,利用了一切可以利用的空間,并且這個(gè)方案將匿名反向映射和文件緩存反向映射分離,在文件反向映射中使用優(yōu)先級(jí)樹高效處理,相比前一個(gè)早期的版本性能提高了不少。2.6的后期版本中的反向映射解決方案的資料是比較多的,我就不多說了,但是早期的反向映射的資料比較少,因此本文就分析了代碼。本文主要想表達(dá)的意思就是linux的后期版本的性能基本都比以前的高,不管它的代碼的可讀性有多糟糕,其實(shí)閱讀linux代碼和理解代碼的關(guān)鍵就是理解作者的設(shè)計(jì)思想,最好的辦法就是看changelog,只要理解了changelog就可以理解作者的意圖,讀懂了代碼才可以修改代碼,才可以添加自己的邏輯,開發(fā)自己的內(nèi)核。
2、Linux2.6中是如何實(shí)現(xiàn)反向映射
(以下代碼均來自內(nèi)核版本2.6.11.)
2.1 與RM(Reverse Mapping)相關(guān)的結(jié)構(gòu)
page, address_space, vm_area_struct, mm_struct, anon_vma.
以下均顯示部分成員:
struct page{
struct address_space *mapping; /* address_space類型,為對(duì)齊需要,其值為4的位數(shù),所以最低兩位無用,為充分利用資源,所以此處利用此最低位。
* 最低位為1表示該頁為匿名頁,并且它指向anon_vma對(duì)象。
* 最低為0表映射頁,此時(shí)mapping指向文件節(jié)點(diǎn)地址空間。
*/
atomic_t _mapcount; /* 取值-1時(shí)表示沒有指向該頁框的引用,
取值0時(shí)表示該頁框不可共享
取值大于0時(shí)表示該頁框可共享表示有幾個(gè)PTE引用
*/
pgoff_t index;
};
?
struct mm_struct {
pgd_t * pgd;
}
?
truct vm_area_struct {
struct list_head anon_vma_node; /* Serialized by anon_vma->lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
}
?
struct anon_vma {
spinlock_t lock; /* Serialize access to vma list */
struct list_head head; /* List of private "related" vmas */
};
2.2 進(jìn)程地址空間
?
?
每個(gè)進(jìn)程有個(gè)進(jìn)程描述符task_struct,其中有mm域指向該進(jìn)程的內(nèi)存描述符mm_struct。
每個(gè)進(jìn)程都擁有一個(gè)內(nèi)存描述符,其中有PGD域,指向該進(jìn)程地址空間的全局頁目錄;mmap域指向第一個(gè)內(nèi)存區(qū)域描述符vm_area_strut1。
進(jìn)程通過內(nèi)存區(qū)域描述符vm_area_struct管理內(nèi)存區(qū)域,每個(gè)內(nèi)存區(qū)域描述符都有vm_start和vm_end域指向該內(nèi)存區(qū)域的在虛擬內(nèi)存中的起始位置;vm_mm域指向該進(jìn)程的內(nèi)存描述符;每個(gè)vm_area_struct都有一個(gè)anon_vma域指向該進(jìn)程的anon_vma;
每個(gè)進(jìn)程都有一個(gè)anon_vma,是用于鏈接所有vm_area_struct的頭結(jié)點(diǎn),通過vm_area_struct的anon_vma_node構(gòu)成雙循環(huán)鏈表。
最終形成了上圖。
?
現(xiàn)在假設(shè)我們要回收一個(gè)頁,我們要做的是訪問所有與該頁相關(guān)聯(lián)的PTE并修改之取消二者之間的關(guān)聯(lián)。與之相關(guān)聯(lián)的函數(shù)為:try_to_unmap。
2.3 try_to_unmap
2.3.1 try_to_unmap函數(shù)及PageOn宏 分析
int try_to_unmap(struct page *page)
{
int ret;
BUG_ON(PageReserved(page));
BUG_ON(!PageLocked(page));
/*判斷是不是匿名頁,若是的話執(zhí)行try_to_unmap_anon函數(shù),否則的話執(zhí)行try_to_unmap_file函數(shù)*/
if (PageAnon(page)) // PageAnon函數(shù)分析在下面
ret = try_to_unmap_anon(page);
else
ret = try_to_unmap_file(page);
if (!page_mapped(page))
ret = SWAP_SUCCESS;
return ret;
}
static inline int PageAnon(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
/* #define PAGE_MAPPING_ANON 1 此函數(shù)非常easy,就是判斷page的mapping成員的末位是不是1,是的話返回1,不是的話返回0*/
}
2.3.2 try_to_unmap_anon函數(shù)及page_lock_anon_vma函數(shù)及l(fā)ist_for_each_entry宏 分析
還沒開始看文件系統(tǒng)一節(jié),所以try_to_unmap_file沒看懂,所以此處只分析 try_to_unmap_anon函數(shù),等看完vfs后再來補(bǔ)充吧。
static int try_to_unmap_anon(struct page *page)
{
struct anon_vma *anon_vma;
struct vm_area_struct *vma;
int ret = SWAP_AGAIN;
anon_vma = page_lock_anon_vma(page); /* 獲取該匿名頁的anon_vma結(jié)構(gòu)
* page_lock_anon_vma函數(shù)分析在下面。
*/
if (!anon_vma)
return ret;
list_for_each_entry(vma, &anon_vma->head, anon_vma_node) { /* 循環(huán)遍歷
* list_for_each_entry分析在下面
* anon_vma就是上圖中anon_vma的指針,anon_vma->head得到其head成員(是list_head)類型,
* 其next值便對(duì)應(yīng)上圖中vm_area_struct1中的anon_vma_node中的head。
* vma 是vm_area_struct類型的指針,anon_vma_node為typeof(*vma)即vm_area_struct中的成員。
* 到此便可以開始雙鏈表循環(huán)
*/
ret = try_to_unmap_one(page, vma); // 不管是調(diào)用try_to_unmap_anon還是try_to_unmap_file最終還是回到try_to_unmap_one上
if (ret == SWAP_FAIL || !page_mapped(page))
break;
}
spin_unlock(&anon_vma->lock);
return ret;
}
static struct anon_vma *page_lock_anon_vma(struct page *page)
{
struct anon_vma *anon_vma = NULL;
unsigned long anon_mapping;
rcu_read_lock();
anon_mapping = (unsigned long) page->mapping;
if (!(anon_mapping & PAGE_MAPPING_ANON))
goto out;
if (!page_mapped(page))
goto out;
// 前面已經(jīng)提到,mapping最低位為1時(shí)表匿名頁,此時(shí)mapping是指向anon_vma指針,故此處-1后強(qiáng)制轉(zhuǎn)化為struct anon_vma指針類型,并返回該值。
anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
spin_lock(&anon_vma->lock);
out:
rcu_read_unlock();
return anon_vma;
}
/* 參數(shù)含義:
* head是list_head指針,無非此處需要的第一個(gè)list_head是head->next
* pos是個(gè)指向包含list_head的結(jié)構(gòu)體的指針,可以用typeof(*pos)解引用來得到此結(jié)構(gòu)體
* member 是list_head在typeof(*pos)中的名稱
* 這樣pos = list_entry((head)->next, typeof(*pos), member)第一次便初始化pos為指向包含head->next指向的那個(gè)結(jié)構(gòu)體的指針。
* 之后便是雙循環(huán)鏈表遍歷了
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \ // list_entry分析在下面
prefetch(pos->member.next), &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/* list_entry函數(shù)其實(shí)非常簡(jiǎn)單,其各參數(shù)的意義:
* ptr 指向list_head類型的指針
* type 包含list_head類型的結(jié)構(gòu)體
* member list_head在type中的名稱
* 返回值:包含ptr指向的list_head的類型為type的指針,即由list_head指針得到包含此list_head結(jié)構(gòu)體的指針,實(shí)現(xiàn)也很簡(jiǎn)單,ptr減去member在type中的偏移即可
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
2.3.3 try_to_unmap_one函數(shù)及vma_address函數(shù)及pdg_offset宏 分析
?
?
Linux采用三級(jí)頁表:
PGD:頂級(jí)頁表,由pgd_t項(xiàng)組成的數(shù)組,其中第一項(xiàng)指向一個(gè)二級(jí)頁表。
PMD:二級(jí)頁表,由pmd_t項(xiàng)組成的數(shù)組,其中第一項(xiàng)指向一個(gè)三級(jí)頁表(兩級(jí)處理器沒有物理的PMD)。
PTE:是一個(gè)頁對(duì)齊的數(shù)組,第一項(xiàng)稱為一個(gè)頁表項(xiàng),由pte_t類型表示。一個(gè)pte_t包含了數(shù)據(jù)頁的物理地址。
static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma)
{
struct mm_struct *mm = vma->vm_mm;
unsigned long address;
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
pte_t pteval;
int ret = SWAP_AGAIN;
if (!mm->rss)
goto out;
address = vma_address(page, vma); /* 通過頁和vma得到線性地址
* vm_address函數(shù)解析在下面
*/
if (address == -EFAULT)
goto out;
/*
* We need the page_table_lock to protect us from page faults,
* munmap, fork, etc...
*/
spin_lock(&mm->page_table_lock); // 頁表鎖
pgd = pgd_offset(mm, address); /* 獲得pdg
* pdg_offset通過內(nèi)存描述符和線性地址得到pgd
* 該函數(shù)解析在下面
*/
if (!pgd_present(*pgd))
goto out_unlock;
pud = pud_offset(pgd, address); /* 獲得pud
i386上應(yīng)該是0吧?
*/
if (!pud_present(*pud))
goto out_unlock;
pmd = pmd_offset(pud, address); /* 獲得pmd */
if (!pmd_present(*pmd))
goto out_unlock;
pte = pte_offset_map(pmd, address); /* 獲得pte */
if (!pte_present(*pte))
goto out_unmap;
/* 有了pgd pmd pte 后我們便達(dá)到我們目的了 ===> 查找與頁相關(guān)聯(lián)系的頁表項(xiàng),找到后便可以進(jìn)行修改了(如果是要換出該頁的話則應(yīng)該解除映射pte_unmap()函數(shù))
* 但修改之前還要做些判斷和處理
*/
//
if (page_to_pfn(page) != pte_pfn(*pte))
goto out_unmap;
/*
* If the page is mlock()d, we cannot swap it out.
* If it's recently referenced (perhaps page_referenced
* skipped over this mm) then we should reactivate it.
*/
if ((vma->vm_flags & (VM_LOCKED|VM_RESERVED)) ||
ptep_clear_flush_young(vma, address, pte)) {
ret = SWAP_FAIL;
goto out_unmap;
}
/*
* Don't pull an anonymous page out from under get_user_pages.
* GUP carefully breaks COW and raises page count (while holding
* page_table_lock, as we have here) to make sure that the page
* cannot be freed. If we unmap that page here, a user write
* access to the virtual address will bring back the page, but
* its raised count will (ironically) be taken to mean it's not
* an exclusive swap page, do_wp_page will replace it by a copy
* page, and the user never get to see the data GUP was holding
* the original page for.
*
* This test is also useful for when swapoff (unuse_process) has
* to drop page lock: its reference to the page stops existing
* ptes from being unmapped, so swapoff can make progress.
*/
if (PageSwapCache(page) &&
page_count(page) != page_mapcount(page) + 2) {
ret = SWAP_FAIL;
goto out_unmap;
}
/* Nuke the page table entry. */
flush_cache_page(vma, address);
pteval = ptep_clear_flush(vma, address, pte);
/* Move the dirty bit to the physical page now the pte is gone. */
if (pte_dirty(pteval))
set_page_dirty(page);
if (PageAnon(page)) {
swp_entry_t entry = { .val = page->private };
/*
* Store the swap location in the pte.
* See handle_pte_fault() ...
*/
BUG_ON(!PageSwapCache(page));
swap_duplicate(entry);
if (list_empty(&mm->mmlist)) {
spin_lock(&mmlist_lock);
list_add(&mm->mmlist, &init_mm.mmlist);
spin_unlock(&mmlist_lock);
}
set_pte(pte, swp_entry_to_pte(entry));
BUG_ON(pte_file(*pte));
mm->anon_rss--;
}
mm->rss--;
acct_update_integrals();
page_remove_rmap(page);
page_cache_release(page);
out_unmap:
pte_unmap(pte);
out_unlock:
spin_unlock(&mm->page_table_lock);
out:
return ret;
}
static inline unsigned long
vma_address(struct page *page, struct vm_area_struct *vma)
{
pgoff_t pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT); /* PAGE_CACHE_SHIFT - PAGE_SHIFT值為0,其實(shí)就是把page->index賦給pgoff
* 至于為什么要這樣右移一下,我也不清楚
*/
unsigned long address;
address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT); /* page->index是頁的偏移
* vma->vm_pgoff是內(nèi)存區(qū)域首地址的偏移,都是以頁為單位
* 相減后再vm_start || address >= vma->vm_end)) { /* 得到的地址應(yīng)該在vm->vm_start和vm_end之間,否則報(bào)錯(cuò) */
/* page should be within any vma from prio_tree_next */
BUG_ON(!PageAnon(page));
return -EFAULT;
}
return address;
}
#define PGDIR_SHIFT 22 // 在i386機(jī)子上線性地址0-11位表PTE,12-21表PMD,22-31位表PGD,即線性地址右移22位的結(jié)果為其在全局頁目錄的偏移
#define PTRS_PER_PGD 1024 // 因PGD共10位,所以其最多可以有2^10=1024個(gè)全局描述符項(xiàng)
#define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1)) // 得到線性地址address在全局頁目錄里面的偏移
#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address)) // 再加上全局描述符基地址(存儲(chǔ)在內(nèi)存描述符mm_struct中的pdg域)后便得到其在全局描述符中的具體位置便得到頁在內(nèi)存區(qū)域的中的偏移>便得到頁在內(nèi)存區(qū)域中的地址>
?
?
?
評(píng)論
查看更多