虚地址转换为物理地址

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 一、 与页相关的数据结构及宏的定义    分页机制是硬件对分页的支持,这是虚拟内存管理的硬件基础。要想使这种硬件机制充分发挥其功能,必须有相应软件的支持,我们来看一下Linux所定义的一些主要数据结构,其分布在include/asm-i386/目录下的page.h,pgtable.h及pgtable-2level.h三个文件中。

一、 与页相关的数据结构及宏的定义
    分页机制是硬件对分页的支持,这是虚拟内存管理的硬件基础。要想使这种硬件机制充分发挥其功能,必须有相应软件的支持,我们来看一下Linux所定义的一些主要数据结构,其分布在include/asm-i386/目录下的page.hpgtable.hpgtable-2level.h三个文件中。
 1. 表项的定义
   如上所述,PGDPMDPT表的表项都占4个字节,因此,把它们定义为无符号长整数,分别叫做pgd_tpmd_tpte_t(pte Page table Entry),在page.h中定义如下:
    typedef struct { unsignedlong pte_low; } pte_t;
    typedef struct { unsignedlong pmd; } pmd_t;
typedef struct { unsigned long pgd; }pgd_t;
    typedefstruct { unsigned long pgprot; } pgprot_t;
    可以看出,Linux没有把这几个类型直接定义长整数而是定义为一个结构,这是为了让gcc在编译时进行更严格的类型检查。另外,还定义了几个宏来访问这些结构的成分,这也是一种面向对象思想的体现:
   #definepte_val(x)      ((x).pte_low)
   #define pmd_val(x)     ((x).pmd)
   #define pgd_val(x)      ((x).pgd)
      从图2.13可以看出,对这些表项应该定义成位段,但内核并没有这样定义,而是定义了一个页面保护结构pgprot_t和一些宏:
   typedefstruct { unsigned long pgprot; } pgprot_t;
   #definepgprot_val(x)   ((x).pgprot)
  字段pgprot的值与图2.13表项的低12位相对应,其中的9位对应09位,在pgtalbe.h中定义了对应的宏:
  #define _PAGE_PRESENT   0×001  
  #define _PAGE_RW        0×002
  #define _PAGE_USER      0×004
  #define _PAGE_PWT       0×008
  #define _PAGE_PCD       0×010
  #define _PAGE_ACCESSED  0×020
  #define _PAGE_DIRTY     0×040
  #define _PAGE_PSE       0×080  /* 4 MB (or 2MB) page, Pentium+, if present.. */
  #define _PAGE_GLOBAL    0×100  /* Global TLB entry PPro+ */
    在你阅读源代码的过程中你能体会到,把标志位定义为宏而不是位段更有利于编码。
    另外,页目录表及页表在pgtable.h中定义如下:
     externpgd_t swapper_pg_dir[1024];
     externunsigned long pg0[1024];
     swapper_pg_dir为临时页目录表,是在内核编译的过程中被静态初始化的。pg0为初始化过程中使用的一临时页表。
 2.线性地址域的定义
    
      
(1)页偏移量的位数
  #define PAGE_SHIFT      12
  #define PAGE_SIZE       (1UL      #define PTRS_PER_PTE    1024
  #define PAGE_MASK       (~(PAGE_SIZE-1))
   其中PAGE_SHIFT宏定义了页偏移量的位数为12,因此页大小PAGE_SIZE2124096字节; PTRS_PER_PTE为页表的项数;最后PAGE_MASK值定义为0xfffff000,用以屏蔽掉偏移量域的所有位(12位)。
(2)PGDIR_SHIFT
   #define PGDIR_SHIFT     22
   #define PTRS_PER_PGD    1024
   #define PGDIR_SIZE      (1UL    #define PGDIR_MASK      (~(PGDIR_SIZE-1))
   PGDIR_SHIFT是页表所能映射区域线性地址的位数,它的值为2212位的偏移量加上10位的页表);PTRS_PER_PGD为页目录目录项数;PGDIR_SIZE为页目录的大小,222,即4MBPGDIR_MASK0xffc00000,用于屏蔽偏移量位与页表域的所有位。
3PMD_SHIFT
#definePMD_SHIFT       22
#definePTRS_PER_PMD    1
   PMD_SHIFT为中间目录表映射的地址位数,其值也为22,但是对于两级页表结构,让其目录项个数为1,这就使得中间目录在指针序列中的位置被保存,以便同样的代码在32位系统和64位系统下都能使用。后面的讨论我们不再提及中间目录。
 
3  对页目录及页表的处理
   page.hpgtable.hpgtable-2level.h三个文件中还定义有大量的宏,用以对页目录、页表及表项的处理,我们在此介绍一些主要的宏和函数。
 
  3.1.表项值的确定
   staticinline int pgd_none(pgd_t pgd)          { return 0; }
   staticinline int pgd_present(pgd_t pgd)       { return 1; }
  
   #definepte_present(x)  ((x).pte_low &(_PAGE_PRESENT | _PAGE_PROTNONE))
     pgd_none()函数直接返回0,表示尚未为这个页目录建立映射,所以页目录项为空。pgd_present()函数直接返回1,表示映射虽然还没有建立,但页目录所映射的页表肯定存在于内存(即页表必须一直在内存)。
pte_present宏的值为10,表示P标志位。如果页表项不为0,但标志位为0,则表示映射已经建立,但所映射的物理页面不在内存。
  3.2. 清相应表的表项:
  #definepgd_clear(xp)                          do { } while (0)
  #definepte_clear(xp)   do { set_pte(xp,__pte(0)); } while (0)
  pgd_clear宏实际上什么也不做,定义它可能是为了保持编程风格的一致。pte_clear就是把0写到页表表项中。
 3.3.对页表表项标志值进行操作的宏。
 这些宏的代码在pgtable.h文件中,表2.2给出宏名及其功能。
  2.2 对页表表项标志值进行操作的宏及其功能

       宏名
  
  

功能

  

  Set_pte()
  
  把一个具体的值写入表项
  
  Pte_read()
  
  返回User/Supervisor标志值(由此可以得知是否可以在用户态下访问此页)
  
  Pte _write()
  
  如果Present标志和Read/Write标志都为1,则返回1(此页是否存在并可写)
  
  Pte _exec()
  
  返回User/Supervisor标志值
  
  Pte _dirty()
  
  返回Dirty标志的值(说明此页是否被修改过)
  
  Pte _young()
  
  返回Accessed标志的值(说明此页是否被存取过) 
  
  Pte _wrprotect()
  
  清除Read/Write标志
  
  Pte _rdprotect()
  
  清除User/Supervisor标志
  
  Pte _mkwrite
  
  设置Read/Write标志
  
  Pte _mkread
  
  设置User/Supervisor标志
  
  Pte _mkdirty()
  
  Dirty标志置1
  
  Pte _mkclean()
  
  Dirty标志置0
  
  Pte _mkyoung
  
  Accessed标志置1
  
  Pte _mkold()
  
  Accessed标志置0
  
  Pte _modify(p,v)
  
  把页表表项p的所有存取权限设置为指定的值v
  
  mk_pte()
  
  把一个线性地址和一组存取权限合并来创建一个32位的页表表项
  
  pte _pte_phys()
  
  把一个物理地址与存取权限合并来创建一个页表表项
  
  pte _page()
  
  从页表表项返回页的线性地址
  


 
   实际上页表的处理是一个复杂的过程,在这里我们仅仅让读者对软硬件如何结合起来有一个初步的认识。
三、模块编程举例

结合上面的介绍,我们编写一个内核模块,把一个给定的虚地址转换为内存的物理地址:

  1. /*****************************************************************
  2. 文件名:mem.c
  3. 输入参数:
  4. pid 接收待查询进程的PID
  5. va 接收待查询的虚拟地址
  6. *****************************************************************/
  7. #include  linux/module.h>
  8. #include linux/kernel.h>
  9. #include linux/init.h>
  10. #include linux/sched.h>
  11. #include linux/mm.h>
  12. #include asm/pgtable.h>
  13. #include asm/page.h>
  14. MODULE_LICENSE("GPL");
  15. static int pid;
  16. static unsigned long va;
  17. module_param(pid,int,0644);
  18. module_param(va,ulong,0644);
  19. static int find_pgd_init(void)
  20. {
  21.         unsigned long pa = 0;
  22.         struct task_struct *pcb_tmp = NULL;
  23.         pgd_t *pgd_tmp = NULL;
  24.         pud_t *pud_tmp = NULL;
  25.         pmd_t *pmd_tmp = NULL;
  26.         pte_t *pte_tmp = NULL;
  27.         printk(KERN_INFO"PAGE_OFFSET = 0x%lx\n",PAGE_OFFSET);
  28.         printk(KERN_INFO"PGDIR_SHIFT = %d\n",PGDIR_SHIFT);
  29.         printk(KERN_INFO"PUD_SHIFT = %d\n",PUD_SHIFT);
  30.         printk(KERN_INFO"PMD_SHIFT = %d\n",PMD_SHIFT);
  31.         printk(KERN_INFO"PAGE_SHIFT = %d\n",PAGE_SHIFT);
  32.         printk(KERN_INFO"PTRS_PER_PGD = %d\n",PTRS_PER_PGD);
  33.         printk(KERN_INFO"PTRS_PER_PUD = %d\n",PTRS_PER_PUD);
  34.         printk(KERN_INFO"PTRS_PER_PMD = %d\n",PTRS_PER_PMD);
  35.         printk(KERN_INFO"PTRS_PER_PTE = %d\n",PTRS_PER_PTE);
  36.         printk(KERN_INFO"PAGE_MASK = 0x%lx\n",PAGE_MASK);
  37.         if(!(pcb_tmp = find_task_by_pid(pid))) {
  38.                 printk(KERN_INFO"Can't find the task %d .\n",pid);
  39.                 return 0;
  40.         }
  41.         printk(KERN_INFO"pgd = 0x%p\n",pcb_tmp->mm->pgd);
  42.                 /* 判断给出的地址va是否合法(va<vm_end)*/
  43.         if(!find_vma(pcb_tmp->mm,va)){
  44.                 printk(KERN_INFO"virt_addr 0x%lx not available.\n",va);
  45.                 return 0;
  46.         }
  47.         pgd_tmp = pgd_offset(pcb_tmp->mm,va);
  48.         printk(KERN_INFO"pgd_tmp = 0x%p\n",pgd_tmp);
  49.         printk(KERN_INFO"pgd_val(*pgd_tmp) = 0x%lx\n",pgd_val(*pgd_tmp));
  50.         if(pgd_none(*pgd_tmp)){
  51.                 printk(KERN_INFO"Not mapped in pgd.\n");        
  52.                 return 0;
  53.         }
  54.         pud_tmp = pud_offset(pgd_tmp,va);
  55.         printk(KERN_INFO"pud_tmp = 0x%p\n",pud_tmp);
  56.         printk(KERN_INFO"pud_val(*pud_tmp) = 0x%lx\n",pud_val(*pud_tmp));
  57.         if(pud_none(*pud_tmp)){
  58.                 printk(KERN_INFO"Not mapped in pud.\n");
  59.                 return 0;
  60.         }
  61.         pmd_tmp = pmd_offset(pud_tmp,va);
  62.         printk(KERN_INFO"pmd_tmp = 0x%p\n",pmd_tmp);
  63.         printk(KERN_INFO"pmd_val(*pmd_tmp) = 0x%lx\n",pmd_val(*pmd_tmp));
  64.         if(pmd_none(*pmd_tmp)){
  65.                 printk(KERN_INFO"Not mapped in pmd.\n");
  66.                 return 0;
  67.         }
  68.         /*在这里,把原来的pte_offset_map()改成了pte_offset_kernel*/
  69.         pte_tmp = pte_offset_kernel(pmd_tmp,va);
  70.         printk(KERN_INFO"pte_tmp = 0x%p\n",pte_tmp);
  71.         printk(KERN_INFO"pte_val(*pte_tmp) = 0x%lx\n",pte_val(*pte_tmp));
  72.         if(pte_none(*pte_tmp)){
  73.                 printk(KERN_INFO"Not mapped in pte.\n");
  74.                 return 0;
  75.         }
  76.         if(!pte_present(*pte_tmp)){
  77.                 printk(KERN_INFO"pte not in RAM.\n");
  78.                 return 0;
  79.         }
  80.         pa = (pte_val(*pte_tmp) &amp; PAGE_MASK) |(va &amp; ~PAGE_MASK);
  81.         printk(KERN_INFO"virt_addr 0x%lx in RAM is 0x%lx .\n",va,pa);
  82.         printk(KERN_INFO"contect in 0x%lx is 0x%lx\n",pa,
  83.                 *(unsigned long *)((char *)pa + PAGE_OFFSET));
  84.                                                         
  85.         return 0;
  86. }
  87. static void  find_pgd_exit(void)
  88. {
  89.         printk(KERN_INFO"Goodbye!\n");
  90. }
  91. module_init(find_pgd_init);
  92. module_exit(find_pgd_exit);

测试:打开gedit, 再打开任务管理器,查看gedit的进程号pid=12749,
右键查看其内存映射,找到一个有效的虚拟地址va=0xb8041000,然后:
sudo insmod mem.ko pid=12749 va=0xb8041000

如果你的内核是2.6.24以后的,需要将find_task_by_pid改为find_task_by_vpid

结果:pid=12749 va=0xb8041000

目录
相关文章
|
2天前
|
存储 芯片
什么是虚地址,什么是物理地址?
什么是虚地址,什么是物理地址?
11 0
|
2天前
|
存储 程序员 芯片
虚拟地址和物理地址之间的区别
【4月更文挑战第12天】
16 2
虚拟地址和物理地址之间的区别
|
3月前
|
算法
页表、地址重定位、地址保护的名词解释
页表、地址重定位、地址保护的名词解释
12 0
|
9月前
|
存储 索引
逻辑地址与物理地址的转换
最近一直在学8086,上课老师突然问了个这。对于问题“8086 CPU 能提供20位的地址信息,可直接对1M个存储单元进行访问,而CPU内部可用来提供地址信息的寄存器都是16位,那怎样用16位寄存器来实现20位地址寻址呢"明白了不少。
71 0
|
11月前
|
存储
逻辑地址和物理地址转换
逻辑地址和物理地址转换
|
7月前
|
网络协议
物理地址通过什么协议转换为ip地址
物理地址通过什么协议转换为ip地址
72 0
|
程序员 C语言
数据寻址——偏移寻址
数据寻址——偏移寻址
490 0
数据寻址——偏移寻址
|
程序员 索引
《操作系统真象还原》——0.9 物理地址、逻辑地址、有效地址、线性地址、虚拟地址的区别
物理地址就是物理内存真正的地址,相当于内存中每个存储单元的门牌号,具有唯一性。不管在什么模式下,不管什么虚拟地址、线性地址,CPU最终都要以物理地址去访问内存,只有物理地址才是内存访问的终点站。
1997 0
汇编(二) 内存地址空间、寄存器、物理地址以及物理地址的计算
汇编、内存地址空间、寄存器、物理地址、物理地址的计算、汇编指令
4814 1