由SnpUniqueFwd和SnpUnique混合使用引发的思考
一致性与内存屏障:一场在CHI总线上的花活
前几日,在 xhs 上刷到一则颇有意思的讨论:关于SnpUniqueFwd和SnpUnique混合使用的话题,以及这种混用究竟会在底层引发什么灾难性的影响。在讨论这个哑谜之前,先得理清这两个概念的底牌是什么。
Coherence(缓存一致性)和 Consistency(内存一致性)到底是俩兄弟还是父子,真不好分清。咱们拿《王者荣耀》瞎举个例子:Coherence 就像是红蓝 Buff 的存活状态,不管十个玩家的网络延迟有多大,只要红 Buff 被打掉了,所有人最终看到这个野怪坑位肯定都是空的(死磕单一对象);而 Consistency 关乎的是全图的因果律,它就像是英雄的连招顺序。比如对面打野先“交了闪现(事件 A)”,然后才“放出大招秒人(事件 B)”。如果在你的手机屏幕上看到的画面是:自己突然先掉血被秒了,然后对面打野才从墙外闪现进来,这种违背常理的吃书现象就叫因果律崩溃(它管的是多个独立事件在全局的先后呈现顺序)。这个例子不是非常恰到,这两个毕竟有非常明显的层次关系。
在多核架构的世界里,缓存分布在不同的核心上,依赖监听事务去同步。SnpUnique是一种非转发类型的监听请求(Non-forwarding snoop),为了获取缓存行的独占(Unique)副本,它会严谨地要求系统内所有持有该副本的节点立刻将缓存销毁(作废缓存,强制转换为 Invalid (I) 态)。属于妥...
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(); \... 读《High Performing Cache Hierarchies for Server Workloads》
背景
在介绍这篇论文前,先抛出一个问题:Intel平台Haswell到Skylake-SP一个比较重要的变化就是将原来的Inclusive Cache改换为Exclusive Cache,这种调整是出于什么考虑?所以不禁让人想像由于什么原因需要使用Exclusive Cache?当然微架构中的很多问题都是权衡。这篇15年的论文中介绍的背景以及实验就可以理解该演变的其中原因之一。当然先阅读“伴读:Ring Bus VS Mesh”节开胃也未尝不可。
一段Inclusive Cache与Exclusive Cache描述:
- 包含式Cache(Inclusive Cache):拿L1 L2来举例说明,L1的数据一定在L2中,但L2中的数据不一定在L1中。优点是设计复杂度低,是一个天然的Snoop Filter,缺点是会造成空间浪费,需要保证L2/LLC比例;
- 独占式Cache(Exclusive Cache):L1和L2中的数据不重叠。设计复杂,同时需要单独设计一个模块充当Snoop Filter;
- 非包含非独占 NI/NE(Non-inclusive Non-exclusive):没有绝对的Exclusive Cache,表达成Non-inclusive Cache更准确些。
关于NI/NE较详细的说明,这里有一份资料可以参考:http://yuhaozhu.com/CacheMemory.pdf
论文摘要
...透明代码大页:让数据库也能用上2MB大页!
背景
大页技术是操作系统中优化内存访问延迟的一种技术,其优化原理与CPU TLB硬件有直接关系,而其优化效果不仅受CPU TLB硬件影响,还需要看应用访存特点。只考虑arm和x86两种平台,已知的大页技术包括透明大页、hugetlbfs、16k和64k全局大页。在合适的场景,大页技术可以提升应用性能达10%以上,尤其是针对当前云上应用逐年增长的内存使用趋势,使用大页技术是其中重要的提升“性能-成本”比例的优化手段。透明大页(Transparent Huge Pages,THP)从2011年开始在Linux内核中已经支持起来,其通过一次性分配2M页填充进程页表,避免多次缺页开销,更深层次从硬件角度优化了TLB缺失开销,在最好情况下,对应用的优化效果达到10%左右。除以上优点外,透明大页(主要供堆栈使用)使用过度也会导致严重的内存碎片化、内存膨胀和内存利用率低等问题,这就是当前透明大页没有在数据库中使用的核心原因,只能感叹“卿本巧技,奈何有坑”。
代码大页在透明大页的基础上,将支持扩展到可执行二进制文件,包括进程二进制文件本身、共享库等可执行数据。与透明大页相比,由于代码大页仅将占比较低且有限的可执行文件页部分转换为大页,从根本上避开了内存碎片以及内存不足的问题。与此同时,由于代码类数据和普通堆栈数据访问热度对整体性能影响不同(主要指代码数据或堆栈数据访问缺页一次的性能影响),导致代码类数据使用大页所提升的性能远大于同样分量的透明大页。所以推广和完善代码大页相比透明大页更加简单和容易。
本文主要介绍我们的代码大页方案以及一些实验阶段性能测试。为了方便阅读,在这里简单归纳了一下L...
MAP_DENYWRITE:被Linux内核屏蔽的flag
一 背景
谈到MAP_DENYWRITE,可能有些陌生。这个flag很少被用户态开发者关注,其中没有被关注的理由主要是“this flag is ignored by os”,简而言之,操作系统(Linux内核)将会忽略掉用户传入的MAP_DENYWRITE标志。回到MAP_DENYWRITE是什么?与MAP_ANONYMOUS、MAP_SHARED、MAP_PRIVATE等一样,是系统调用mmap()函数为映射目标设置映射方式的一种flag。顾名思义,MAP_DENYWRITE表示这段映射的虚拟地址区间不允许写操作,例如,我们通过open()以可写的方式获取文件句柄,将会返回“ETXTBSY: text file is busy”错误。
本文主要介绍MAP_DENYWRITE相关的内容,例如使用了MAP_DENYWRITE的可执行二进制文件和忽略MAP_DENYWRITE的动态共享库。为了描述简单,后面直接使用EXEC和DSO(Dynamic Shared Object)分别表示可执行二进制文件和动态共享库。为了更加清晰的展示MAP_DENYWRITE作用,设计了实验1和实验2来显示用户态可见的差异。同时,借助实验1和实验2也为引出两个疑问:(a)为什么i_writecount会导致“ETXTBSY: text file is busy”;(b)为什么vim“写-存”DSO触发程序“Segment fault”;其答案分别可以在第三节和第四节获取。最后,第五节设计了最后一个实验对实验3进行补充。
二 MAP_DENYWRITE是什么
...
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;...