2.1 内存篇-如何找到存放hello world的物理地址

标签: llinux-4.9

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
结果如下,完全符合
在这里插入图片描述

版权声明:本文为weixin_43690845原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43690845/article/details/105650221