您的位置:首页 > 其它

page_address()函数分析--如何通过page取得虚拟地址

2010-10-21 10:49 495 查看
由于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;
//pagevoid
*
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;}}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: