page_address()函數分析


由於X86平台上面,內存是划分為低端內存和高端內存的,所以在兩個區域內的page查找對應的虛擬地址是不一樣的。

一. x86上關於page_address()函數的定義


在include/linux/mm.h里面,有對page_address()函數的三種宏定義,主要依賴於不同的平台:

首先來看看幾個宏的定義:
CONFIG_HIGHMEM:顧名思義,就是是否支持高端內存,可以查看config文件,一般推薦內存超過896M的時候,才配置為支持高端內存。
WANT_PAGE_VIRTUAL:X86平台是沒有定義的。
所以下面的HASHED_PAGE_VIRTUAL在支持高端內存的i386平台上是有定義的

#if defined(CONFIG_HIGHMEM) && !defined(WANT_PAGE_VIRTUAL)
#define HASHED_PAGE_VIRTUAL
#endif


1.//所以這里是假的,page_address()在i386上不是在這里定義的


    #if defined(WANT_PAGE_VIRTUAL)

    #define page_address(page) ((page)->virtual)

    #define set_page_address(page, address) \\
    do { \\
    (page)->virtual = (address); \\
    } while(0)
    #define page_address_init() do { } while(0)
    #endif


2.//在沒有配置CONFIG_HIGHMEM的i386平台上,page_address是在這里定義的

#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


3.//所以支持高端內存的i386平台上,page_address()是在這里定義的

#if defined(HASHED_PAGE_VIRTUAL)

    void *page_address(struct page *page);

    void set_page_address(struct page *page, void *virtual);

    void page_address_init(void);

    #endif

二. 在低端內存中的page對應的page_address()的實現
在沒有配置CONFIG_HIGHMEM的i386平台上,page_address()是等同於lowmem_page_address():

#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)

 

#define __va(x) ((void *)((unsigned long)(x) + PAGE_OFFSET))


我們知道,在小於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里面實現的:


/**
* 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)
{

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(僅高端內存中的)和對應的虛擬地址,所有的高端內存中的這種映射都通過鏈表鏈接起來,這個結構是在高端內存映射的時候建立,並加入到鏈表中的。

/*
* Describes one page->virtual association
*/
struct page_address_map {
struct page *page; //page
void *virtual; //虛擬地址
struct list_head list; //指向下一個該結構
};


又因為如果內存遠遠大於896M,那么高端內存中的page就比較多((內存-896M)/4K個頁,假設頁大小為4K),如果只用一個鏈表來表示,那么查找起來就比較耗時了,所以這里引入了HASH算法,采用多個鏈表,每個page通過一定的hash算法,對應到一個鏈表上,總夠有128個鏈表:

/*
* Hash table bucket
*/
static struct page_address_slot {
struct list_head lh; // List of page_address_maps 指向一個  

                     //page_address_map結構 鏈表
spinlock_t lock; /* Protect this bucket\'s list */ 
}page_address_htable[1<<PA_HASH_ORDER];

PA_HASH_ORDER=7, 所以一共有1<<7(128)個鏈表,每一個page通過HASH算法后對應一個 page_address_htable鏈表, 然后再遍歷這個鏈表來找到對應的PAGE和虛擬地址。
page通過HASH算法后對應一個 page_address_htable鏈表的代碼如下:

static struct page_address_slot *page_slot(struct page *page)
{
return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}


hash_ptr(val, bits)函數在32位的機器上是一個很簡單的hash算法,就是把val乘一個黃金值 GOLDEN_RATIO_PRIME_32,在把得到的結果(32位)取高 bits位 (這里就是7位)作為哈希表的索引

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);
}


這樣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;
   }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM