在Linux中,内核的页面映射机制分为三层,页面目录和页面表中间设有一个“中间目录”。中间目录是为了对64位CPU兼容而设计的。在内核代码中,页面目录称为PGD,中间目录称为PMD,页面表成为PT, PT中的表项称为PTE, 是“page table entry的缩写”。

一个进程的线性地址从高位到低位划分为4个段,各占若干位,分别作用为目录PGD中的下标,中间目录PMD的下标、页面表的下标以及物理页面内的位移。因此,给定一个线性地址,利用其前面三个段,就可以得到这个线性地址对应的pte。本文将分别阐述如何在X86以及龙芯2f下通过线性地址来得到对应的pte。

我们都知道,每一个进程都有一个task_struct结构,这个结构也就是我们通常所说的PCB。linux内核提供一个指针current,该指针指向当前进程的task_struct结构。通过current指针我们能得到当前进程的mm_struct结构。mm_struct结构是进程整个用户空间的一个抽象。通过进程的mm_struct我们就可以得到进程的pgd,然后按照线性地址的组成,我们就可以一步一步的得到线性地址对应的pte。

我的实验是基于这么一个框架完成的:写一个模块,模块注册一个设备,我们可以通过write系统调用向这个设备中写一个线性地址,模块会通过printk打印出对应的pte。

让我们现来分析下从32位的x86下如何得到线性地址的pte。在32为的x86下,线性地址为32bit,PGD位段大小为10,也就是说线性地址前10位表示目录项在页面目录中的下标,通过这个下标就能得到对应的目录项。因为i386结构物理上是二层映射,所以linux把PMD的位段大小设为0,也就是把PMD给mask掉了。目录项逻辑上指向一个大小为1的中间目录PMD,但是物理上直接指向相应的页面表(PT),i386的内存管理单元并不知道PMD的存在。PT的位段大小也为10,以PT位段为下标就能得到相应的表项PTE。

在实际的操作中,内核提供了一些函数来简化这些取位的操作,函数将在代码中做简单的介绍。这里主要介绍取pte的方法和思路,对模块实现的细节也不做解释。

X86下实现该模块的代码如下:

#include

#include

#include

#include

#include

#include

//#include

#include

#include

MODULE_LICENSE("Dual BSD/GPL");

/*这个函数的目的是把一个字符串类型的线性地址转化成整型,可以忽略这个函数的实现过程*/

int convert_vaddr(char *buff, int count){

//该函数的输入是一个字符串和字符串长度,如"8048000, 8",输出是一个int型整数,如0x8048000

//具体形式在下面给出

}

/*模块的主要函数,该函数输入一个线性地址,然后将该地址对应的pte打印出来*/

int event_write(struct file *filp, char __user *buff, size_t count,

loff_t *f_ops){

struct mm_struct *mm;

pgd_t *pgd;

pmd_t *pmd;

pud_t *pud;

pte_t *pte;

char buff_k[count];

int vaddr;

unsigned long pa;

if(copy_from_user(buff_k,buff,count)) //将线性地址从用户空间拷贝到内核空间

return -EFAULT;

if((vaddr = convert_vaddr(buff_k,count))==-EINVAL) //将字符串类型的线性地址转换成int型

return -EINVAL;

mm = current->mm; //获得当前进程的mm_struct

pgd = pgd_offset(mm, vaddr); //pgd_offset函数通过mm_struct和线性地址得到该地址对应的页面目录项

pud = pud_offset(pgd,vaddr); //pud是page uper directory,是pgd和pmd之间的一层映射,在i386的两级映射中不起作用

pmd = pmd_offset(pud,vaddr); //这里使用pud_offset和pmd_offset仅讲pgd的值类型转换后传承下来。

pte = pte_offset_kernel(pmd, vaddr); //pte_offset_kernel 根据线性地址和pmd,找到该线性地址对应的页表项。

pa = pte_val(*pte) ; //得到pte的内容

printk("current process's pte is:%x\n",pa);

printk("$\n");

return 0;

}

struct cdev event;

struct file_operations event_ops;

//init function

static int init_event(void){

//register the device event

if(register_chrdev_region(MKDEV(33,0),1,"readpte")){

printk("register error!!\n");

return -EFAULT;

}

//register operations

event_ops.write = event_write;

cdev_init(&event, &event_ops);

cdev_add(&event,MKDEV(33,0),1);

return 0;

}

//clean function

static void exit_event(void){

//unregister device

cdev_del(&event);

unregister_chrdev_region(MKDEV(33,0),1);

}

module_init(init_event);

//module_init(init_event);

module_exit(exit_event);

#include

#include

#include

#include

#include

#include

//#include

#include

#include

MODULE_LICENSE("Dual BSD/GPL");

/*这个函数的目的是把一个字符串类型的线性地址转化成整型,可以忽略这个函数的实现过程*/

int convert_vaddr(char *buff, int count){

//该函数的输入是一个字符串和字符串长度,如"8048000, 8",输出是一个int型整数,如0x8048000

//具体形式在下面给出

}

/*模块的主要函数,该函数输入一个线性地址,然后将该地址对应的pte打印出来*/

int event_write(struct file *filp, char __user *buff, size_t count,

loff_t *f_ops){

struct mm_struct *mm;

pgd_t *pgd;

pmd_t *pmd;

pud_t *pud;

pte_t *pte;

char buff_k[count];

int vaddr;

unsigned long pa;

if(copy_from_user(buff_k,buff,count)) //将线性地址从用户空间拷贝到内核空间

return -EFAULT;

if((vaddr = convert_vaddr(buff_k,count))==-EINVAL) //将字符串类型的线性地址转换成int型

return -EINVAL;

mm = current->mm; //获得当前进程的mm_struct

pgd = pgd_offset(mm, vaddr); //pgd_offset函数通过mm_struct和线性地址得到该地址对应的页面目录项

pud = pud_offset(pgd,vaddr); //pud是page uper directory,是pgd和pmd之间的一层映射,在i386的两级映射中不起作用

pmd = pmd_offset(pud,vaddr); //这里使用pud_offset和pmd_offset仅讲pgd的值类型转换后传承下来。

pte = pte_offset_kernel(pmd, vaddr); //pte_offset_kernel 根据线性地址和pmd,找到该线性地址对应的页表项。

pa = pte_val(*pte) ; //得到pte的内容

printk("current process's pte is:%x\n",pa);

printk("$\n");

return 0;

}

struct cdev event;

struct file_operations event_ops;

//init function

static int init_event(void){

//register the device event

if(register_chrdev_region(MKDEV(33,0),1,"readpte")){

printk("register error!!\n");

return -EFAULT;

}

//register operations

event_ops.write = event_write;

cdev_init(&event, &event_ops);

cdev_add(&event,MKDEV(33,0),1);

return 0;

}

//clean function

static void exit_event(void){

//unregister device

cdev_del(&event);

unregister_chrdev_region(MKDEV(33,0),1);

}

module_init(init_event);

//module_init(init_event);

module_exit(exit_event);

为了方便大家使用代码,保证代码完整性,下面给出convert_vaddr函数的代码:

int convert_vaddr(char *buff, int count){

int i;

int ret=0;

for (i = 0;i

//printk("loop i is:%d, buff[i] is %c\n",i, buff[i]);

switch (buff[i]){

case '0':

break;

case '1':

ret = ret+(1<

break;

case '2':

ret = ret+(2<

break;

case '3':

ret = ret+(3<

break;

case '4':

ret = ret+(4<

break;

case '5':

ret = ret+(5<

break;

case '6':

ret = ret+(6<

break;

case '7':

ret = ret+(7<

break;

case '8':

ret = ret+(8<

break;

case '9':

ret = ret+(9<

break;

case 'a':

ret = ret+(0xa<

break;

case 'b':

ret = ret+(0xb<

break;

case 'c':

ret = ret+(0xc<

break;

case 'd':

ret = ret+(0xd<

break;

case 'e':

ret = ret+(0xe<

break;

case 'f':

ret = ret+(0xf<

break;

default:

printk("error vaddr!!\n");

return -EINVAL;

}

}

return ret;

}

int convert_vaddr(char *buff, int count){

int i;

int ret=0;

for (i = 0;i

//printk("loop i is:%d, buff[i] is %c\n",i, buff[i]);

switch (buff[i]){

case '0':

break;

case '1':

ret = ret+(1<

break;

case '2':

ret = ret+(2<

break;

case '3':

ret = ret+(3<

break;

case '4':

ret = ret+(4<

break;

case '5':

ret = ret+(5<

break;

case '6':

ret = ret+(6<

break;

case '7':

ret = ret+(7<

break;

case '8':

ret = ret+(8<

break;

case '9':

ret = ret+(9<

break;

case 'a':

ret = ret+(0xa<

break;

case 'b':

ret = ret+(0xb<

break;

case 'c':

ret = ret+(0xc<

break;

case 'd':

ret = ret+(0xd<

break;

case 'e':

ret = ret+(0xe<

break;

case 'f':

ret = ret+(0xf<

break;

default:

printk("error vaddr!!\n");

return -EINVAL;

}

}

return ret;

}

在龙芯2F下获得PTE的基本方法大致跟x86一样,但是也有一些区别,比如龙芯2F下PMD的位段不是0,因此PMD是有效的。

下面给出龙芯2F下获得PTE的模块代码,这次将主要通过对线性地址的位操作来实现。注意龙芯的页大小为16k,虚拟地址空间是64位,并且兼容32位,我们这里之考虑32位地址。隐藏一切无关的细节,我们需要注意的是,在32位的地址空间中,龙芯线性地址pgd位段大小为0,PMD位段大小为7,PTE位段大小为11。

为节约篇幅,将只给出主要功能函数event_write代码,将这个函数替换上面x86代码下的同名函数就能得到一个完整的代码。

int event_write(struct file *filp, char __user *buff, size_t count,

loff_t *f_ops){

struct mm_struct *mm;

pgd_t *pgd;

pmd_t *pmd;

pte_t *pte;

char buff_k[count];

int vaddr;

int pmd_entry, pte_entry;

if(copy_from_user(buff_k,buff,count))

return -EFAULT;

if((vaddr = convert_vaddr(buff_k,count))==-EINVAL)

return -EINVAL;

mm = current->mm;

pgd = mm->pgd; //pgd此时指向页面目录的首地址,因为pgd的位段大小为0,所以页面目录只有一项,指向pmd

pmd=(pmd_t*)(pgd->pgd); //pmd指向中间目录首地址

pmd_entry = (vaddr>>25)&(0x7f); //取线性地址的高7位,计算其在中间目录中的下标

pmd = pmd+pmd_entry; //得到中间目录项

pte = (pte_t *)(((pmd->;pmd)>>14)<<14); //中间目录项的前18位指向页面表,后面加上14个0后就是该页面表的地址

//这里pte就指向页面表的首地址

pte_entry = (((vaddr & 0x1ffc000)>>14) & 0x7ff); //取线性地址的中间11位,计算其在页面表中的下标

pte = pte+pte_entry; //得到页面表项,即PTE

printk("current process's pte is:%x\n",(unsigned int)(pte->pte)); //输出页面表项的值

printk("$\n");

return 0;

}

int event_write(struct file *filp, char __user *buff, size_t count,

loff_t *f_ops){

struct mm_struct *mm;

pgd_t *pgd;

pmd_t *pmd;

pte_t *pte;

char buff_k[count];

int vaddr;

int pmd_entry, pte_entry;

if(copy_from_user(buff_k,buff,count))

return -EFAULT;

if((vaddr = convert_vaddr(buff_k,count))==-EINVAL)

return -EINVAL;

mm = current->mm;

pgd = mm->pgd; //pgd此时指向页面目录的首地址,因为pgd的位段大小为0,所以页面目录只有一项,指向pmd

pmd=(pmd_t*)(pgd->pgd); //pmd指向中间目录首地址

pmd_entry = (vaddr>>25)&(0x7f); //取线性地址的高7位,计算其在中间目录中的下标

pmd = pmd+pmd_entry; //得到中间目录项

pte = (pte_t *)(((pmd->;pmd)>>14)<<14); //中间目录项的前18位指向页面表,后面加上14个0后就是该页面表的地址

//这里pte就指向页面表的首地址

pte_entry = (((vaddr & 0x1ffc000)>>14) & 0x7ff); //取线性地址的中间11位,计算其在页面表中的下标

pte = pte+pte_entry; //得到页面表项,即PTE

printk("current process's pte is:%x\n",(unsigned int)(pte->pte)); //输出页面表项的值

printk("$\n");

return 0;

}

anyShare分享到:

linux 内核参数 pte,Linux下通过线性地址得到页表项pte(X86和龙芯2F下)相关推荐

  1. 开机时设置linux 内核参数 mem,Linux内核开机保留大块内存的方法总结

    在网上搜了很久,才慢慢了解在开机保留内存的方法,现在总结一下这阶段的学习过程!(我是在ARM板子上进行的实验,内核版本是2.6.38) 在开机保留内存的方式一共有三种方法: 1. reserve_bo ...

  2. linux 内核参数 rss,Linux控制内存的内核参数

    环境 Red Hat Enterprise Linux (RHEL) 5.x (X86) 在 X86 高内存设备中,当用户进程使用 mlock() 在常规区域分配大量内存时,可重新使用的 lowmem ...

  3. linux内核参数分析,linux内核启动第一阶段分析

    linux内核启动第一阶段分析 http://blog.csdn.net/aaronychen/article/details/2838341 本文的很多内容是参考了网上某位大侠的文章写的<&l ...

  4. linux 内核参数 杨,Linux 内核参数

    牢记!内核参数可以调整,但不是随便乱调,需要根据业务进行判断,并且要知道调整的后果是什么,存在哪些风险. 牢记!!!调整参数时,做好记录!!! 网络参数 /proc/sys/net/core/wmem ...

  5. linux 内核参数 杨,Linux 内核参数

    1. net.core.somaxconn freebsd版本中对应的参数为 kern.ipc.somaxconn 作用:限制接收新 TCP 连接侦听队列的大小 关注点:nginx debug:acc ...

  6. linux 内核参数分析,linux 内核参数VM调优 之 参数调节和场景分析

    总结可知cached中的脏数据满足如下几个条件中一个或者多个的时候就会被pdflush刷新到磁盘: (1)数据存在的时间超过了dirty_expire_centisecs(默认30s)时间 (2)脏数 ...

  7. linux 内核参数mss,linux 内核对于TCPMSS的处理

    iptables -A FORWARD -p tcp- -tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 这条规则的目的就是改变TCP MSS以 ...

  8. linux 内核参数调整说明

    linux 内核参数调整说明 所有的TCP/IP调优参数都位于/proc/sys/net/目录.例如, 下面是最重要的一些调优参数, 后面是它们的含义: 1. /proc/sys/net/core/r ...

  9. linux内核参数汇总

    目录: 目录 linux内核参数配置 内核参数列表 内存参数列表 网络参数列表 linux内核参数配置 Linux在系统运行时修改内核参数(/proc/sys与/etc/sysctl.conf),而不 ...

  10. 修改Linux内核参数提高服务器并发能力

    1.参数设置 查看相关的参数 sysctl -a|grep tcp_keepalive net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive ...

最新文章

  1. 如何成为有效学习的高手(许岑)——思维导图
  2. Kruskal(P)和Prim(K)算法
  3. (3.13)mysql基础深入——mysql日志分析工具之mysqlsla【待完善】
  4. 通过窗口名字(caption的内容)查找窗口,并将其隐藏或者置顶显示
  5. Java基础学习总结(73)——Java最新面试题汇总
  6. android学习之ListView如何使用
  7. STM32F103ZET6 PWM输出
  8. 强大的ETL利器—DataFlow3.0
  9. vue中改变v-html元素样式
  10. 更新DOTA2显示无法连接到更新服务器,DOTA2无法正常更新的解决方法 官方公告
  11. mouseover、mouseenter
  12. 阻塞与非阻塞、同步与异步、I/O模型
  13. DFRobot智能视觉传感器二哈识图(Huskylens)的应用
  14. 怎么彻底清除计算机病毒,Win7旗舰版系统如何才能彻底删除电脑病毒
  15. Python数据分析2-pandas入门和实战
  16. 阮一峰访谈问题有奖征集
  17. 解决百度地图内存泄露问题
  18. 怎么退出自适应巡航_你的自适应巡航“全速”了吗?
  19. Java框架之Struts2(六)
  20. C++中的数组初始化

热门文章

  1. 全世界所有程序员都会犯的错误-蔡学镛
  2. MATLAB数据类型及转换
  3. 数学模型——初步理解马尔可夫链(Markov chain)
  4. postgresql点云las_三维点云目标提取总结【转】
  5. 信捷plc与台达变频器modbus rtu通讯程序
  6. 图像处理之matlab的取整函数round、ceil、floor和fix
  7. 快速集成华为AGC-AppLinking服务-Cocos平台
  8. 炒股杠杆-API智能交易软件基本函数
  9. android端的声音检测程序(检测声音分贝大小)
  10. html 语音识别输入法,9种外语语音识别 搜狗输入法成国内支持语种最多输入法...