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

Posted

技术标签:

【中文标题】如何从 Linux 内核模块中的逻辑地址获取物理地址?【英文标题】:How to get the physical address from the logical one in a Linux kernel module? 【发布时间】:2011-09-09 06:32:09 【问题描述】:

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

所以,我不想发明***,我认为手动重新实现 follow_page 功能并不是很好。

【问题讨论】:

为什么不用mmap()和ioremap()读写物理内存呢?如果这不是你想要的,你能详细说明你的目的吗? 我已经连接了page_fault 处理程序,并尝试在用户页面的分配上进行操作。所以,当异常发生时,我需要确切地知道物理页面地址和大小...... 最简单的答案是没有简单的答案。这是因为用户虚拟地址映射的物理地址的存在/持久性不是给定的;它可以被调出或重新定位,例如随时复制。为了使其“可检查”,必须以某种方式锁定映射,如前所述,例如通过ioremap() 或类似方式,使其永久化。即使您通过 pagedir walk 计算出一个时间点值,您如何确保其他一些内核活动不会立即更改它? 好吧,介绍一下...想象一下,您可以挂钩page_fault 处理程序,并且您的代码的一部分在do_page_fault 之前运行,而另一部分在它之后运行。因此,如您所知,在do_page_fault 之前无法获得#PF,因为中断被禁用。至于刚刚分配的页面在我们仍在异常处理程序中时被调出的可能性,我认为这是非常非常理论的情况,正如您提到的锁定很重要。那么,有了这个假设,有没有一种简单的方法可以将虚拟地址转换为物理地址? 【参考方案1】:

听起来您正在寻找virt_to_phys

【讨论】:

没有。 virt_to_phys 用于内核空间地址,而不是用户空间。 @Ilya:通过阅读您的问题和手册页,我不明白为什么它不起作用。它会给你带来什么? 从描述virt_to_phys函数的内核源代码中,我看到“...返回的物理地址是给定内存地址的物理(CPU)映射。仅使用它才有效对通过 kmalloc 直接映射或分配的地址起作用..." @Ilya:我想我对内存分配不够了解。我知道 kmalloc 可用于分配用户内存,但也许还有其他方法无法使用此功能。不过,这可能值得一试。【参考方案2】:

我认为您可以通过/proc/[pid]/maps(为进程提供虚拟映射)和/proc/[pid]/pagemap(为每个可寻址页面提供虚拟页面到物理页面的映射)的组合,通过间接方法实现虚拟->物理转换.首先,从maps找出你的进程的虚拟地址的映射(这样做是为了你不会搜索pagemap中的每个字节)然后在pagemap中检查所需虚拟地址的物理映射(pagemap是不是文本格式。这里是格式的详细解释Pagemap) 这应该为您提供准确的虚拟-->物理映射

【讨论】:

Hmm.. 页面映射接口似乎不打算在内核中使用。此外,内核文档说:“..pagemap 是内核中的一组新接口(从 2.6.25 开始),它允许用户空间程序通过读取 /proc 中的文件来检查页表和相关信息......”。所以不适合在内核中使用。 @Ilya:好的。即使您获得了一种方法,可以通过该方法在内核中映射虚拟->物理地址,您将如何处理它?对于任何读/写操作,您都只需要使用虚拟地址,因为您无法绕过 MMU。【参考方案3】:

嗯,它可能看起来像这样(从虚拟地址跟随 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 代码本身复制到模块中有何不同? 没错,它只是follow_page 代码的简化版本。您可以尝试直接调用follow_page 或将其代码复制到模块中。

以上是关于如何从 Linux 内核模块中的逻辑地址获取物理地址?的主要内容,如果未能解决你的问题,请参考以下文章

linux进程内存相关

Linux内核之内存管理完全剖析

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

Linux内核空间-理解高端内存

有没有办法从linux内核模块调用用户空间函数?

linux内存池能分配连续物理内存吗