如何查看和刷新 Linux 内核物理页面的内容?

How do I view and flush the contents of a Linux kernel physical page?

我的 Linux 内核模块存在以下问题(没有错误检查的简化示例):

addr = (uint32_t*) mmap(NULL, 4096, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, 0);
for (uint32_t i = 0; i < 1024; i++)
   addr[i] = 0xCCCCDDDDD;
munmap(addr, 4096);

addr = (uint32_t*) mmap(NULL, 4096, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, 0);
for (uint32_t i = 0; i < 1024; i++)
    assert(addr[i] == 0xCCCCDDDDD)
munmap(addr, 4096);

在某些架构(Intel x86)上,这会成功。在其他架构 (ARM) 上,代码最终会命中断言(尽管数组中命中的具体位置各不相同)。

我可以访问此缓冲区的物理页面 (struct page)。我尝试在 vm_operations_struct 的关闭回调期间调用 flush_dcache_page,但这并不能阻止上面的代码命中断言。这很奇怪,因为我还确保在创建映射时将 vm_page_prot 设置为 pgprot_noncachedpgprot_writecombine

三个问题:

1) 我描述的行为可能是什么原因造成的?

2) 有什么方法可以查看 struct page 内存中的实际数据?我知道 kmap 会生成一个内核映射,但它也可能不会写入物理页面并卡在某种缓存中。 kmap 关闭虚拟内存区域时表示内存的某些部分被零填充。我试着用 kmap/kunmap:

写我的幻数
v = kmap(pages[i]);
for (j = 0; j < (PAGE_SIZE / sizeof(uint32_t)); ++j) {
    printk("v[%u] before: 0x%08X ", j, v[j]);
    v[j] = 0xCCCCDDDD;
    printk("v[%u] after: 0x%08X\n", j, v[j]);
}
kunmap(pages[i]);
flush_dcache_page(pages[i]);

这是用户space写的。之前,printk 语句指示缓冲区的某些部分充满了我的幻数,之后它总是如此,即:

v[3] before: 0xCCCCDDDD v[3] after: 0xCCCCDDDD
v[4] before: 0xCCCCDDDD v[4] after: 0xCCCCDDDD
v[5] before: 0xCCCCDDDD v[5] after: 0xCCCCDDDD
v[6] before: 0xCCCCDDDD v[6] after: 0xCCCCDDDD
v[7] before: 0xCCCCDDDD v[7] after: 0xCCCCDDDD
v[8] before: 0xCCCCDDDD v[8] after: 0xCCCCDDDD
v[9] before: 0xCCCCDDDD v[9] after: 0xCCCCDDDD
v[10] before: 0xCCCCDDDD v[10] after: 0xCCCCDDDD
v[11] before: 0xCCCCDDDD v[11] after: 0xCCCCDDDD
v[12] before: 0xCCCCDDDD v[12] after: 0xCCCCDDDD
v[13] before: 0xCCCCDDDD v[13] after: 0xCCCCDDDD
v[14] before: 0xCCCCDDDD v[14] after: 0xCCCCDDDD
v[15] before: 0xCCCCDDDD v[15] after: 0xCCCCDDDD
v[16] before: 0 v[16] after: 0xCCCCDDDD
v[17] before: 0 v[17] after: 0xCCCCDDDD
v[18] before: 0 v[18] after: 0xCCCCDDDD
v[19] before: 0 v[19] after: 0xCCCCDDDD
v[20] before: 0 v[20] after: 0xCCCCDDDD
v[21] before: 0 v[21] after: 0xCCCCDDDD
v[22] before: 0 v[22] after: 0xCCCCDDDD
v[23] before: 0 v[23] after: 0xCCCCDDDD
v[24] before: 0 v[24] after: 0xCCCCDDDD
v[25] before: 0 v[25] after: 0xCCCCDDDD
v[26] before: 0 v[26] after: 0xCCCCDDDD
v[27] before: 0 v[27] after: 0xCCCCDDDD
v[28] before: 0 v[28] after: 0xCCCCDDDD
v[29] before: 0 v[29] after: 0xCCCCDDDD
v[30] before: 0 v[30] after: 0xCCCCDDDD
v[31] before: 0 v[31] after: 0xCCCCDDDD
v[32] before: 0 v[32] after: 0xCCCCDDDD
v[33] before: 0 v[33] after: 0xCCCCDDDD
v[34] before: 0 v[34] after: 0xCCCCDDDD
v[35] before: 0 v[35] after: 0xCCCCDDDD
v[36] before: 0 v[36] after: 0xCCCCDDDD
v[37] before: 0 v[37] after: 0xCCCCDDDD

然而,即使在这些额外的写入之后,用户 space 仍然会遇到表明写入未刷新到物理内存的断言。这就是为什么我想直接检查物理内存。

3) 在给定 struct page 的情况下,是否有任何可靠的方法刷新或使 Linux 内核中的所有缓存无效?

看起来像是未定义的行为:您正在映射 1024 字节

addr = (uint32_t*) mmap(NULL, 1024, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, 0);

然后在循环中访问4096字节。

for (uint32_t i = 0; i < 1024; i++)
    assert(addr[i] == 0xCCCCDDDDD)

所以你的长度不正确,来自 mmap man:

The contents of a file mapping (as opposed to an anonymous mapping; see MAP_ANONYMOUS below), are initialized using length bytes starting at offset offset in the file (or other object) referred to by the file descriptor fd.

正如所怀疑的那样,存在一些缓存问题。事实证明,页面的内容在之前使用时仍在缓存中。挂起的写入和用户空间写入之间存在竞争。解决方案是在内核模块获取页面时刷新缓存。

在我的特定实例中,看起来 flush_dcache_page 是一个空操作。对 ARM 有帮助的是使用 DMA API——即 dma_sync_sg_for_device。