天天看點

page_address()函數分析--如何通過page取得虛拟位址

由于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) &amp;&amp; !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)-&gt;virtual)</code>

<code>    #define set_page_address(page, address) \\     do { \\     (page)-&gt;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) &amp;&amp; !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) &lt;&lt; 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&lt;&lt;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: &amp;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(&amp;pas-&gt;lock, flags);</code>

<code>if (!list_empty(&amp;pas-&gt;lh)) {</code>

<code>struct page_address_map *pam;</code>

<code>list_for_each_entry(pam, &amp;pas-&gt;lh, list) {</code>

<code>if (pam-&gt;page == page) {</code>

<code>ret = pam-&gt;virtual;</code>

<code>goto done;</code>

<code>}</code><code></code>

<code>}</code>

<code>done:</code>

<code>spin_unlock_irqrestore(&amp;pas-&gt;lock, flags);</code>

<code>return ret;</code>

在高端記憶體中,由于不能通過像在低端記憶體中一樣,直接通過實體位址加PAGE_OFFSET得到線性位址,是以引入了一個結構叫做 page_address_map結構,該結構儲存有每個page(僅高端記憶體中的)和對應的虛拟位址,所有的高端記憶體中的這種映射都通過連結清單連結起來,這個結構是在高端記憶體映射的時候建立,并加入到連結清單中的。

<code>/* * Describes one page-&gt;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&lt;&lt;PA_HASH_ORDER];</code>

PA_HASH_ORDER=7, 是以一共有1&lt;&lt;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 &amp;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 &gt;&gt; (32 - bits); }</code>

這樣pas = page_slot(page)執行過後,pas就指向該page對應的page_address_map結構所在的連結清單的表頭。

然後再周遊這個連結清單,就可以找到對應的線性位址(如果存在的話),否則就傳回NULL

<code>list_for_each_entry(pam, &amp;pas-&gt;lh, list) {    if (pam-&gt;page == page) {       ret = pam-&gt;virtual;       goto done;    } }</code>

繼續閱讀