Linux内存管理之MMU的过程( 二 )

进程在切换的时候就是根据task_struct找到mm_struct里的PGD字段,取得新进程的页全局目录,然后填充到CR3寄存器,就完成了页的切换 。
下面我们动手操作一下,通过代码来深度理解下虚拟地址是如何转化为物理地址的 。
#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/sched.h>#include <linux/pid.h>#include <linux/mm.h>#include <asm/pgtable.h>#include <asm/page.h>MODULE_DESCRIPTION("vitual address to physics address");static int pid;static unsigned long va;module_param(pid,int,0644); //从命令行传递参数(变量,类型,权限)module_param(va,ulong,0644); //va表示的是虚拟地址static int find_pgd_init(void){unsigned long pa = 0; //pa表示的物理地址struct task_struct *pcb_tmp = ;pgd_t *pgd_tmp = ;pud_t *pud_tmp = ;pmd_t *pmd_tmp = ;pte_t *pte_tmp = ;printk(KERN_INFO"PAGE_OFFSET = 0x%lxn",PAGE_OFFSET); //页表中有多少个项/*pud和pmd等等 在线性地址中占据多少位*/printk(KERN_INFO"PGDIR_SHIFT = %dn",PGDIR_SHIFT);//注意:在32位系统中 PGD和PUD是相同的printk(KERN_INFO"PUD_SHIFT = %dn",PUD_SHIFT);printk(KERN_INFO"PMD_SHIFT = %dn",PMD_SHIFT);printk(KERN_INFO"PAGE_SHIFT = %dn",PAGE_SHIFT);printk(KERN_INFO"PTRS_PER_PGD = %dn",PTRS_PER_PGD); //每个PGD里面有多少个ptrsprintk(KERN_INFO"PTRS_PER_PUD = %dn",PTRS_PER_PUD);printk(KERN_INFO"PTRS_PER_PMD = %dn",PTRS_PER_PMD); //PMD中有多少个项printk(KERN_INFO"PTRS_PER_PTE = %dn",PTRS_PER_PTE);printk(KERN_INFO"PAGE_MASK = 0x%lxn",PAGE_MASK); //页的掩码struct pid *p = ;p = find_vpid(pid); //通过进程的pid号数字找到struct pid的结构体pcb_tmp = pid_task(p,PIDTYPE_PID); //通过pid的结构体找到进程的task structprintk(KERN_INFO"pgd = 0x%pn",pcb_tmp->mm->pgd);// 判断给出的地址va是否合法(va&lt;vm_end)if(!find_vma(pcb_tmp->mm,va)){printk(KERN_INFO"virt_addr 0x%lx not available.n",va);return 0;}pgd_tmp = pgd_offset(pcb_tmp->mm,va); //返回线性地址va,在页全局目录中对应表项的线性地址printk(KERN_INFO"pgd_tmp = 0x%pn",pgd_tmp);//pgd_val获得pgd_tmp所指的页全局目录项//pgd_val是将pgd_tmp中的值打印出来printk(KERN_INFO"pgd_val(*pgd_tmp) = 0x%lxn",pgd_val(*pgd_tmp));if(pgd_none(*pgd_tmp)){ //判断pgd有没有映射printk(KERN_INFO"Not mApped in pgd.n");return 0;}pud_tmp = pud_offset(pgd_tmp,va); //返回va对应的页上级目录项的线性地址printk(KERN_INFO"pud_tmp = 0x%pn",pud_tmp);printk(KERN_INFO"pud_val(*pud_tmp) = 0x%lxn",pud_val(*pud_tmp));if(pud_none(*pud_tmp)){printk(KERN_INFO"Not mapped in pud.n");return 0;}pmd_tmp = pmd_offset(pud_tmp,va); //返回va在页中间目录中对应表项的线性地址printk(KERN_INFO"pmd_tmp = 0x%pn",pmd_tmp);printk(KERN_INFO"pmd_val(*pmd_tmp) = 0x%lxn",pmd_val(*pmd_tmp));if(pmd_none(*pmd_tmp)){printk(KERN_INFO"Not mapped in pmd.n");return 0;}//在这里,把原来的pte_offset_map改成了pte_offset_kernelpte_tmp = pte_offset_kernel(pmd_tmp,va); //pte指的是 找到表printk(KERN_INFO"pte_tmp = 0x%pn",pte_tmp);printk(KERN_INFO"pte_val(*pte_tmp) = 0x%lxn",pte_val(*pte_tmp));if(pte_none(*pte_tmp)){ //判断有没有映射printk(KERN_INFO"Not mapped in pte.n");return 0;}if(!pte_present(*pte_tmp)){printk(KERN_INFO"pte not in RAM.n");return 0;}pa = (pte_val(*pte_tmp) & PAGE_MASK) ;//物理地址的计算方法printk(KERN_INFO"virt_addr 0x%lx in RAM Page is 0x%lx .n",va,pa);//printk(KERN_INFO"contect in 0x%lx is 0x%lxn",pa,*(unsigned long *)((char *)pa + PAGE_OFFSET));return 0;}static void __exit find_pgd_exit(void){printk(KERN_INFO"Goodbye!n");}module_init(find_pgd_init);module_exit(find_pgd_exit);MODULE_LICENSE("GPL");【Linux内存管理之MMU的过程】运行结果如下:

Linux内存管理之MMU的过程

文章插图
可以看出虚拟地址ffff99b488d48000对应的物理地址是80000000c8d48000 。这个过程也是mmu的过程 。
 
小结我相信你已经对cpu通过MMU访问内存的本质有所掌握(还是不理解的话不要说认识我),而且通过linux的一个实验,对其软件模拟流程也有所感性的认识 。下一篇我们正式进入内存管理的大门——linux内存管理 。




推荐阅读