由于内核在不同的CPU上运行,甚至包括目前的64位机器。Linux内核提供了4级页表的管理机制,它可以兼容各种架构的CPU。
一个虚拟地址会被分为5个部分:页全局目录PGD(Page Global Directory),页上级目录PUD (Page Upper Directory),页中间目录PMD(Page Middle Directory,页表PT (Page Table)以及 偏移量offset,其中的表项叫页表项PTE(Page table entry)。也就是说一个线性地址中除去偏移量,分别存放了4级目录表项的索引值。
具体的线性地址翻译成物理地址的过程是:
(1)首先从进程地址描述符中(mm_struct)中读取pgd字段的内容。它就是页全局目录的起始地址;
(2)然后,页全局目录起始地址+页全局目录索引---->页上级目录的起始地址;
(3)页上级目录+页上级目录索引---->页中间目录的起始地址;
(4)页中间目录的起始地址+页中间目录的索引---->页表起始地址;
(5)页表起始地址+索引---->页表项;
(6)从页表项中取出物理页的基址,加上偏移量可以得到最终的物理地址。
以2级页表管理机制做一个原理性的说明,4级页表管理类似:
2级页表管理机制原理:
对于4级页表管理机制:
那么线性地址被分成五部分:
页表:
页表项的集合形成了页表。在一级页表内,页表项连续存放。在虚拟地址转为为物理地址过程中,没访问一次页表就需要访问一次内存。
页表项:
每个页表项的信息分为两部分:页框基地址和属性,对于我们查找物理地址来说有用的部分是页表基地址。
内核相关部分代码分析:
PAGE_SZIE
/* page.h:PAGE_SHIFT determines the page size */ #define PAGE_SHIFT 12 #define PAGE_SIZE (_AC(1, UL) << PAGE_SHIFT) #define PAGE_MASK (~(PAGE_SIZE-1))其中PAGE_SIZE表明了一个page的大小(2^12字节,4K大小)。我们用的是PAGE_MASK为 11111111111111111111000000000000(20位“1”,12位’0‘)实际上能够达到取物理地址基址和屏蔽页内地址的作用。
PGD
/*pgtable-2level.h*/ #define PGDIR_SHIFT 22 #define PGDIR_SIZE (1UL << PGDIR_SHIFT) #define PGDIR_MASK (~(PGDIR_SIZE-1)) #define PTRS_PER_PGD<span style="white-space:pre"> </span>1024 /*pgtable.h*/ #define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1)) /* to find an entry in a page-table-directory */ #define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr))
说明:从源代码可以看出,对于不同平台 以及64位平台,每级页表的偏移量定义都是不相同的,主要用PGD_SHIFT 控制。但是计算原理都一样。
PUD
#define pud_index(address) (((address) >> PUD_SHIFT) & (PTRS_PER_PUD-1)) #define pud_offset(pgd, address) ((pud_t *) pgd)
/*pgtable.h*/ #define pmd_index(address) (((address) >> PMD_SHIFT) & (PTRS_PER_PMD-1)) #define pmd_offset(pud, address) ((pmd_t *) pud + pmd_index(address))
PTE
/*pgtable.h*/ #define pte_index(address) (((address) >> PAGE_SHIFT) & (PTRS_PER_PTE-1)) #define pmd_deref(pmd) (pmd_val(pmd) & _SEGMENT_ENTRY_ORIGIN) #define pte_offset(pmd, addr) ((pte_t *) pmd_deref(*(pmd)) + pte_index(addr)) #define pte_offset_kernel(pmd, address) pte_offset(pmd,address)
/* * These are used to make use of C type-checking.. */ typedef struct { unsigned long pte; } pte_t; typedef struct { unsigned long ste[64];} pmd_t; typedef struct { pmd_t pue[1]; } pud_t; typedef struct { pud_t pge[1]; } pgd_t; typedef struct { unsigned long pgprot; } pgprot_t; typedef struct page *pgtable_t; #define pte_val(x) ((x).pte) #define pmd_val(x) ((x).ste[0]) #define pud_val(x) ((x).pue[0]) #define pgd_val(x) ((x).pge[0])
内核模块实现虚拟地址转换为物理地址代码:
/*get_physic_addr.c */
内核版本:Linux-2.6.35
#include<linux/kernel.h> #include<linux/module.h> #include<linux/init.h> #include<linux/mm.h> #include<linux/mm_types.h> #include<asm/pgtable.h> #include<linux/vmalloc.h> #include<linux/sched.h> static unsigned long vaddr_to_paddr(struct mm_struct *mm,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; // struct mm_struct *mm=current->mm; pgd=pgd_offset(mm,vaddr); /*获得addr对应的pgd的地址*/ if(pgd_none(*pgd) || unlikely(pgd_bad(*pgd))) goto out; pud=pud_offset(pgd,vaddr); if(pud_none(*pud) || unlikely(pud_bad(*pud))) goto out; pmd=pmd_offset(pud,vaddr); if(pmd_none(*pmd) || unlikely(pmd_bad(*pmd))) goto out; pte=pte_offset_kernel(pmd,vaddr); if(pte_none(*pte)) goto out; 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=%lx,paddr=%lx\n",vaddr,paddr); out: return paddr; } static int __init get_physic_init(void) { printk("<1>get_physic_addr Modules running.....\n"); unsigned long vaddr=0; vaddr=(unsigned long)vmalloc(1000*sizeof(char)); if(vaddr==0){ printk("vmalloc failed...\n"); return 0; } vaddr_to_paddr(current->mm,vaddr); vfree((void*)vaddr); return 0; } static void __exit get_physic_exit(void) { printk("<1>get_phyisc_addr Modules exit\n"); } module_init(get_physic_init); module_exit(get_physic_exit); MODULE_LICENSE("GPL");
obj-m:=get_physic_addr.o CURRENT_PATH=$(shell pwd) LINUX_KERNEL_PATH=/usr/src/kernels/$(shell uname -r) all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules clean: make -C $(LINUX_KERNEL_PATH) m=$(CURRENT_PATH) clean
结果:
学习参考:http://edsionte.com/techblog/archives/3435