虚拟地址如何访问到物理地址

环境:32bit CPU
一、通过二级页表映射的方式访问物理地址
虚拟地址如何访问到物理地址_第1张图片
1、取一级页表的基地址Abase1
2、取虚拟地址的前12bit[31:20]地址O1
3、计算得到新地址Apgd=(Abase1&0xFFFFF000)+O1,此地址是PGD页表上的地址,取此地址中的数据Abase2
4、取虚拟地址的中间8bit[19:12]地址O2
5、计算得到新地址Apte=(Abase2&0xFFFFFF00)+O2,此地址是PTE页表上的地址,取此地址中的数据Abase3
6、取虚拟地址的后12bit[11:0]地址O3
7、计算得到新地址Aphy=(Abase3&0xFFFFF000)+O3,此地址就是实实在在的物理地址
以上的计算和查找过程有MMU模块实现,同时会把映射信息存放在MMU中的TLB中,类似于cache,方便下次快速的查找。
二、疑问
1、一级页表的基地址存放在什么地方?
2、如何找到存放在物理地址的信息?
三、分析问题
1、操作系统有个专门的结构体负责进程的内存使用情况,task_struct里面的mm_struct

struct task_struct {
	......
	int on_rq;

	int prio, static_prio, normal_prio;
	unsigned int rt_priority;
	const struct sched_class *sched_class;
	struct sched_entity se;
	struct sched_rt_entity rt;
	......

	struct mm_struct *mm, *active_mm;

struct mm_struct {
	......
	unsigned long mmap_base;		/* base of mmap area */
	unsigned long mmap_legacy_base;         /* base of mmap area in bottom-up allocations */
	unsigned long task_size;		/* size of task vm space */
	unsigned long highest_vm_end;		/* highest vma end address */
	pgd_t * pgd;
	atomic_t mm_users;			/* How many users with user space? */
	atomic_t mm_count;			/* How many references to "struct mm_struct" (users count as 1) */
	atomic_long_t nr_ptes;			/* PTE page table pages */
	......

其中pgd保存的就是一级页表的基地址。进程切换时,会把TTBRx寄存器(上图所示)保存起来,把下一个进程的pgd内容写到TTBRx寄存器中。
2、写段程序,找到存放信息的物理地址
2.1、内核态代码
根据上面所描述的页表映射方式,查找到虚拟地址对应的物理地址
需要在内核态下面运行,内核态下可以获取当前进程的PCB task_struct进而获取mm_struct内存管理模块

#include 
unsigned int get_phy_addr(struct mm_struct *mm, unsigned long addr)
{
       pgd_t *pgd        = NULL;
       pud_t *pud        = NULL;
       pmd_t *pmd        = NULL;
       pte_t *pte        = NULL;
       unsigned int phy_addr = 0;

       printk("pgd_base = %p\n",mm->pgd);
       pgd = pgd_offset(mm, addr);                                  //一级页表项的地址
       printk("vaddr=[0x%08x] *pgd=%08x\n", addr, pgd_val(*pgd));

       if (pgd_none(*pgd))
       		goto out;

       if (pgd_bad(*pgd)) {
	       printk("pgd bad\n");
       	   goto out;
       }


       pud = pud_offset(pgd, addr);
       printk("pud=0x%08x,*pud=%08x\n", pud, pud_val(*pud));

       if (pud_none(*pud))
       		goto out;
               
       if (pud_bad(*pud)) {
       		printk("pud bad");
			goto out;
       }


       pmd = pmd_offset(pud, addr);
       printk("pmd=0x%08x,*pmd=%08x\n", pmd, pmd_val(*pmd));

       if (pmd_none(*pmd))
       		goto out;

       if (pmd_bad(*pmd)) {
	   		printk("(bad)");
       		goto out;
       }
       
       //二级映射,pmd等于pgd。函数中实现两个功能:1、根据pgd地址查找二级页表基地址;2、二级页表基地址和addr算出二级页表地址
       pte = pte_offset_map(pmd, addr);     
       printk("pte=0x%08x, *pte=%08x\n", pte, pte_val(*pte));

       if(!pte_none(*pte) && pte_present(*pte)) {
       		phy_addr = (pte_val(*pte) & 0xFFFFF000) | (addr & 0xFFF);
       		printk("phy_addr is 0x%x\n", phy_addr);
       }else{
       		printk("pte is not present\n") ;
       }

       pte_unmap(pte);

       return phy_addr;
out:
       return -1;
}

2.2、/dev/mem节点说明
知道虚拟地址对应的物理地址,我们就可以借助/dev/mem节点访问对应的物理地址查看是否是虚拟地址存放的数据。
/dev/mem是系统物理内存的映像文件,这里的物理内存是指我们插在内存槽上的内存条吗?当然是,但物理内存不单单指内存条。
物理内存严格来讲应该是指 物理地址空间 ,内存条只是映射到这个地址空间的一部分,其余的还有各种PCI设备,IO端口等。
我们可以从/proc/iomem中看到这个映射:

cat /proc/iomem

虚拟地址如何访问到物理地址_第2张图片
其中 内存分配的物理地址是0x80000000~0x83FFFFFF 64M
事实上,它就是一个活着的Linux系统实时映像,所有的进程task_struct结构体,sock结构体,sk_buff结构体,进程数据等等都在里面的某个位置:
虚拟地址如何访问到物理地址_第3张图片
如果能定位它们在/dev/mem里的位置,我们就能得到系统中这些数据结构的实时值,所谓的调试工具所做的也不过如此。其实我们在调试内核转储文件的时候,vmcore也是一个物理内存映像,和/dev/mem不同的是,它是一具尸体。
无论是活体,还是尸体,均五脏俱全,分析它们的手段是一致。和静态分析vmcore不同的是,/dev/mem是一个动态的内存映像,有时候借助它可以做一些正经的事情。
2.3、应用层获取/dev/mem的映射

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
       int i = 0, fd = 0;
       unsigned int addr = 0, content 	= 0;
       unsigned int page_size        	= getpagesize();
       unsigned int taddr        		= 0;
       unsigned int *map_base        	= NULL;
       unsigned int offset_in_page 		= 0;

       printf("page_size=0x%x\n", page_size);
       fd = open("/dev/mem", O_RDWR|O_SYNC);
       if (fd == -1) {
               printf("open /dev/mem error.\n");
               return -1;
       }

       taddr = strtoll(argv[1], NULL, 16);
       offset_in_page = taddr & (page_size - 1);
       //map_base = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, taddr & (~(unsigned int)(page_size - 1)));
  	   map_base = mmap(NULL, page_size, PROT_READ, MAP_PRIVATE, fd, taddr & (~(unsigned int)(page_size - 1)));
       if (-1 == (int)map_base) {
               printf("mmap failed!, errno=%d\n", errno);
               close(fd);
               return -1;
       }
       
       printf("map_base=0x%x\n", map_base);
       
       addr = (unsigned int)(((unsigned char *)map_base) + offset_in_page);
       printf("addressx:0x%x\n", addr);
       printf("content 0x%x\n\n", *(unsigned int *)(addr));

	   //*(unsigned int *)(addr) = 0x6f6a6944;
	   
       close(fd);
       munmap(map_base, page_size);

       return 1;
}

2.4、测试程序

测试程序:
int main(int argc, char** argv)
{
       int fd                        = -1;
       struct param_s param;


       char *buf = "Chinaxxxxx.\n";
       memset(&param, 0x00, sizeof(struct ty_motor_param_s));

       fd = open("/dev/motor", O_RDWR);
       if (fd < 0) {
               printf("open error.\n");
               return 0;
       }

       printf("buf=0x%x", buf);

       param.addr = buf;
       ioctl(fd, IOCTL_SET, &param);

       close(fd);

       sleep(10);
	   printf("buf=0x%x", buf);
	   
       return 0;
}

2.5、实验开始
2.5.1、运行2.4程序,通过ioctl运行内核代码,执行2.3程序get_phy_addr函数,获取物理地址。
2.5.2、通过传入获取到的物理地址,运行2.2程序,打印物理地址的信息。
在这里插入图片描述
其中0x83e6573c是获取的物理地址
ARM处理器小端存储数据,因此0x43 0x68 0x69 0x6e 刚好对应着Chinaxxxxx中的Chin的Ascii码值
2.6、扩展
2.6.1、既然知道物理地址了,是不是可以修改物理地址里面的内容?
2.6.2、打开2.3代码的注释 *(unsigned int *)(addr) = 0x6f6a6944;
2.6.3、再操作2.5中的步骤,发现原先的Chinaxxxxx被修改了
在这里插入图片描述

3、遇到的问题
3.1.1、2.3中的代码mmap一直返回-1,错误码一直是1(errno 1 Operation not permitted)
3.1.2、解决方法
在.config文件中设置CONFIG_STRICT_DEVMEM is not set

正如上面描述的,通过获取物理地址修改物理地址里面的数据,这对系统来说就毫无安全可言。因此内核通过CONFIG_STRICT_DEVMEM配置项禁止内存空间实现映射。

你可能感兴趣的:(linux)