pgprot_noncached()与set_memory_uc()的区别及页级缓存选择性禁用方法
内核页级缓存控制的疑问与解决方案
set_memory_uc()的实际作用
你测试中看到移除set_memory_uc()后内存仍为不可缓存,是因为pgprot_noncached()会修改VMA的保护属性,内核在建立该VMA的页映射时,会自动生成带有UNCACHABLE(UC)属性的页表项。但set_memory_uc()并非毫无用处:
- 它直接作用于物理页的全局缓存属性,如果分配的物理页之前被其他映射使用过(比如默认的Write-Back模式),
set_memory_uc()会强制刷新该页的缓存,清理脏数据,避免后续访问出现数据不一致。 - 在部分旧内核版本或特殊架构场景下,VMA的
pgprot设置可能不会覆盖已存在的页表项,此时set_memory_uc()需要用来强制修正物理页的缓存属性。
为何多数资料要求同时调用两个函数
核心原因是兼容性与数据一致性:
- 不同内核版本对
pgprot_noncached()的实现有差异,早期内核可能不会自动同步物理页的缓存属性,必须通过set_memory_uc()来补全修改。 - 物理页可能存在复用情况,若之前的映射使用了WB缓存模式,仅通过VMA设置UC属性可能不会触发缓存刷新,残留的脏数据会导致读写错误,
set_memory_uc()会强制完成缓存刷新与属性同步。 - 部分架构(如x86)中,页表项的缓存属性需要同时匹配VMA设置和物理页的全局属性,两者配合才能确保所有访问路径都遵循UC规则。
实现VMA内页级选择性禁用缓存的方法
要在同一段VMA中针对单个页面切换缓存属性,不能依赖VMA的全局pgprot设置,需要手动操作页表与物理页属性:
- 分配内存与创建VMA:
- 使用
alloc_pages()或__get_free_pages()分配连续物理页,创建VMA时使用默认的Write-Back保护属性(如PAGE_KERNEL),确保初始状态为可缓存。
- 使用
- 切换单个页面为UC:
- 获取目标页面的虚拟地址,调用
lookup_address()找到对应的页表项(PTE)指针。 - 调用
pte_mkuc()修改PTE的缓存属性为UC,再用set_pte_at()更新页表。 - 调用
flush_tlb_page()刷新TLB,确保CPU立即使用新的页表属性。 - 调用
set_pages_uc()针对该物理页设置全局UC属性,同时触发缓存刷新(避免脏数据残留)。
- 获取目标页面的虚拟地址,调用
- 恢复为WB模式:
- 类似地,使用
pte_mkwb()修改PTE属性,调用flush_tlb_page(),再用set_pages_wb()恢复物理页的缓存属性。
- 类似地,使用
示例代码片段(x86架构,内核5.15):
// 假设addr是目标页面的虚拟地址 struct page *page = virt_to_page(addr); pte_t *pte = lookup_address((unsigned long)addr, &level); if (pte && !pte_none(*pte)) { // 切换为UC set_pte_at(&init_mm, (unsigned long)addr, pte, pte_mkuc(*pte)); flush_tlb_page(&init_mm, (unsigned long)addr); set_pages_uc(page, 1); }
内容的提问来源于stack exchange,提问作者Joao Oliveira
相关产品推荐
相关产品推荐

