由於X86平台上面,內存是划分為低端內存和高端內存的,所以在兩個區域內的page查找對應的虛擬地址是不一樣的。
一. x86上關於page_address()函數的定義
在include/linux/mm.h里面,有對page_address()函數的三種宏定義,主要依賴於不同的平台:
首先來看看幾個宏的定義:
CONFIG_HIGHMEM:顧名思義,就是是否支持高端內存,可以查看config文件,一般推薦內存超過896M的時候,才配置為支持高端內存。
WANT_PAGE_VIRTUAL:X86平台是沒有定義的。
所以下面的HASHED_PAGE_VIRTUAL在支持高端內存的i386平台上是有定義的
|
1.//所以這里是假的,page_address()在i386上不是在這里定義的
#define page_address(page) ((page)->virtual)
|
2.//在沒有配置CONFIG_HIGHMEM的i386平台上,page_address是在這里定義的
|
3.//所以支持高端內存的i386平台上,page_address()是在這里定義的
|
二. 在低端內存中的page對應的page_address()的實現
在沒有配置CONFIG_HIGHMEM的i386平台上,page_address()是等同於lowmem_page_address
():
|
我們知道,在小於896M(低端內存)的物理地址空間和3G--3G+896M的線性地址空間是一一對應映射的,所以我們只要知道page所對應的物理地址,就可以知道這個page對應的線性地址空間(pa+PAGE_OFFSET)。
那如何找一個page對應的物理地址呢?我們知道物理內存按照大小為(1<<PAGE_SHIFT)分為很多個頁,每個這樣的頁就對應一個struct page * page結構,這些頁描述結構存放在一個稱之為mem_map的數組里面,而且是嚴格按照物理內存的順序來存放的,也就是物理上的第一個頁描述結構,作為mem_map數組的第一個元素,依次類推。所以,每個頁描述結構(page)在數組mem_map里的位置在乘以頁的大小,就可以得到該頁的物理地址了。上面的代碼就是依照這個原理來的:
page_to_pfn(page)函數就是得到每個page在mem_map里的位置,左移PAGE_SHIFT就是乘以頁的大小,這就得到了該頁的物理地址。這個物理地址加上個PAGE_OFFSET(3G)就得到了該page的線性地址了
在低端內存中(小於896M),通過頁(struct page * page)取得虛擬地址就是這樣轉換的。
三. 在高端內存中的page對應的page_address()的實現:
在有配置CONFIG_HIGHMEM的i386平台上,page_address是在mm/highmem.c里面實現的:
unsigned long flags;
void *ret;
struct page_address_slot *pas;
if (!PageHighMem(page)) //判斷是否屬於高端內存,如果不是,那么就是屬於低
存的,通過上面的方法可以直接找到
return lowmem_page_address(page);
pas = page_slot(page); //見下分析,pas指向page對應的page_address_map結構所在的鏈表表頭
ret = NULL;
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;
list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
|
在高端內存中,由於不能通過像在低端內存中一樣,直接通過物理地址加PAGE_OFFSET得到線性地址,所以引入了一個結構叫做 page_address_map結構,該結構保存有每個page(僅高端內存中的)和對應的虛擬地址,所有的高端內存中的這種映射都通過鏈表鏈接起來,這個結構是在高端內存映射的時候建立,並加入到鏈表中的。
|
又因為如果內存遠遠大於896M,那么高端內存中的page就比較多((內存-896M)/4K個頁,假設頁大小為4K),如果只用一個鏈表來表示,那么查找起來就比較耗時了,所以這里引入了HASH算法,采用多個鏈表,每個page通過一定的hash算法,對應到一個鏈表上,總夠有128個鏈表:
|
PA_HASH_ORDER=7, 所以一共有1<<7(128)個鏈表,每一個page通過HASH算法后對應一個 page_address_htable鏈表, 然后再遍歷這個鏈表來找到對應的PAGE和虛擬地址。
page通過HASH算法后對應一個 page_address_htable鏈表的代碼如下:
|
hash_ptr(val, bits)函數在32位的機器上是一個很簡單的hash算法,就是把val乘一個黃金值 GOLDEN_RATIO_PRIME_32,在把得到的結果(32位)取高 bits位 (這里就是7位)作為哈希表的索引
|
這樣pas = page_slot(page)執行過后,pas就指向該page對應的page_address_map結構所在的鏈表的表頭。
然后再遍歷這個鏈表,就可以找到對應的線性地址(如果存在的話),否則就返回NULL
list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}