1:代码实现
通过下面的vaddr_2_paddr函数即可完成虚拟地址到物理地址的转化,整体代码如下
环境:ubuntu kernel-4.4
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/mm.h>
#include<linux/mm_types.h>
#include<linux/sched.h>
#include<linux/export.h>
static unsigned long vaddr = 0;
static unsigned long cr0,cr3;
void get_pgtable_macro(void)
{
cr0 = read_cr0();
cr3 = read_cr3();
printk("cr0 = 0x%lx, cr3 = 0x%lx\n",cr0, cr3);
printk("PGDIR: %d\n",PGDIR_SHIFT);
printk("PUD: %d\n",PUD_SHIFT);
printk("PMD: %d\n",PMD_SHIFT);
printk("PAGE: %d\n",PAGE_SHIFT);
printk("PTRS_PER_PGD: %d\n",PTRS_PER_PGD);
printk("PTRS_PER_PUD: %d\n",PTRS_PER_PUD);
printk("PTRS_PER_PMD: %d\n",PTRS_PER_PMD);
printk("PTRS_PER_PTE: %d\n",PTRS_PER_PTE);
printk("PAGE_MASK: 0x%lx\n",PAGE_MASK);
}
static int vaddr_2_paddr(unsigned long vaddr)
{
pgd_t *pgd;
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 = %llx, pgd_index = %lu\n", pgd_val(*pgd),pgd_index(vaddr));
if(pgd_none(*pgd)){
printk("not map int pgd\n");
return -1;
}
pud = pud_offset(pgd, vaddr);
if(pud_none(*pud)){
printk("not map int pud\n");
return -1;
}
pmd = pmd_offset(pud, vaddr);
printk("pmd_val = 0x%llx, pmd_index = %lu\n", pmd_val(*pmd),pmd_index(vaddr));
if(pmd_none(*pmd)){
printk("not map int pmd\n");
return -1;
}
pte = pte_offset_kernel(pmd, vaddr);
printk("pte_val = 0x%llx, pte_index = %lu\n", pte_val(*pte),pte_index(vaddr));
if(pte_none(*pte)){
printk("not map int pte\n");
return -1;
}
page_addr = pte_val(*pte) & PAGE_MASK;
page_offset = vaddr & ~PAGE_MASK;
paddr = page_addr | page_offset;
printk("page_addr:0x%lx,page_offset:0x%lx\n",page_addr,page_offset);
printk("vaddr:0x%lx,paddr:0x%lx\n",vaddr,paddr);
return paddr;
}
static int __init v2p_init(void)
{
printk("v2p start:\n");
//基础信息
get_pgtable_macro();
vaddr = __get_free_page(GFP_KERNEL);
if(vaddr == 0)
{
printk("get free page error\n");
return 0;
}
sprintf((char *)vaddr,"hello world");
printk("vaddr:%lx\n",vaddr);
//完成地址转化
vaddr_2_paddr(vaddr);
return 0;
}
static void __exit v2p_exit(void)
{
printk("exit v2p\n");
free_page(vaddr);
}
module_init(v2p_init);
module_exit(v2p_exit);
MODULE_LICENSE("GPL");
2:过程解析
2.1:地址转化基础概念
1)段机制-线性地址
线性地址 = 段基地址+偏移
段基地址通过虚拟地址的选择符在段表中找到,但是linux通过将基地址设为0,这样就是事实线性地址等于偏移量,这样就绕过了段机制。
#defien __KERNEL_CS 0X10 //内核代码段 index=2,TI=0,RPL=0
#defien __KERNEL_DS 0X18 //内核数据段 index=3,TI=0,RPL=0
#defien __USER_CS 0X10 //用户代码段 index=4,TI=0,RPL=3
#defien __USER_DS 0X10 //用户数据段 index=5,TI=0,RPL=3
2)页机制-物理地址
pgd_t *pgd; //Page Global Directory (页全局目录)
pud_t *pud; //Page Upper Directory (页上级目录)
pmd_t *pmd; //Page Middle Directory (页中间目录)
pte_t *pte; //Page Table Entry (页表项)
四级转化关系如下:
2.2 代码解析
1)线性地址
根据上面的地址转化关系,由于kernel中已经将段基地址设置为0,那么打印出来的逻辑地址即为线性地址(虚拟地址)。
vaddr = __get_free_page(GFP_KERNEL);
sprintf((char *)vaddr,"hello world");
printk("vaddr:%lx\n",vaddr);
2)物理地址
根据上面的的四级转化关系,代码也分为4步
1:通过MM找到PGD(因为所有的进程共用内核空间,所以选择当前进程的mm即可)
#define pgd_index(addr) ((addr) >> PGDIR_SHIFT) //获得在pgd表中的索引
#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr)) //获得pmd表的起始地址
pgd = pgd_offset(current->mm, vaddr);
2:通过PGD找到PUD
static inline pud_t *pud_offset(pgd_t *pgd, unsigned long address)
{
return (pud_t *)pgd_page_vaddr(*pgd) + pud_index(address);
}
pud = pud_offset(pgd, vaddr);
3:通过PUD找到PMD
/* Find an entry in the second-level page table.. */
static inline pmd_t *pmd_offset(pud_t *pud, unsigned long address)
{
return (pmd_t *)pud_page_vaddr(*pud) + pmd_index(address);
}
pmd = pmd_offset(pud, vaddr);
4:通过PMD找到PTE
static inline pte_t *pte_offset_kernel(pmd_t *pmd, unsigned long address)
{
return (pte_t *)pmd_page_vaddr(*pmd) + pte_index(address);
}
pte = pte_offset_kernel(pmd, vaddr);
经过上述步骤,我们可以通过线性地址找到了页表项,而页表项到最终获得物理地址通过以下三句代码即可
//通过页表项找到页地址
page_addr = pte_val(*pte) & PAGE_MASK;
//通过线性地址找到页内偏移
page_offset = vaddr & ~PAGE_MASK;
//物理地址= 页地址+页内偏移
paddr = page_addr | page_offset;
3:结果验证
当完成了上述的过程,我们如何查看该物理地址是否真的是“hello world”呢?
这里需要用到俩小工具:dram和fileview
链接:
https://download.csdn.net/download/weixin_43690845/12346472
验证过程如下:
1)插入v2p.ko,查看打印如下,物理地址为0x32bb6000
2)加载dram.ko,创建相关节点
sudo insmod dram.ko
mknod /dev/dram c 85 0
3)使用fileview查看物理地址
./fileview /dev/dram
结果如下,完全符合