由于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;
   }
}

 

page_address()函数分析--如何通过page取得虚拟地址的更多相关文章

  1. page_address()函数分析

    由于X86平台上面,内存是划分为低端内存和高端内存的,所以在两个区域内的page查找对应的虚拟地址是不一样的. 一. x86上关于page_address()函数的定义 在include/linux/ ...

  2. Linux-0.11内核源代码分析系列:内存管理get_free_page()函数分析

    Linux-0.11内存管理模块是源码中比較难以理解的部分,如今把笔者个人的理解发表 先发Linux-0.11内核内存管理get_free_page()函数分析 有时间再写其它函数或者文件的:) /* ...

  3. 如何验证一个地址可否使用—— MmIsAddressValid函数分析

    又是一篇内核函数分析的博文,我个人觉得Windows的内核是最好的老师,当你想实现一个功能之前可以看看Windows内核是怎么做的,说不定就有灵感呢:) 首先看下官方的注释说明: /*++ Routi ...

  4. 网页细分图结果分析(Web Page Diagnostics)

    Discuz开源论坛网页细分图结果分析(Web Page Diagnostics) 续LR实战之Discuz开源论坛项目,之前一直是创建虚拟用户脚本(Virtual User Generator)和场 ...

  5. split(),preg_split()与explode()函数分析与介

    split(),preg_split()与explode()函数分析与介 发布时间:2013-06-01 18:32:45   来源:尔玉毕业设计   评论:0 点击:965 split()函数可以实 ...

  6. string函数分析

    string函数分析string函数包含在string.c文件中,经常被C文件使用.1. strcpy函数原型: char* strcpy(char* str1,char* str2);函数功能: 把 ...

  7. start_amboot()函数分析

    一.整体流程 start_amboot()函数是执行完start.S汇编文件后第一个C语言函数,完成的功能自然还是初始化的工作 . 1.全局变量指针r8设定,以及全局变量区清零 2.执行一些类初始化函 ...

  8. uboot的jumptable_init函数分析

    一.函数说明 函数功能:安装系统函数指针 函数位置:common/exports.c 二.函数分析 void jumptable_init (void) { int i; gd->jt = (v ...

  9. 31.QPainter-rotate()函数分析-文字旋转不倾斜,图片旋转实现等待

    在上章和上上上章: 28.QT-QPainter介绍 30.QT-渐变之QLinearGradient. QConicalGradient.QRadialGradient 学习了QPainter基础绘 ...

随机推荐

  1. MySQL5.5登陆

    通过cmd登陆 mysql -h localhost -P 3306 -u root -p123456 h后面跟的是域名或IP地址:大写的P后面跟的是端口号:u后面跟的是用户名:小写的p后面跟的是密码 ...

  2. MongoDB for Java

    开发环境 操作系统:Windows7 IDE: MyEclipse Database: MongoDB 开发依赖库 bson-3.0.1.jar mongodb-driver-3.0.1.jar mo ...

  3. PHP进阶知识总结

    周末梳理了下这段时间看书的一些知识点,进步的过程不仅要实践,还要安排多看书.思考.总结. 只针对知识点进行了罗列和简单说明,很多细节还未整理好,待后面再专门详细写. 基础易忽略概念 PHP是一个支持面 ...

  4. 网络I/O虚拟化,SR-IOV技术

    1.简介 网络I/O虚拟化是服务器虚拟化技术的重要组成部分,在服务器虚拟化技术领域,计算虚拟化(如CPU和内存虚拟化)已经日趋成熟,但是,网络I/O虚拟化技术的发展相对比较滞后.当前,主流的网络I/O ...

  5. .net core系列之《.net core中使用MySql以及Dapper》

    当我们决定使用.Net Core开发的时候,就放弃使用SqlServer的打算吧.那应该选择哪个数据库呢?一般选择MySql的比较多. 接下来我们来演示在.Net Core中使用MySQL吧. 1.原 ...

  6. Java笔记-添加自定义公共类库

    大型项目,为了方便团队开发,需要建立公共类库,提高类库的重用性和维护性步骤如下: --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/387 ...

  7. awk单行脚本快速参考

    AWK单行脚本快速参考 2008年4月28日编辑: Eric Pement eric [at] pement.org 版本 0.26翻译: 董一粟 yisudong [at] gmail.com 最新 ...

  8. JavaScript的本地对象、内置对象、宿主对象

    首先解释下宿主环境:一般宿主环境由外壳程序创建与维护,只要能提供js引擎执行的环境都可称之为外壳程序.如:web浏览器,一些桌面应用系统等.即由web浏览器或是这些桌面应用系统早就的环境即宿主环境. ...

  9. netstat 常用方法

    netstat简介 netstat是一个监控TCP/IP网络的非常有用的工具,它可以显示路由表,实际的网络连接以及每一个网络接口设备的状态信息,netstat用于显示与IP,TCP,UDP和ICMP协 ...

  10. 使用Visual Studio Code开发Arduino

    首发于MSPrecious成长荟 https://zhuanlan.zhihu.com/p/30868224 使用Visual Studio Code开发Arduino 1.下载安装 VS Code ...