TLB flush考古、追溯
为什么要刷 TLB?
写在前面,简单说明一些为什么要刷 TLB?能看到这里的小伙伴,大概率是知道什么是 TLB,TLB 就是一块简单的 cache,但是又与 cache 不同,比如一致性。常有 cache coherence(方便描述,后面直接简写成 CC),但是不见有 TLB coherence。最简单来看,支持 CC,我们就理解成硬件会自动同步 cache 中被修改的数据,保证与其他 cache 中的备份数据,以及内存中的数据的一致性。比如 core 0 上的 cache 数据被更新了,硬件会自动失效其他 core 上相同的备份数据。假如 TLB 与 cache 一样,支持 CC,那么如果 core 0 上的 TLB entry 被失效后,硬件会自动失效其他核心上的 TLB entry(这个 CC 看上去,和 Aarch64 上 TLB flush 的硬件实现有点相似)。
回到正题,由于 TLB 并不支持 CC,所以当内存的数据发生变化时,硬件上并不会失效相应的 TLB entry,这些已经过期的 TLB entry,称之为 stale TLB,如果发生读访问,可能会访问到被释放甚至是其他进程的数据,如果发生写操作,轻则进程异常,重则数据丢失。来个严肃的例子,进程A munmap 一段后备是文件的地址(比如表格 A,写全 A),由于某种不知名的原因没有刷 TLB,直接返回,且被切换到其他子线程 B,子线程申请到了刚才虚拟地址写表格 B(写全 B),这个时候可能会出现数据写串的情况,表格 A 里边出现了完全不可能的字母 B...
因此,在修改 PTE 后,且在虚拟地址空间还未再次分配前,尽快刷新 stale TLB,避免 ABA 问题。
最理想的情况,我们清理了一个 PTE,执行一个 TLB flush 指令,硬件会自动取 snoop 相关的 TLB entry 并失效,等下次访问这个...
内存怪谭
写在前面
本系列主要用于记录Linux内核内存BUG相关的故事,以及一些内存奇闻轶事。
shmem - sleep in atomic context
这个BUG在我们龙蜥cloud-kernel的4.19中用stress-ng压测复现。通常情况下,这个BUG对系统稳定性影响并不是很大,仅仅在CONFIG_PREEMPT_COUNT打开的情况下才会,而这个config属于debug类型,一般情况下不会打开。接下来,就分享一下这个怪异的BUG是如何在CONFIG_PREEMPT_COUNT打开的情况下触发的。
首先
#ifdef CONFIG_PREEMPT_COUNT
#define preempt_disable() \
do { \
>-------preempt_count_inc(); \
>-------barrier(); \
} while (0)
#define preempt_enable() \
do { \
>-------barrier(); \
>-------preempt_count_dec(); \... 透明代码大页:让数据库也能用上2MB大页!
背景
大页技术是操作系统中优化内存访问延迟的一种技术,其优化原理与CPU TLB硬件有直接关系,而其优化效果不仅受CPU TLB硬件影响,还需要看应用访存特点。只考虑arm和x86两种平台,已知的大页技术包括透明大页、hugetlbfs、16k和64k全局大页。在合适的场景,大页技术可以提升应用性能达10%以上,尤其是针对当前云上应用逐年增长的内存使用趋势,使用大页技术是其中重要的提升“性能-成本”比例的优化手段。透明大页(Transparent Huge Pages,THP)从2011年开始在Linux内核中已经支持起来,其通过一次性分配2M页填充进程页表,避免多次缺页开销,更深层次从硬件角度优化了TLB缺失开销,在最好情况下,对应用的优化效果达到10%左右。除以上优点外,透明大页(主要供堆栈使用)使用过度也会导致严重的内存碎片化、内存膨胀和内存利用率低等问题,这就是当前透明大页没有在数据库中使用的核心原因,只能感叹“卿本巧技,奈何有坑”。
代码大页在透明大页的基础上,将支持扩展到可执行二进制文件,包括进程二进制文件本身、共享库等可执行数据。与透明大页相比,由于代码大页仅将占比较低且有限的可执行文件页部分转换为大页,从根本上避开了内存碎片以及内存不足的问题。与此同时,由于代码类数据和普通堆栈数据访问热度对整体性能影响不同(主要指代码数据或堆栈数据访问缺页一次的性能影响),导致代码类数据使用大页所提升的性能远大于同样分量的透明大页。所以推广和完善代码大页相比透明大页更加简单和容易。
本文主要介绍我们的代码大页方案以及一些实验阶段性能测试。为了方便阅读,在这里简单归纳了一下L...
arm64 TLB 硬件设计
TLB flush 硬件行为
arm64 与 x86 在 tlb flush 差异挺大,比如 flush 的范围、同步机制。x86 的 tlb flush 一般是(1)只处理当前核,其他核是否需要刷有操作系统内核去判断,这算是同步机制的范畴,依赖软件发起 IPI 到需要的核,(2)而 arm64 的 TLBI 的范围是整个共享域,以不成熟的经验,arm64 一般只会设计一个共享域,是否其他核也需要同步,是由硬件上去判断。
先从最简单的场景,TLBI 只刷一个核开始。
首先,TLBI 在硬件上并不是一个高优的指令,在执行 TLBI 时,首先会将其存到具有 4 个条目的后台任务队列 STQ 中,等 MMU 主流水线有空闲周期时,开始处理 STQ 队列中的任务。下面是其中一个条目,命名分别是 stq0-stq3:
always_ff @(posedge clk)
begin: u_mm_stq0_type_q_1_0_grp
if (mm_stq0_en == 1\'b1) begin
mm_stq0_type_q[1:0] <= `PERSEUS_DFF_DELAY l2_cpu_snp_type[1:0];
mm_stq0_va_q[`PERSEUS_MMU_VA] <= `PERSEUS_DFF_DELAY l2_cpu_snp_addr[`PERSEUS_MMU_SNP_VA_RANGE];
mm_stq0_va_valid_q <= `PERSEUS_DFF_DELAY l2_cpu_snp_va_valid_ql;...