Linux kernel 如何从Linux内核中的任何地址获取结构页

Linux kernel 如何从Linux内核中的任何地址获取结构页,linux-kernel,virtual-memory,Linux Kernel,Virtual Memory,我有一个现有的代码,它获取一个struct page*列表,并构建一个描述符表来与设备共享内存。该代码的上层当前期望使用vmalloc或从用户空间分配一个缓冲区,并使用该缓冲区获取相应的struct page* 现在上层需要处理各种内存,而不仅仅是通过vmalloc获得的内存。这可能是通过kmalloc获得的缓冲区,内核线程堆栈中的指针,或者我不知道的其他情况。我唯一能保证的是,这个上层的调用者必须确保所讨论的内存缓冲区在该点映射到内核空间(即,访问buffer[I]是有效的)对于所有0您都可以

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


现在上层需要处理各种内存,而不仅仅是通过
vmalloc
获得的内存。这可能是通过
kmalloc
获得的缓冲区,内核线程堆栈中的指针,或者我不知道的其他情况。我唯一能保证的是,这个上层的调用者必须确保所讨论的内存缓冲区在该点映射到内核空间(即,访问
buffer[I]是有效的)
对于所有
0您都可以尝试。我不确定这是否是您想要的,但至少它是一个开始寻找的地方。

我想您想要的是一个页面表漫游,类似(警告,不是实际代码,锁定丢失等):


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

将地址映射到结构页面

Linux需要有一种将虚拟地址映射到物理地址以及将结构页映射到其物理地址的快速方法。Linux通过了解全局内存映射数组在虚拟内存和物理内存中的位置来实现这一点,因为全局数组具有指向表示物理内存的所有结构页的指针所有的体系结构都是通过非常相似的机制实现这一点的,但是,为了便于说明,我们将只仔细研究x86

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

任何虚拟地址都可以通过简单地减去PAGE_OFFSET转换为物理地址,这基本上就是函数virt_to_phys()与宏u pa()的作用:


/*from对于用户空间分配内存,您需要使用
get_user_pages
,这将为您提供与malloc'd内存相关联的页面列表,并增加它们的引用计数器(处理完这些页面后,您需要在每个页面上调用
page_cache_release


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

对于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);

buffer
的目标是否需要任何对齐或大小要求?@Karmastan:不,没有对齐约束。下层将映射整个页面。我可以使用(干净的等价物)
buffer&=~(PAGE\u size-1)启动
上层
。根据,
virt_to_page
仅适用于逻辑地址,不适用于
vmalloc
缓冲区或高内存。因此,这可能是解决方案的一部分,但我不太清楚,无法确信我编写的代码足够健壮。嗯,可能(现在开始阅读和测试)。对
地址的限制是什么?(在一天结束时,缓冲区需要与设备共享,这意味着它跨越的所有页面都将被共享。代码现在适用于显然更难的情况
vmalloc
。这不是一个外部API,我的问题中的上层/下层只是我们的内部设计。)之所以授予悬赏,是因为这有助于我更好地理解
vmalloc_to_page
在做什么。然而,在我的测试中,我只在取消引用
pte\u offset_map
时触发了一个oops:“无法处理虚拟地址340009f4处的内核分页请求”,用于调用函数堆栈上的地址或由
kmalloc
@Gilles返回的地址:可能是因为内核本身使用了大页面(例如2MB页面)?在这种情况下,执行页面表遍历的方式会有所不同(可能不会给您结构页面-如果存在的话).
vmalloc_to_page
可能不在乎,因为它知道它使用的是正常的4K页面。可以肯定的是,请查看每个页面表级别上的页面表属性-如果它是一个大页面,其中一个级别必须有这样的属性(否则硬件本身无法知道)@Gilles:少于4级时,内核会“折叠”它们,所以未使用的级别似乎只有一个条目,编译器会优化掉额外的调用。@IgorR。不,我从来没有让代码使用堆栈缓冲区运行,现在整个驱动程序都是用不同的体系结构从头重写的。我已经读过了。我想问的是LDD3 ch.15或我不完整的unde中缺少什么了解它:什么是虚拟地址(实验上不是全局缓冲区的地址)?如何从虚拟(或任何其他)地址获取页面数据(结构页面,而不是物理地址)?@Gilles:也许这篇文章有助于理解虚拟地址是什么
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);
/* 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 }
 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);