Linux内核中采用了通用的四级分页模型,这种模型不仅适合32位系统也适合64位系统。分页单元是MMU(内存管理单元)中的一部分,它将线性地址转换为物理地址。本文通过一个内核模块程序模拟内核中虚拟地址转换为物理地址的过程,有关分页机制的原理可以参见这里的文章。
static void get_pgtable_macro(void) { printk("PAGE_OFFSET = 0x%lx\n", PAGE_OFFSET); printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT); printk("PUD_SHIFT = %d\n", PUD_SHIFT); printk("PMD_SHIFT = %d\n", PMD_SHIFT); printk("PAGE_SHIFT = %d\n", PAGE_SHIFT); printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD); printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD); printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD); printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE); printk("PAGE_MASK = 0x%lx\n", PAGE_MASK); } static unsigned long vaddr2paddr(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; pgd = pgd_offset(current->mm, vaddr); printk("pgd_val = 0x%lx\n", pgd_val(*pgd)); printk("pgd_index = %lu\n", pgd_index(vaddr)); if (pgd_none(*pgd)) { printk("not mapped in pgd\n"); return -1; } pud = pud_offset(pgd, vaddr); printk("pud_val = 0x%lx\n", pud_val(*pud)); if (pud_none(*pud)) { printk("not mapped in pud\n"); return -1; } pmd = pmd_offset(pud, vaddr); printk("pmd_val = 0x%lx\n", pmd_val(*pmd)); printk("pmd_index = %lu\n", pmd_index(vaddr)); if (pmd_none(*pmd)) { printk("not mapped in pmd\n"); return -1; } pte = pte_offset_kernel(pmd, vaddr); printk("pte_val = 0x%lx\n", pte_val(*pte)); printk("pte_index = %lu\n", pte_index(vaddr)); if (pte_none(*pte)) { printk("not mapped in pte\n"); return -1; } //页框物理地址机制 | 偏移量 page_addr = pte_val(*pte) & PAGE_MASK; page_offset = vaddr & ~PAGE_MASK; paddr = page_addr | page_offset; printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset); printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr); return paddr; } static int __init v2p_init(void) { unsigned long vaddr = 0; printk("vaddr to paddr module is running..\n"); get_pgtable_macro(); printk("\n"); vaddr = (unsigned long)vmalloc(1000 * sizeof(char)); if (vaddr == 0) { printk("vmalloc failed..\n"); return 0; } printk("vmalloc_vaddr=0x%lx\n", vaddr); vaddr2paddr(vaddr); printk("\n\n"); vaddr = __get_free_page(GFP_KERNEL); if (vaddr == 0) { printk("__get_free_page failed..\n"); return 0; } printk("get_page_vaddr=0x%lx\n", vaddr); vaddr2paddr(vaddr); return 0; } static void __exit v2p_exit(void) { printk("vaddr to paddr module is leaving..\n"); vfree((void *)vaddr); free_page(vaddr); }
整个程序的结构如下:
1.get_pgtable_macro()打印当前系统分页机制中的一些宏。
2.通过vmalloc()在内核空间中分配内存,调用vaddr2paddr()将虚拟地址转化成物理地址。
3.通过__get_free_pages()在内核空间中分配页框,调用vaddr2paddr()将虚拟地址转化成物理地址。
4.分别通过vfree()和free_page()释放申请的内存空间。
vaddr2paddr()的执行过程如下:
1.通过pgd_offset计算页全局目录项的线性地址pgd,传入的参数为内存描述符mm和线性地址vaddr。接着打印pgd所指的页全局目录项。
2.通过pud_offset计算页上级目录项的线性地址pud,传入的参数为页全局目录项的线性地址pgd和线性地址vaddr。接着打印pud所指的页上级目录项。
3.通过pmd_offset计算页中间目录项的线性地址pmd,传入的参数为页上级目录项的线性地址pud和线性地址vaddr。接着打印pmd所指的页中间目录项。
4.通过pte_offset_kernel计算页表项的线性地址pte,传入的参数为页中间目录项的线性地址pmd和线性地址vaddr。接着打印pte所指的页表项。
5.pte_val(*pte)先取出页表项,与PAGE_MASK相与的结果是得到要访问页的物理地址;vaddr&~PAGE_MASK用来得到线性地址offset字段;两者或运算得到最终的物理地址。
6.打印物理地址。
pmd = pmd_offset(pud, vaddr); 这句话 都让pmd指向0地址了(arm体系架构),那是不是pgd_offset和pud_offse这2步可以直接去掉了吗
[回复一下]
edsionte 回复:
8 5 月, 2013 at 13:27
@Alias0129, 理论上是可以的。这样的宏是为了体系架构的通用性。
[回复一下]
请问27行出现的变量current是哪来的?
[回复一下]
edsionte 回复:
9 5 月, 2013 at 12:55
@Loggerhead, 全局变量,表示当前进程的进程描述符。你可以通过源码查看改变量的实现。
[回复一下]
Loggerhead 回复:
9 5 月, 2013 at 14:43
@edsionte, 明白了,谢谢!
[回复一下]
这段程序能够在module驱动程序中被运行么?
[回复一下]
本代码需要包含哪些头文件呢?
[回复一下]
可以哈。这个本身就是内核模块程序。
[回复一下]
current->mm是不是应该换成current->active_mm?内核里面实际上使用的应该是active_mm吧,测试的时候发现如果是mm则最后发现会输出 not mapped in pte
[回复一下]