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
)。我尝试在flush_dcache_page
的关闭回调期间调用vm_operations_struct
,但这并不妨碍上述代码触及断言。这很奇怪,因为我还确保在创建映射时将vm_page_prot
设置为pgprot_noncached
或pgprot_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]);
这是在用户空间写入之后。之前,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
但是,即使在这些额外写入之后,用户空间仍然会发出断言,表明写入未与物理内存齐平。这就是我想直接检查物理内存的原因。
3)在给定struct page
的情况下,是否有可靠的方法刷新或使Linux内核中的所有缓存无效?
答案 0 :(得分:1)
它看起来像未定义的行为:您正在映射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:
内容 文件映射(与匿名映射相反;请参阅 下面的MAP_ANONYMOUS,使用从...开始的长度字节进行初始化 文件引用的文件(或其他对象)中的偏移量偏移量 描述符fd。
答案 1 :(得分:1)
如所怀疑的,有一些缓存问题正在发生。事实证明,页面的内容仍然在以前使用的缓存中。挂起的写入和用户空间写入之间存在竞争。解决方案是在内核模块获取页面时刷新缓存。
在我的特定实例中,看起来flush_dcache_page是一个无操作。对ARM有帮助的是使用DMA API - 即dma_sync_sg_for_device。