如何从Linux内核中的任何地址获取struct page

28

我有现有的代码,它接收一个struct page *列表,并构建一个描述符表以与设备共享内存。该代码的上层当前期望使用vmalloc或来自用户空间的缓冲区分配的缓冲区,并使用vmalloc_to_page获取相应的struct page *

现在,上层需要处理各种类型的内存,而不仅仅是通过vmalloc获得的内存。这可以是使用kmalloc获得的缓冲区、内核线程堆栈内部的指针或其他我不知道的情况。我唯一的保证是,此上层调用者必须确保在该点映射了问题内存缓冲区(即,在此点上访问buffer [i]对于所有0<=i<size都是有效的)。 如何获取与任意指针相应的struct page*

将其放入伪代码中:

lower_layer(struct page*);
upper_layer(void *buffer, size_t size) {
    for (addr = buffer & PAGE_MASK; addr <= buffer + size; addr += PAGE_SIZE) {
        struct page *pg = vmalloc_to_page(addr);
        lower_layer(pg);
    }
}

现在我需要更改upper_layer以适应任何有效的缓冲区(而不更改lower_layer)。

我找到了virt_to_pageLinux设备驱动程序指示其操作“逻辑地址,而不是来自vmalloc或高内存的内存”。 此外,is_vmalloc_addr测试地址是否来自vmalloc,而virt_addr_valid测试地址是否为有效虚拟地址(供virt_to_page使用;这包括kmalloc(GFP_KERNEL)和内核堆栈)。其他情况呢:全局缓冲区,高内存(虽然我现在可以忽略它),可能还有其他类型,我不知道吗?所以我可以重新阐述我的问题:

  1. 内核中所有内存区域的种类是什么?
  2. 如何区分它们?
  3. 如何获取每个区域的页面映射信息?

如果有影响,该代码正在运行于具有MMU的ARM上,并且内核版本至少为2.6.26。


buffer 的目标是否需要满足任何对齐或大小的要求? - Karmastan
@Karmastan:不,没有对齐约束。较低层将无论如何映射整个页面。我可以使用(干净等效的)buffer &= ~(PAGE_SIZE-1)来启动upper_layer - Gilles 'SO- stop being evil'
5个回答

15

我猜你想要的是一个页面表遍历,类似于(警告,不是实际代码,缺少锁等):

struct mm_struct *mm = current->mm;
pgd = pgd_offset(mm, address);
pmd = pmd_offset(pgd, address);  
pte = *pte_offset_map(pmd, address);  
page = pte_page(pte);

但是你应该非常小心。例如,你得到的kmalloc地址很可能不是页面对齐的。这对我来说听起来像是一个非常危险的API。


2
@Gilles:对于小于4个级别的情况,内核会将它们“折叠”,因此未使用的级别似乎只有一个条目,并且编译器会优化掉额外的调用。 - CesarB
我猜这是因为堆栈地址和kmalloc地址没有按页对齐。 - gby
在现代内核(> 2.6.38)中,您必须考虑到可能存在透明大页面 http://lwn.net/Articles/423584/,因此您需要遍历它们... - Ilya Matveychikov
@Gilles 你解决了这个问题吗?当我尝试翻译堆栈上的(4K对齐)地址时,我也会遇到崩溃。 - Igor R.
1
@IgorR。不,我从未使用堆栈缓冲区运行过那段代码,现在整个驱动程序已经被重写为不同的架构。 - Gilles 'SO- stop being evil'
显示剩余4条评论

7

将地址映射到struct page

在Linux中,有一个要求需要快速地将虚拟地址映射到物理地址,并将struct page映射到它们的物理地址。Linux通过知道全局mem_map数组的位置来实现这一点,因为该全局数组具有指向表示系统中物理内存的所有struct pages的指针。所有架构都使用非常相似的机制来实现这一点,但是为了说明问题,我们仅仔细研究x86。

将物理地址映射到虚拟内核地址

任何虚拟地址都可以通过简单地减去PAGE_OFFSET来转换为物理地址,这本质上就是函数virt_to_phys()和宏__pa()所做的事情:

/* from <asm-i386/page.h> */
132 #define __pa(x)        ((unsigned long)(x)-PAGE_OFFSET)

/* from <asm-i386/io.h> */
 76 static inline unsigned long virt_to_phys(volatile void * address)
 77 {
 78         return __pa(address);
 79 }

显然,反向操作只需添加PAGE_OFFSET即可,该操作由函数phys_to_virt()和宏__va()执行。接下来,我们将看到如何将struct pages映射到物理地址。 有一个例外,virt_to_phys()无法用于将虚拟地址转换为物理地址。 具体而言,在PPC和ARM体系结构上,virt_to_phys()无法用于转换由consistent_alloc()函数返回的地址。在PPC和ARM体系结构上,consistent_alloc()用于返回从非缓存中用于DMA的内存。 内核中所有内存区域都有哪些种类? <--- 点击这里查看。

我已经阅读过那个。我的问题是,LDD3第15章或我的不完全理解中缺少了什么:虚拟地址是什么(不是全局缓冲区的地址,经过实验证明)?如何从虚拟(或任何其他)地址访问页面数据(struct page而不是物理地址)? - Gilles 'SO- stop being evil'
@Gilles:也许这篇文章可以帮助理解什么是虚拟地址virtual memory - Lars

3

对于用户空间分配的内存,您需要使用get_user_pages,它将为您提供与malloc'd内存相关联的页面列表,并增加其引用计数(一旦完成,您需要在每个页面上调用page_cache_release)。

对于vmalloc'd页面,vmalloc_to_page是您的朋友,我认为您不需要做任何事情。


page_cache_release不再可用,请改用put_page - Roi

2

对于64位架构,gby的答案应该做出调整:

 pgd_t * pgd;
 pmd_t * pmd;
 pte_t * pte;
 struct page *page = NULL;
 pud_t * pud;
 void * kernel_address;

 pgd = pgd_offset(mm, address);
 pud = pud_offset(pgd, address);
 pmd = pmd_offset(pud, address);
 pte = pte_offset_map(pmd, address);
 page = pte_page(*pte);

 // mapping in kernel memory:
 kernel_address = kmap(page);

 // work with kernel_address....

 kunmap(page);

1
你可以尝试使用virt_to_page。我不确定它是否符合你的要求,但至少这是一个开始寻找的地方。

3
根据LDDvirt_to_page仅适用于逻辑地址,而不适用于vmalloc缓冲区或高端内存。因此它可能是解决方案的一部分,但我不太确定是否编写了足够健壮的代码。 - Gilles 'SO- stop being evil'

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