linux内核 - 如何获取物理地址(内存管理)?

时间:2016-12-11 19:53:00

标签: linux memory-management kernel

enter image description here

在linux中,

页面全局目录偏移地址(cr3 + index)可以使用 pgd_offset()MACRO计算。

页面上部目录偏移地址可以使用 pud_offset()API计算。

页面中间目录偏移地址可以使用 pmd_offset()API计算。

页面表格输入偏移地址可以使用 pte_offset_map()MACRO计算。

然后,如何获得实际地址? (yellow line in above picture)

是否有函数或MACRO来计算物理地址?

edit : x86-64 architecture.

1 个答案:

答案 0 :(得分:3)

Linux内核使用通用的四页分页模型,它不仅适用于32位系统,也适用于64位系统。寻呼单元是MMU(存储器管理单元)的一部分,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);
    }
  1. Get_pgtable_macro()在当前系统分页机制中打印一些宏。

  2. 通过内核空间内存空间分配中的vmalloc(),调用vaddr2paddr()将转换为虚拟地址物理地址。

  3. 使用vaddr2paddr()将虚拟地址转换为物理地址,方法是在内核空间中使用__get_free_pages()分配帧。
  4. 分别通过vfree()和free_page()释放请求的内存空间。
  5. Vaddr2paddr()执行如下:

    1. 通过pgd_offset计算页面全局商品的线性地址pgd,传入内存描述符mm和线性地址vaddr。接下来,打印由pgd。

    2. 指向的页面全局商品
    3. 通过pud_offset计算页面父目录条目的线性地址pud,将参数传递给页面全局目录条目的线性地址pgd和线性地址vaddr。然后打印引用父目录条目的pud。

    4. 通过pmd_offset计算页面中间目录条目的线性地址pmd,将参数传递给父目录条目的线性地址pud和线性地址vaddr。然后打印引用pmd目录条目的页面中间。

    5. Pte_offset_kernel由线性地址pte计算pte_offset_kernel,该参数为线性地址pmd线性地址和地址vaddr的目录条目的中间位置。然后打印pte。

    6. 指向的页面表项
    7. pte_val(* pte)删除页表项,并且PAGE_MASK阶段和结果是访问页面的物理地址; vaddr& ~PAGE_MASK用于获取线性地址偏移量字段;两个或最终的物理地址计算。

    8. 打印实际地址