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平台上是有定义的
 

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



转自:http://blog.chinaunix.net/space.php?uid=21752164&do=blog&id=2689670

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
void eeprom_buffer_write(uint8_t* p_buffer, uint8_t write_address, uint16_t number_of_byte) { uint8_t number_of_page = 0, number_of_single = 0, address = 0, count = 0; address = write_address % I2C_PAGE_SIZE; count = I2C_PAGE_SIZE - address; number_of_page = number_of_byte / I2C_PAGE_SIZE; number_of_single = number_of_byte % I2C_PAGE_SIZE; //write_address相对于当前页的偏移量 //count表示该页中还剩余多少可写的空间 //number_of_page表示要写入的整个页数(即不包括最后一页) //number_of_single表示要写入的最后一页的字节数 if(0 == address){ while(number_of_page--){ eeprom_page_write(p_buffer, write_address, I2C_PAGE_SIZE); eeprom_wait_standby_state(); write_address += I2C_PAGE_SIZE; p_buffer += I2C_PAGE_SIZE; } if(0 != number_of_single){ eeprom_page_write(p_buffer, write_address, number_of_single); eeprom_wait_standby_state(); } }else{ /* if write_address is not I2C_PAGE_SIZE aligned / // if(number_of_byte < count){ eeprom_page_write(p_buffer, write_address, number_of_byte); eeprom_wait_standby_state(); }else{ number_of_byte -= count; number_of_page = number_of_byte / I2C_PAGE_SIZE; number_of_single = number_of_byte % I2C_PAGE_SIZE; if(0 != count){ eeprom_page_write(p_buffer, write_address, count); eeprom_wait_standby_state(); write_address += count; p_buffer += count; } / write page / while(number_of_page--){ eeprom_page_write(p_buffer, write_address, I2C_PAGE_SIZE); eeprom_wait_standby_state(); write_address += I2C_PAGE_SIZE; p_buffer += I2C_PAGE_SIZE; } / write single */ if(0 != number_of_single){ eeprom_page_write(p_buffer, write_address, number_of_single); eeprom_wait_standby_state(); } } } }详细解释这段的代码的每一个语句
06-10
这段代码是用于在EEPROM中写入数据的函数。下面对每个语句进行详细解释: 1. uint8_t number_of_page = 0, number_of_single = 0, address = 0, count = 0; 定义4个变量,分别为要写入的整页数、最后一页的字节数、写入地址的偏移量和当前页可写入的字节数。 2. address = write_address % I2C_PAGE_SIZE; 求出写入地址相对于当前页的偏移量。 3. count = I2C_PAGE_SIZE - address; 计算当前页中还剩余多少可写的空间。 4. number_of_page = number_of_byte / I2C_PAGE_SIZE; 计算要写入的整个页数(即不包括最后一页)。 5. number_of_single = number_of_byte % I2C_PAGE_SIZE; 计算要写入的最后一页的字节数。 6. if(0 == address){ 如果写入地址已经对齐到一页的起始地址,执行以下的循环。 7. while(number_of_page--){ 循环写入整页数据。 8. eeprom_page_write(p_buffer, write_address, I2C_PAGE_SIZE); 写入一个整页的数据到EEPROM中。 9. eeprom_wait_standby_state(); 等待EEPROM进入就绪状态。 10. write_address += I2C_PAGE_SIZE; 将写入地址指向下一页的起始地址。 11. p_buffer += I2C_PAGE_SIZE; 将数据指针移动到下一页的起始地址。 12. if(0 != number_of_single){ 如果还有剩余的字节需要写入,执行以下代码。 13. eeprom_page_write(p_buffer, write_address, number_of_single); 写入最后一页的数据到EEPROM中。 14. eeprom_wait_standby_state(); 等待EEPROM进入就绪状态。 15. }else{ 如果没有剩余的字节需要写入,直接结束函数。 16. }else{ 如果写入地址没有对齐到一页的起始地址,执行以下代码。 17. if(number_of_byte < count){ 如果要写入的字节数小于当前页可写的字节数,执行以下代码。 18. eeprom_page_write(p_buffer, write_address, number_of_byte); 直接写入所有字节到当前页中。 19. eeprom_wait_standby_state(); 等待EEPROM进入就绪状态。 20. }else{ 如果要写入的字节数大于当前页可写的字节数,执行以下代码。 21. number_of_byte -= count; 将当前页可写的字节数从要写入的字节数中减去。 22. number_of_page = number_of_byte / I2C_PAGE_SIZE; 计算要写入的整个页数(即不包括最后一页)。 23. number_of_single = number_of_byte % I2C_PAGE_SIZE; 计算要写入的最后一页的字节数。 24. if(0 != count){ 如果当前页还有剩余的空间可写,执行以下代码。 25. eeprom_page_write(p_buffer, write_address, count); 将当前页剩余的空间全部写入。 26. eeprom_wait_standby_state(); 等待EEPROM进入就绪状态。 27. write_address += count; 将写入地址指向下一页的起始地址。 28. p_buffer += count; 将数据指针移动到下一页的起始地址。 29. } 30. while(number_of_page--){ 循环写入整页数据。 31. eeprom_page_write(p_buffer, write_address, I2C_PAGE_SIZE); 写入一个整页的数据到EEPROM中。 32. eeprom_wait_standby_state(); 等待EEPROM进入就绪状态。 33. write_address += I2C_PAGE_SIZE; 将写入地址指向下一页的起始地址。 34. p_buffer += I2C_PAGE_SIZE; 将数据指针移动到下一页的起始地址。 35. } 36. if(0 != number_of_single){ 如果还有剩余的字节需要写入,执行以下代码。 37. eeprom_page_write(p_buffer, write_address, number_of_single); 写入最后一页的数据到EEPROM中。 38. eeprom_wait_standby_state(); 等待EEPROM进入就绪状态。 39. } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值