如何从 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 内核模块中的逻辑地址获取物理地址?的主要内容,如果未能解决你的问题,请参考以下文章