page_address()函数分析--如何通过page取得虚拟地址
来源:互联网 发布:nba凯尔开人球队数据 编辑:程序博客网 时间:2024/05/17 23:13
一. 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] ;
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;
}
}
转自:http://blog.csdn.net/liujun01203/article/details/5932783
- page_address()函数分析--如何通过page取得虚拟地址
- page_address()函数分析--如何通过page取得虚拟地址
- page_address()函数分析--如何通过page取得虚拟地址
- page_address()函数分析--如何通过page取得虚拟地址
- page_address()函数分析--如何通过page取得虚拟地址
- page_address()函数分析--如何通过page取得虚拟地址
- 汇编取得函数地址
- Java通过命令行取得MAC地址
- Java通过命令行取得MAC地址
- Java通过命令行取得MAC地址
- 如何通过函数名获取虚函数的地址?
- 虚拟化及地址空间转换(page translation table)
- 如何取得客户段的ip地址
- 如何取得客户端的IP地址
- 如何取得结构变量的偏移地址
- 如何通过 PHP 取得 MySQL procedure 结果
- Wxpython如何通过控件ID取得控件
- 如何通过 模糊搜索,取得完整关键词!
- android 屏幕适配原则
- undefined与null的区别
- Android:资源 id 及资源 id 的动态获取
- 一个 wsgi的 python log
- jQgrid插件根据一列,控制另一列的输出
- page_address()函数分析--如何通过page取得虚拟地址
- CAS SSO SSL证书配置
- log4cxx配置案例
- 小说以刻画人物形象为中心
- 《声声慢·寻寻觅觅》 李清照
- projecteuler---->problem=28----Number spiral diagonals
- 解决bash: mysql: command not found 的方法
- quazip
- 九月十月百度人搜,阿里巴巴,腾讯华为笔试面试八十题(第331-410题)