由于X86平台上面,記憶體是劃分為低端記憶體和高端記憶體的,是以在兩個區域内的page查找對應的虛拟位址是不一樣的。
一. x86上關于page_address()函數的定義
在include/linux/mm.h裡面,有對page_address()函數的三種宏定義,主要依賴于不同的平台:
首先來看看幾個宏的定義:
CONFIG_HIGHMEM:顧名思義,就是是否支援高端記憶體,可以檢視config檔案,一般推薦記憶體超過896M的時候,才配置為支援高端記憶體。
WANT_PAGE_VIRTUAL:X86平台是沒有定義的。
是以下面的HASHED_PAGE_VIRTUAL在支援高端記憶體的i386平台上是有定義的
<code>#if defined(CONFIG_HIGHMEM) && !defined(WANT_PAGE_VIRTUAL) #define HASHED_PAGE_VIRTUAL #endif</code>
1.//是以這裡是假的,page_address()在i386上不是在這裡定義的
<code></code>
<code> #if defined(WANT_PAGE_VIRTUAL)</code>
<code> #define page_address(page) ((page)->virtual)</code>
<code> #define set_page_address(page, address) \\ do { \\ (page)->virtual = (address); \\ } while(0) #define page_address_init() do { } while(0) #endif</code>
2.//在沒有配置CONFIG_HIGHMEM的i386平台上,page_address是在這裡定義的
<code>#if !defined(HASHED_PAGE_VIRTUAL) && !defined(WANT_PAGE_VIRTUAL) #define page_address(page) lowmem_page_address(page) #define set_page_address(page, address) do { } while(0) #define page_address_init() do { } while(0) #endif</code>
3.//是以支援高端記憶體的i386平台上,page_address()是在這裡定義的
<code>#if defined(HASHED_PAGE_VIRTUAL)</code>
<code> void *page_address(struct page *page);</code>
<code> void set_page_address(struct page *page, void *virtual);</code>
<code> void page_address_init(void);</code>
<code> #endif</code>
二. 在低端記憶體中的page對應的page_address()的實作
在沒有配置CONFIG_HIGHMEM的i386平台上,page_address()是等同于<code>lowmem_page_address</code>():
<code>#define page_address(page) lowmem_page_address(page) static __always_inline void *lowmem_page_address(struct page *page) { return __va(page_to_pfn(page) << PAGE_SHIFT); } #define page_to_pfn(page) ((unsigned long)((page) - mem_map) + \ ARCH_PFN_OFFSET)</code>
<code> </code>
<code>#define __va(x) ((void *)((unsigned long)(x) + PAGE_OFFSET))</code>
我們知道,在小于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裡面實作的:
<code>/** * page_address - get the mapped virtual address of a page * @page: &struct page to get the virtual address of * * Returns the page\'s virtual address. */ void *page_address(struct page *page) {</code>
<code>unsigned long flags;</code>
<code>void *ret;</code>
<code>struct page_address_slot *pas;</code>
<code>if (!PageHighMem(page)) //判斷是否屬于高端記憶體,如果不是,那麼就是屬于低 端内</code><code>存的,通過上面的方法可以直接找到</code>
<code> return lowmem_page_address(page);</code>
<code>pas = page_slot(page); //見下分析,pas指向page對應的page_address_map結構所在的連結清單表頭</code>
<code>ret = NULL;</code>
<code>spin_lock_irqsave(&pas->lock, flags);</code>
<code>if (!list_empty(&pas->lh)) {</code>
<code>struct page_address_map *pam;</code>
<code>list_for_each_entry(pam, &pas->lh, list) {</code>
<code>if (pam->page == page) {</code>
<code>ret = pam->virtual;</code>
<code>goto done;</code>
<code>}</code><code></code>
<code>}</code>
<code>done:</code>
<code>spin_unlock_irqrestore(&pas->lock, flags);</code>
<code>return ret;</code>
在高端記憶體中,由于不能通過像在低端記憶體中一樣,直接通過實體位址加PAGE_OFFSET得到線性位址,是以引入了一個結構叫做 page_address_map結構,該結構儲存有每個page(僅高端記憶體中的)和對應的虛拟位址,所有的高端記憶體中的這種映射都通過連結清單連結起來,這個結構是在高端記憶體映射的時候建立,并加入到連結清單中的。
<code>/* * Describes one page->virtual association */ struct page_address_map { struct page *page; //page void *virtual; //虛拟位址 struct list_head list; //指向下一個該結構 };</code>
又因為如果記憶體遠遠大于896M,那麼高端記憶體中的page就比較多((記憶體-896M)/4K個頁,假設頁大小為4K),如果隻用一個連結清單來表示,那麼查找起來就比較耗時了,是以這裡引入了HASH算法,采用多個連結清單,每個page通過一定的hash算法,對應到一個連結清單上,總夠有128個連結清單:
<code>/* * Hash table bucket */ static struct page_address_slot { struct list_head lh; // List of page_address_maps 指向一個 </code>
<code> //page_address_map結構 連結清單 spinlock_t lock; /* Protect this bucket\'s list */ }page_address_htable[1<<PA_HASH_ORDER];</code>
PA_HASH_ORDER=7, 是以一共有1<<7(128)個連結清單,每一個page通過HASH算法後對應一個 page_address_htable連結清單, 然後再周遊這個連結清單來找到對應的PAGE和虛拟位址。
page通過HASH算法後對應一個 page_address_htable連結清單的代碼如下:
<code>static struct page_address_slot *page_slot(struct page *page) { return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)]; }</code>
hash_ptr(val, bits)函數在32位的機器上是一個很簡單的hash算法,就是把val乘一個黃金值 GOLDEN_RATIO_PRIME_32,在把得到的結果(32位)取高 bits位 (這裡就是7位)作為哈希表的索引
<code>static inline u32 hash_32(u32 val, unsigned int bits) { /* On some cpus multiply is faster, on others gcc will do shifts */ u32 hash = val * GOLDEN_RATIO_PRIME_32; /* High bits are more random, so use them. */ return hash >> (32 - bits); }</code>
這樣pas = page_slot(page)執行過後,pas就指向該page對應的page_address_map結構所在的連結清單的表頭。
然後再周遊這個連結清單,就可以找到對應的線性位址(如果存在的話),否則就傳回NULL
<code>list_for_each_entry(pam, &pas->lh, list) { if (pam->page == page) { ret = pam->virtual; goto done; } }</code>