linux 内核参数 pte,Linux下通过线性地址得到页表项pte(X86和龙芯2F下)
在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下)相关推荐
- 开机时设置linux 内核参数 mem,Linux内核开机保留大块内存的方法总结
在网上搜了很久,才慢慢了解在开机保留内存的方法,现在总结一下这阶段的学习过程!(我是在ARM板子上进行的实验,内核版本是2.6.38) 在开机保留内存的方式一共有三种方法: 1. reserve_bo ...
- linux 内核参数 rss,Linux控制内存的内核参数
环境 Red Hat Enterprise Linux (RHEL) 5.x (X86) 在 X86 高内存设备中,当用户进程使用 mlock() 在常规区域分配大量内存时,可重新使用的 lowmem ...
- linux内核参数分析,linux内核启动第一阶段分析
linux内核启动第一阶段分析 http://blog.csdn.net/aaronychen/article/details/2838341 本文的很多内容是参考了网上某位大侠的文章写的<&l ...
- linux 内核参数 杨,Linux 内核参数
牢记!内核参数可以调整,但不是随便乱调,需要根据业务进行判断,并且要知道调整的后果是什么,存在哪些风险. 牢记!!!调整参数时,做好记录!!! 网络参数 /proc/sys/net/core/wmem ...
- linux 内核参数 杨,Linux 内核参数
1. net.core.somaxconn freebsd版本中对应的参数为 kern.ipc.somaxconn 作用:限制接收新 TCP 连接侦听队列的大小 关注点:nginx debug:acc ...
- linux 内核参数分析,linux 内核参数VM调优 之 参数调节和场景分析
总结可知cached中的脏数据满足如下几个条件中一个或者多个的时候就会被pdflush刷新到磁盘: (1)数据存在的时间超过了dirty_expire_centisecs(默认30s)时间 (2)脏数 ...
- linux 内核参数mss,linux 内核对于TCPMSS的处理
iptables -A FORWARD -p tcp- -tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 这条规则的目的就是改变TCP MSS以 ...
- linux 内核参数调整说明
linux 内核参数调整说明 所有的TCP/IP调优参数都位于/proc/sys/net/目录.例如, 下面是最重要的一些调优参数, 后面是它们的含义: 1. /proc/sys/net/core/r ...
- linux内核参数汇总
目录: 目录 linux内核参数配置 内核参数列表 内存参数列表 网络参数列表 linux内核参数配置 Linux在系统运行时修改内核参数(/proc/sys与/etc/sysctl.conf),而不 ...
- 修改Linux内核参数提高服务器并发能力
1.参数设置 查看相关的参数 sysctl -a|grep tcp_keepalive net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive ...
最新文章
- 如何成为有效学习的高手(许岑)——思维导图
- Kruskal(P)和Prim(K)算法
- (3.13)mysql基础深入——mysql日志分析工具之mysqlsla【待完善】
- 通过窗口名字(caption的内容)查找窗口,并将其隐藏或者置顶显示
- Java基础学习总结(73)——Java最新面试题汇总
- android学习之ListView如何使用
- STM32F103ZET6 PWM输出
- 强大的ETL利器—DataFlow3.0
- vue中改变v-html元素样式
- 更新DOTA2显示无法连接到更新服务器,DOTA2无法正常更新的解决方法 官方公告
- mouseover、mouseenter
- 阻塞与非阻塞、同步与异步、I/O模型
- DFRobot智能视觉传感器二哈识图(Huskylens)的应用
- 怎么彻底清除计算机病毒,Win7旗舰版系统如何才能彻底删除电脑病毒
- Python数据分析2-pandas入门和实战
- 阮一峰访谈问题有奖征集
- 解决百度地图内存泄露问题
- 怎么退出自适应巡航_你的自适应巡航“全速”了吗?
- Java框架之Struts2(六)
- C++中的数组初始化
热门文章
- 全世界所有程序员都会犯的错误-蔡学镛
- MATLAB数据类型及转换
- 数学模型——初步理解马尔可夫链(Markov chain)
- postgresql点云las_三维点云目标提取总结【转】
- 信捷plc与台达变频器modbus rtu通讯程序
- 图像处理之matlab的取整函数round、ceil、floor和fix
- 快速集成华为AGC-AppLinking服务-Cocos平台
- 炒股杠杆-API智能交易软件基本函数
- android端的声音检测程序(检测声音分贝大小)
- html 语音识别输入法,9种外语语音识别 搜狗输入法成国内支持语种最多输入法...