如何在Linux内核模块中从逻辑地址获取物理地址?

11

除了手动遍历页面目录项,是否有适当的方法可以通过逻辑地址获取物理地址?我已经在内核源代码中寻找了这个功能,并发现有一个名为follow_page的函数,在内置巨大页和透明巨大页支持方面处理得很好。但它没有被导出到内核模块中(为什么???)......

因此,我不想再造轮子,也认为手动重新实现follow_page功能不是很好。


为什么不使用mmap()和ioremap()来读写物理内存?如果这不是你想要的,请详细说明你的目的。 - Pavan Manjunath
我已经挂钩了 page_fault 处理程序,并尝试在用户分配的页面上进行操作。因此,当异常发生时,我需要确切地知道物理页面地址和大小... - Ilya Matveychikov
1
最简单的答案是没有简单的答案。这是因为用户虚拟地址映射的物理地址的存在/持久性并非必然;它可能会被分页出去或者通过写时复制等方式重新定位。为了使其“可检查”,映射必须以某种方式锁定,例如通过ioremap()之类的方法,以使其永久存在。即使你通过一个页面目录遍历找到了一个特定时间点的值,你又怎么能确保其他内核活动不会在此后对其进行更改呢? - FrankH.
为了引入一些清晰度......想象一下,你可以挂钩page_fault处理程序,你的代码的一部分在do_page_fault之前运行,另一部分在它之后运行。所以,就像你知道的那样,在中断被禁用之前是不可能出现#PF的。至于刚分配的页面在我们仍然处于异常处理程序时被分页出去的概率,我认为这是非常理论化的情况,并且正如你提到的锁定问题一样。因此,在这些假设的情况下,有没有一种简单的方法将虚拟地址转换为物理地址? - Ilya Matveychikov
3个回答

6

好的,这可能看起来像这样(从虚拟地址跟随PTE):

void follow_pte(struct mm_struct * mm, unsigned long address, pte_t * entry)
{
    pgd_t * pgd = pgd_offset(mm, address);

    printk("follow_pte() for %lx\n", address);

    entry->pte = 0;
    if (!pgd_none(*pgd) && !pgd_bad(*pgd)) {
        pud_t * pud = pud_offset(pgd, address);
        struct vm_area_struct * vma = find_vma(mm, address);

        printk(" pgd = %lx\n", pgd_val(*pgd));

        if (pud_none(*pud)) {
            printk("  pud = empty\n");
            return;
        }
        if (pud_huge(*pud) && vma->vm_flags & VM_HUGETLB) {
            entry->pte = pud_val(*pud);
            printk("  pud = huge\n");
            return;
        }

        if (!pud_bad(*pud)) {
            pmd_t * pmd = pmd_offset(pud, address);

            printk("  pud = %lx\n", pud_val(*pud));

            if (pmd_none(*pmd)) {
                printk("   pmd = empty\n");
                return;
            }
            if (pmd_huge(*pmd) && vma->vm_flags & VM_HUGETLB) {
                entry->pte = pmd_val(*pmd);
                printk("   pmd = huge\n");
                return;
            }
            if (pmd_trans_huge(*pmd)) {
                entry->pte = pmd_val(*pmd);
                printk("   pmd = trans_huge\n");
                return;
            }
            if (!pmd_bad(*pmd)) {
                pte_t * pte = pte_offset_map(pmd, address);

                printk("   pmd = %lx\n", pmd_val(*pmd));

                if (!pte_none(*pte)) {
                    entry->pte = pte_val(*pte);
                    printk("    pte = %lx\n", pte_val(*pte));
                } else {
                    printk("    pte = empty\n");
                }
                pte_unmap(pte);
            }
        }
    }
}

你能详细解释一下这段代码吗?它与将follow_page代码直接复制到模块中有什么不同? - Nikratio
没错,这只是“follow_page”代码的简化版本。你可以尝试直接调用“follow_page”,或将其代码复制到模块中。 - Ilya Matveychikov

3

我认为你可以通过间接方法结合/proc/[pid]/maps(提供进程的虚拟映射)和/proc/[pid]/pagemap(为每个可寻址页面提供虚拟页到物理页的映射)实现虚拟->物理翻译。首先,从maps中找出您的进程的虚拟地址映射(这样您就不必在pagemap中搜索每个字节)。然后在pagemap中检查所需虚拟地址的物理映射(pagemap不是文本格式。这里有一个详细的格式说明Pagemap)。 这应该给您精确的虚拟->物理映射。


嗯...看起来pagemap接口并不是用于内核的。此外,内核文档指出:“..pagemap是内核中一个新的(自2.6.25以来)接口集,允许用户空间程序通过读取/proc中的文件来检查页面表和相关信息...”。因此,在内核中使用它是不合适的。 - Ilya Matveychikov
@Ilya:好的。即使你找到了一种方法可以在内核中映射虚拟地址到物理地址,你打算用它做什么呢?无论如何,对于任何读写操作,你都只能使用虚拟地址,因为你无法绕过MMU。 - Pavan Manjunath

0

2
不,virt_to_phys 用于内核空间地址,而不是用户空间。 - Ilya Matveychikov
@Ilya:从阅读你的问题和手册,我不明白为什么它不能工作。它对你返回了什么? - Gabe
从描述virt_to_phys函数的内核源代码中,我看到:“返回的物理地址是给定的内存地址的物理(CPU)映射。只有在直接映射或通过kmalloc分配的地址上使用此函数才有效。” - Ilya Matveychikov
@Ilya:我想我对内存分配的了解还不够。我知道kmalloc可以用来分配用户内存,但也许有其他方法这个函数无法识别。不过尝试一下也许是值得的。 - Gabe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接