static int v2p(unsigned long va)
{
unsigned long pa = 0;
struct task_struct *pcb_tmp;
pgd_t *pgd_tmp = NULL;
pud_t *pud_tmp = NULL;
pmd_t *pmd_tmp = NULL;
pte_t *pte_tmp = NULL;
printk(KERN_INFO"PAGE_OFFSET = 0x%lx\n",PAGE_OFFSET);
printk(KERN_INFO"PGDIR_SHIFT = %d\n",PGDIR_SHIFT);
printk(KERN_INFO"PUD_SHIFT = %d\n",PUD_SHIFT);
printk(KERN_INFO"PMD_SHIFT = %d\n",PMD_SHIFT);
printk(KERN_INFO"PAGE_SHIFT = %d\n",PAGE_SHIFT);
printk(KERN_INFO"PTRS_PER_PGD = %d\n",PTRS_PER_PGD);
printk(KERN_INFO"PTRS_PER_PUD = %d\n",PTRS_PER_PUD);
printk(KERN_INFO"PTRS_PER_PMD = %d\n",PTRS_PER_PMD);
printk(KERN_INFO"PTRS_PER_PTE = %d\n",PTRS_PER_PTE);
printk(KERN_INFO"PAGE_MASK = 0x%lx\n",PAGE_MASK);
pcb_tmp = current;
printk(KERN_INFO"pgd = 0x%p\n",pcb_tmp->mm->pgd);
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);
printk(KERN_INFO"pgd_tmp = 0x%p\n",pgd_tmp);
printk(KERN_INFO"pgd_val(*pgd_tmp) = 0x%lx\n",pgd_val(*pgd_tmp));
if(pgd_none(*pgd_tmp))
{
printk(KERN_INFO"Not mapped in pgd.\n");
return 0;
}
pud_tmp = pud_offset(pgd_tmp,va);
printk(KERN_INFO"pud_tmp = 0x%p\n",pud_tmp);
printk(KERN_INFO"pud_val(*pud_tmp) = 0x%lx\n",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);
printk(KERN_INFO"pmd_tmp = 0x%p\n",pmd_tmp);
printk(KERN_INFO"pmd_val(*pmd_tmp) = 0x%lx\n",pmd_val(*pmd_tmp));
if(pmd_none(*pmd_tmp)){
printk(KERN_INFO"Not mapped in pmd.\n");
return 0;
}
pte_tmp = pte_offset_kernel(pmd_tmp,va);
printk(KERN_INFO"pte_tmp = 0x%p\n",pte_tmp);
printk(KERN_INFO"pte_val(*pte_tmp) = 0x%lx\n",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) |(va & ~PAGE_MASK);
printk(KERN_INFO"virt_addr 0x%lx in RAM is 0x%lx .\n",va,pa);
printk(KERN_INFO"contect in 0x%lx is 0x%lx\n",pa, *(unsigned long *)((char *)pa + PAGE_OFFSET));
return 0;
}
对页表项操作的宏
宏名称 说明
pgd_index(addr) 找到线性地址addr 对应的的目录项在页全局目录中的索引(相对位
置)。
pgd_offset(mm, addr) 接收内存描述符地址mm(参见第九章)和线性地址addr作为参数。这个宏
产生地址addr在页全局目录中相应表项的线性地址;通过内存描述符mm内的一个指针可以找到这个页全
局目录。
pgd_offset_k(addr) 产生主内核页全局目录中的某个项的线性地址,该项对应于地址addr
(参见稍后“内核页表”一节)。
pgd_page(pgd) 通过页全局目录项pgd产生页上级目录所在页框的页描述符地址。在两
级或三级分页系统中,该宏等价于pud_page(),后者应用于页上级目录项。
pud_offset(pgd, addr) 参数为指向页全局目录项的指针pgd和线性地址addr。这个宏产生页上级目
录中目录项addr对应的线性地址。在两级或三级分页系统中,该宏产生pgd,即一个页全局目录项的地址。
pud_page(pud) 通过页上级目录项pud产生相应的页中间目录的线性地址。在两级分页
系统中,该宏等价于pmd_page(),后者应用于页中间目录项。
pmd_index(addr) 产生线性地址addr在页中间目录中所对应目录项的索引(相对位置)。
pmd_offset(pud, addr) 接收指向页上级目录项的指针pud和线性地址addr作为参数。这个宏产生目
录项addr在页中间目录中的偏移地址。在两级或三级分页系统中,它产生pud,即页全局目录项的地址。
pmd_page(pmd) 通过页中间目录项pmd产生相应页表的页描述符地址。在两级或三级分
页系统中,pmd实际上是页全局目录中的一项。
mk_pte(p,prot) 接收页描述符地址p和一组访问权限prot作为参数,并创建相应的页表
项。
pte_index(addr) 产生线性地址addr对应的表项在页表中的索引(相对位置)。
pte_offset_kernel(dir, addr) 线性地址addr在页中间目录dir中有一个对应的项,该宏就产生这个对应
项,即页表的线性地址。另外,该宏只在主内核页表上使用(参见稍后“内
核页表”一节)。
pte_offset_map(dir, addr) 接收指向一个页中间目录项的指针dir和线性地址addr作为参数,它产生与
线性地址addr相对应的页表项的线性地址。如果页表被保存在高端存储器中,那么内核建立一个临时内核映
射(参见第八章“高端内存页框的内核映射”一节),并用pte_unmap对它进行释放。pte_offset_map_nested
宏和pte_unmap_nested宏是相同的,但它们使用不同的临时内核映射。
pte_page( x ) 返回页表项x所引用页的描述符地址。
pte_to_pgoff( pte ) 从一个页表项的pte字段内容中提取出文件偏移量,这个偏移量对应着
一个非线性文件内存映射所在的页(参见第十六章“非线性存储器映射”一
节)。
pgoff_to_pte(offset ) 为非线性文件内存映射所在的页创建对应页表项的内容。
这里罗列最后一组函数来简化页表项的创建和撤消。
当使用两级页表时,创建或删除一个页中间目录项是不重要的。如本节前部分所述,页中间目录仅含有一个
指向下属页表的目录项。所以,页中间目录项只是页全局目录中的一项而已。然而当处理页表时,创建一个
页表项可能很复杂,因为包含页表项的那个页表可能就不存在。在这样的情况下,有必要分配一个新页框,
把它填写为0,并把这个表项加入。
如果PAE被激活,内核使用三级页表。当内核创建一个新的页全局目录时,同时也分配四个相应的页中间目
录;只有当父页全局目录被释放时,这四个页中间目录才得以释放。
当使用两级或三级分页时,页上级目录项总是被映射为页全局目录中的一个单独项。
与以往一样,表28
中列出的函数描述是针对80x86构架的。
虚拟地址转换为物理地址
应用程序只能提供一个虚拟地址,也可以通过如下方法获取物理地址。
Linux采用页表的概念来管理虚拟空间,内核在处理虚拟地址时都必须将其转换为物理地址,然后处理器才能够访问。虚拟地址可以通过Linux的页表操作宏逐层查找到物理地址,简单来说需要将虚拟地址分段,每段地址都作为索引指向页表,最后一级页表指向物理地址。
Linux在2.6.11以后版本为了兼容各种处理器,采用四级页表结构:
PGD:Page Global Directory,页全局目录,是顶级页表。
PUD:Page Upper Directory,页上级目录,是第二级页表
PMD:Page Middle Derectory,页中间目录,是第三级页表。
PTE:Page Table Entry,页面表,最后一级页表,指向物理页面。
可以通过数据结构mm_struct访问PGD找到物理页面,如图4-8,根据页表寻找物理地址的流程见4-9。
图 Linux采用的4级页面
简化的转换代码如下:
static int vir2phy(unsigned long va)
{
struct task_struct *pcb_tmp;
pcb_tmp = current;
pgd_tmp = pgd_offset(pcb_tmp->mm,va);
pud_tmp = pud_offset(pgd_tmp,va);
pmd_tmp = pmd_offset(pud_tmp,va);
pte_tmp = pte_offset_kernel(pmd_tmp,va);
pa = (pte_val(*pte_tmp) & PAGE_MASK) |(va & ~PAGE_MASK);
return pa;
}
pgd_offset(mm, addr) 接收内存描述符地址mm和线性地址addr作为参数。这个宏产生地址addr在页全局目录中相应表项的线性地址;
通过内存描述符mm内的一个指针可以找到这个页全局目录。
pud_offset(pgd, addr) 参数为指向页全局目录项的指针pgd和线性地址addr。这个宏产生页上级目录中目录项addr对应的线性地址。在两级或三级分页系统中,该宏产生pgd,即一个页全局目录项的地址。
pmd_offset(pud, addr) 接收指向页上级目录项的指针pud和线性地址addr作为参数。这个宏产生目录项addr在页中间目录中的偏移地址。在两级或三级分页系统中,它产生pud,即页全局目录项的地址。
pte_offset_kernel(dir, addr) 线性地址addr在页中间目录dir中有一个对应的项,该宏就产生这个对应项,即页表的线性地址。另外,该宏只在主内核页表上使用。