TLB 是页表项的物理 cache,用于加速虚拟地址到物理地址的转换。CPU 在访问一个虚拟地址时,首先会在 TLB 中查找,如果找不到对应的表项,那么就称之为 TLB miss,此时就需要去内存里查询页表,如果页表项是合法的,那么就会把它添加到 TLB 中。如果内核修改了页表,那么就需要主动的去清空一下当前的 TLB。
在 ARM64 上,清空 TLB 的指令是 TLBI,在 Linux 中,与 TLB 清空相关的宏都在 arch/arm64/include/asm/tlbflush.h 文件中定义。清空 TLB 的一般流程在文件开头的注视里有说明:
* DSB ISHST // Ensure prior page-table updates have completed
* TLBI ... // Invalidate the TLB
* DSB ISH // Ensure the TLB invalidation has completed
* if (invalidated kernel mappings)
* ISB // Discard any instructions fetched from the old mapping
在 TLBI 指令执行前后需要几个内存屏障指令的辅助,来防止在 TLB 清空过程中发生的不确定情况。主要的几个宏如下所示:
名称 | 说明 |
---|---|
flush_tlb_all() | 内核+用户空间的地址在所有的CPU上都清空 |
flush_tlb_mm(mm) | 把用户空间的地址在所有的CPU上都清空 |
flush_tlb_range(vma, start, end) | 用户空间的一段范围地址清空 |
flush_tlb_kernel_range(start, end) | 内核空间的一段范围地址清空 |
flush_tlb_page(vma, adds) | 用户空间的一个 page 地址映射清空 |
以flush_tlb_kernel_range
为例:
flush_tlb_kernel_range
是 Linux 内核中的一个函数,用于使一段范围内的翻译后备缓冲区 (TLB) 条目失效。TLB 是一个缓存,用于存储最近的从虚拟内存地址到物理内存地址的转换,这有助于加快使用虚拟内存系统的内存访问速度。
flush_tlb_kernel_range
的主要作用是确保指定范围内的 TLB 中任何过时或无效的条目都被移除。当内核修改页表(例如在内存管理操作中)时,需要将这些变化反映到 TLB 中。start
:要刷新的范围的起始虚拟地址。end
:要刷新的范围的结束虚拟地址。flush_tlb_kernel_range
的实现与具体的 CPU 架构相关,因为不同的 CPU 架构(例如 x86,ARM)的 TLB 结构和操作方式不同。当内核更新其页表时,例如重新映射内核内存、添加新页面或更改访问权限时,需要使受影响的 TLB 条目失效,以确保 CPU 不会使用过时的转换。如果不这样做,可能会导致内存访问错误,带来潜在的安全风险或系统不稳定性。
一个函数原型的示例可能如下所示(实际实现细节因架构而异):
void flush_tlb_kernel_range(unsigned long start, unsigned long end);
总之,flush_tlb_kernel_range
是维护 TLB 一致性的重要函数,用于内核内存管理的上下文中。它确保内核页表中的变化准确反映到 TLB 中,从而防止陈旧条目导致错误的内存访问。
flush_tlb_kernel_range
的实现是与具体的 CPU 架构密切相关的。不同的架构有不同的 TLB 管理方式,因此该函数的实现方式也会有所不同。下面我们以 x86 架构为例,简单介绍一下 flush_tlb_kernel_range
的可能实现方式。
在 x86 架构上,TLB 刷新可以通过重新加载控制寄存器 CR3 来实现,这会导致整个 TLB 被刷新。对于特定范围的 TLB 刷新,可以使用页表条目无效(INVLPG)指令。
以下是一个可能的 flush_tlb_kernel_range
实现,针对 x86 架构:
#include <linux/mm.h>
#include <linux/smp.h>
#include <asm/tlbflush.h>
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
unsigned long addr;
// 对于每个地址,使用INVLPG指令
for (addr = start; addr < end; addr += PAGE_SIZE) {
__flush_tlb_one(addr);
}
// 同时在多处理器系统上处理
smp_mb();
smp_call_function(flush_tlb_mm_range, ¤t->mm, 1);
}
INVLPG
指令刷新指定范围内的每个页。__flush_tlb_one(addr)
是一个内联汇编函数,使用 INVLPG
指令刷新指定的地址。smp_mb()
确保内存屏障,确保前面的 TLB 刷新操作在后续操作之前完成。smp_call_function(flush_tlb_mm_range, ¤t->mm, 1)
在多处理器系统上调用,以确保所有 CPU 都执行 TLB 刷新操作。对于不同的架构,具体实现方式会有所不同,但总体的实现框架大致如下:
可以参考 Linux 内核源代码中的实际实现。例如,可以查看 arch/x86/include/asm/tlbflush.h
文件中的实现细节。不同架构的实现可以在相应的架构目录中找到。
flush_tlb_kernel_range
的具体实现依赖于 CPU 架构,x86 架构通常通过 INVLPG
指令和 CR3 寄存器重新加载来实现 TLB 刷新。上述示例展示了一个基本实现,具体实现应参考 Linux 内核源码,并根据具体需求和架构进行调整。