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

标签 linux memory-management linux-kernel kernel

enter image description here

在 Linux 中,

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

Page Upper Directory 偏移地址可以使用 pud_offset() API 计算。

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

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

那么,如何获取物理地址呢? (上图中黄线)

有计算物理地址的函数或MACRO吗?

编辑:x86-64 架构。

最佳答案

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);
}
  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_offset_kernel由线性地址pte计算得到,参数为线性地址pmd的目录项中间的线性地址和地址vaddr。然后打印pte指向的页表项。

  5. pte_val(*pte) 取出页表项,与PAGE_MASK相结合,结果是访问页的物理地址; vaddr & ~ PAGE_MASK 用于获取线性地址偏移字段;两者还是最终的物理地址计算。

  6. 打印物理地址

关于linux 内核 - 如何获取物理地址(内存管理)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41090469/

相关文章:

C++,从内存加载数据结构到缓存的延迟

ios - NSDictionary 出现 EXC_BAD_ACCESS 错误

linux - 如何从缓冲区填充分散列表

c - Enter E-100E 以太网卡所需的 RTL-8139 驱动程序 - 无法编译源文件

c - 在 c 中杀死 child 后,尝试将变量从 child 添加到父亲时,fork 给出了奇怪的输出

linux - 扫描图像的 ImageMagick 处理

c - 尝试在C中通过空地址访问数据?

linux - enable_irq_wake 和 enable_irq 的区别

linux - 如何将 echo 的结果分配给 bash 脚本中的变量

c++ - LLVM out of source pass 构建 : Loadable modules not supported (on Linux)