You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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设置,需要手动操作页表与物理页属性:

  1. 分配内存与创建VMA:
    • 使用alloc_pages()或__get_free_pages()分配连续物理页,创建VMA时使用默认的Write-Back保护属性(如PAGE_KERNEL),确保初始状态为可缓存。
  2. 切换单个页面为UC:
    • 获取目标页面的虚拟地址,调用lookup_address()找到对应的页表项(PTE)指针。
    • 调用pte_mkuc()修改PTE的缓存属性为UC,再用set_pte_at()更新页表。
    • 调用flush_tlb_page()刷新TLB,确保CPU立即使用新的页表属性。
    • 调用set_pages_uc()针对该物理页设置全局UC属性,同时触发缓存刷新(避免脏数据残留)。
  3. 恢复为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 10:03:21