在Linux中,
页面全局目录偏移地址(cr3 +索引)可以使用pgd_offset() MACRO计算。
页面上层目录偏移地址可以使用pud_offset() API计算。
页面中间目录偏移地址可以使用 pmd_offset() API 计算。
页表项偏移地址可以使用pte_offset_map()宏计算。
那么,如何获取物理地址呢?
(yellow line in above picture)
有没有计算物理地址的函数或宏?
edit : x86-64 architecture.
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 frame physical address mechanism | offset */
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);
}
Get_pgtable_macro() 打印当前系统分页机制中的一些宏。
通过vmalloc()在内核空间分配内存空间,调用vaddr2paddr()将虚拟地址转换为物理地址。
Vaddr2paddr()执行如下:
通过pgd_offset计算页全局编录项的线性地址pgd,传入内存描述符mm和线性地址vaddr。接下来,打印pgd指向的页面全局目录条目。
通过pud_offset计算页父目录项的线性地址pud,将参数传递给页全局目录项的线性地址pgd和线性地址vaddr。然后打印指向父目录条目的 pud。
通过pmd_offset计算页中间目录项的线性地址pmd,将参数传递给线性地址pud和父目录项的线性地址vaddr。然后打印页面中间引用 pmd 目录条目。
pte_offset_kernel pte_offset_kernel由pte的线性地址计算得出,参数为目录项中间的线性地址pmd的线性地址和vaddr的地址。然后打印pte指向的页表项。
pte_val(*pte)删除页表项,与PAGE_MASK相比较,结果是访问页的物理地址; vaddr &~PAGE_MASK 用于获取线性地址偏移字段;两者还是最终的物理地址计算。
打印实际地址
更新:五级页表: pud_offset(p4d_t *p4d,无符号长地址);
p4d_t* p4d;
p4d = p4d_offset(pgd, vaddr);
if (p4d_none(*p4d) || p4d_bad(*p4d)) {
printk("not mapped in p4d\n");
return -1;
}
pud = pud_offset(p4d, vaddr);
printk("pud_val = 0x%lx\n", pud_val(*pud));
if (pud_none(*pud)) {
printk("not mapped in pud\n");
return -1;
}