概述
目录
引言
虚拟地址
页面划分
代码实现
参考资料
引言
本文基于X86系统,简单了讲述了从Linux中在系统开启分页之后将虚拟地址转为物理地址的过程以及具体代码,不包含分页概念。内核版本基于4.19,内核开始支持5级分页,相对于4级分页,多了P4D目录项,但目前系统中并未使用,如有错误,欢迎指正。
虚拟地址
cpu从实模式进入保护模式后,系统开始支持分页,Intel的cpu还支持分段,但Linux通过将所有段基址都置为0,巧妙了跳过了分段机制,使得线性地址等于逻辑地址。
在开启分页之后,每个进程运行的地址不再是实模式中的绝对地址,而是虚拟地址。程序编译时都从地址0开始编译,在载入时系统在寻找未分配的内存页面进行进行分配。使得程序员可以不考虑程序在内存的实际位置,但我们若想获取变量在内存中实际地址怎么做?那就需要我们简单了解下Linux对于页面是进行如何划分的。
页面划分
图1 页面划分
如图1,是Linux对于64位cpu地址的页面划分的情况,64位cpu中的地址总线位数只有48位,高位实际上未使用;
对于给出的虚拟地址,其对应位数含义如下。记录了其在对应目录中的页表项位置。
0~11:页偏移 12位
12~20:页表项 9位
21~29:页中间目录 9位
30~38:页上级目录 9位
39~47:页全局目录 9位
例如虚拟地址:0xffff 997ee853d 000,要获取这个地址的实际物理地址;
(1)、 首先从cr3中获取页全局目录的基址,页全局目录表项为:1 0011 0011(997取前9位)= 132;页全局目录基址+页全局目录表项*8(一个页表项8B)得到存放页上级目录基址。
(2)、仿照(1)中步骤,知道页上级目录基址之后,得到页上级目录表项:0xffff 997ee853d 000的30~38位,获取页中间目录的基址。
(3)、接着获取页表项的基址
(4)、读取页表项的地址+OFFSET即最终的物理地址
图2 地址查找步骤
对于4.x版本内核,在进行地址转换的时候还需要获取P4D(位于PGD和PUD之间)的地址,否则会报错,因为地址查询只能按照一级一级目录查询,如图2,需要从cr3中读取也全局目录地址,然后
代码实现
static unsigned long vaddr2paddr(unsigned long vaddr)
{
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
unsigned long paddr = 0;
unsigned long page_addr = 0;
unsigned long page_offset = 0;
pgd = pgd_offset(current->mm, vaddr);
printk("pgd_val = 0x%lx, pgd_index = %lun", pgd_val(*pgd),
pgd_index(vaddr));
if (pgd_none(*pgd))
{
printk("not mapped in pgdn");
return -1;
}
p4d = p4d_offset(pgd, vaddr);
printk("p4d_val = 0x%lx, p4d_index = %lun", p4d_val(*p4d),
p4d_index(vaddr));
if (p4d_none(*p4d))
{
printk("not mapped in p4dn");
return -1;
}
pud = pud_offset(p4d, vaddr);
printk("pud_val = 0x%lx, pud_index = %lun", pud_val(*pud),
pud_index(vaddr));
if (pud_none(*pud))
{
printk("not mapped in pudn");
return -1;
}
pmd = pmd_offset(pud, vaddr);
printk("pmd_val = 0x%lx, pmd_index = %lun", pmd_val(*pmd),
pmd_index(vaddr));
if (pmd_none(*pmd))
{
printk("not mapped in pmdn");
return -1;
}
pte = pte_offset_kernel(pmd, vaddr);
printk("pte_val = 0x%lx, pte_index = %lun", pte_val(*pte),
pte_index(vaddr));
if (pte_none(*pte))
{
printk("not mapped in pgdn");
return -1;
}
page_addr = pte_val(*pte) & PAGE_MASK; // get high 48 bit, get physical page addr
page_offset = vaddr & ~PAGE_MASK; // get page offset
paddr = page_addr | page_offset;
printk("page_addr = %lx, page_offset = %lxn", page_addr, page_offset);
printk("vaddr = %lx, paddr = %lxn", vaddr, paddr);
return paddr;
}
参考资料
《深入理解Linux内核》
Linux 内核分析与应用 - 西安邮电大学 - 学堂在线
最后
以上就是精明夏天为你收集整理的虚拟地址转物理地址(vaddr2paddr)引言虚拟地址页面划分 代码实现参考资料的全部内容,希望文章能够帮你解决虚拟地址转物理地址(vaddr2paddr)引言虚拟地址页面划分 代码实现参考资料所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复