由于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_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; } } |
==== |
阅读(774) | 评论(0) | 转发(0) |