ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

2023年Linux内核6.1至6.7十大革新:io_uring、EEVDF与BCACHEFS全解析

2023年Linux内核6.1至6.7十大革新:io_uring、EEVDF与BCACHEFS全解析

2023年,Linux内核一口气从6.1走到6.7,平均不到三个月一个大版本,放在整个内核历史上都算高频。这一年里,主线并入的不只是补丁,还有一批能真正改变开发者和运维者做事方式的硬核功能:Rust语言开始在内核里“干正事”、调度器核心从CFS切换到EEVDF、io_uring继续向异步IO的终极形态逼近、BCACHEFS终于在吵了十几年后进了主线、x86硬件影子栈上线……我把这一年里最值得关注的十大技术革新拆开讲一遍。这篇不是逐条念更新日志,而是站在“这功能解决了什么、内核究竟怎么干的、我在真实环境里怎么用怎么避坑”的角度聊,适合做底层开发、SRE、存储相关以及所有对内核新特性敏感的同学参考。

1. 为什么是这十项革新?我自己的筛选逻辑

1.1 从 6.1 到 6.7:一年里的版本快照

先看一张时间线,方便后面对号入座。2023年发布的版本分别是6.1(跨年版本)、6.2、6.3、6.4、6.5、6.6、6.7,其中6.1虽然是2022年底发布的,但大量2023年的后续功能都建立在这棵树上。

版本发布时间(约)当年最值得记住的特性
6.12022年12月Rust基础支持合入、Maple Tree落地、多代LRU
6.22023年2月内核模块签名强化、更多Rust抽象
6.32023年4月调度器继续重构、新的io_uring特性
6.42023年6月BBRv3合入、io_uring提供者注册
6.52023年8月Maple Tree全面接管VMA、注册大页内存
6.62023年10月EEVDF合入、x86影子栈、sched_ext初现
6.72023年12月BCACHEFS进主线、大量io_uring完善

这张表里有个很有意思的信号:真正让我觉得“格局变了”的几件事,大多集中在6.4到6.7这半年。前半年更像是在为后面的大改动铺路。

1.2 我挑选十大革新的三个维度

内核社区一年可能有几百个新特性,但能叫“革新”的必须同时满足三件事:解决了长期痛点、改变了现有生态的做事方式、至少未来五年内会持续产生影响。我按四条主线去筛:

  • 性能主线:io_uring、EEVDF、BBRv3,分别卡在异步IO、CPU调度、网络拥塞这三个性能关键点上。
  • 安全主线:硬件影子栈、LRNG、Rust进内核,覆盖了内存安全、随机数可信、语言级安全三层。
  • 存储与内存架构:BCACHEFS、Maple Tree,一个是文件系统的“新物种”,一个是内核自身数据结构的换代。
  • 生产力与可编程性:BPF持续演进、NUMA优化,这俩不像前面那么“显眼”,但对基础设施软件的影响非常深远。

这个筛选标准可能跟官方release note不完全一致,但我觉得对从业者来说,这十项的“利用率”是最高的。

2. 性能三驾马车:io_uring、EEVDF、BBRv3

2.1 io_uring:异步IO不再只是少数人的玩具

io_uring不是2023年才出现的新东西,它从5.1开始就在主线里,但2023年绝对是它从“能用”到“好用”的关键一年。

先说原理,io_uring的核心思路是绕开系统调用的开销。传统的read/write每一次都要陷入内核,高IOPS场景下系统调用本身成了瓶颈。io_uring在用户态和内核态之间创建两个共享的环形缓冲区:SQ用于提交请求,CQ用于收割完成结果。业务代码把一批IO请求直接写进SQ,通过一次io_uring_enter系统调用批量提交,内核处理后把结果写入CQ,用户态轮询或者等待。

2023年的几个关键改进我得提一下:

  • 提供者注册(registered providers):允许更底层的IO引擎直接注册进ring,减少中间层拷贝和上下文切换。
  • 注册大页内存(registered huge pages):io_uring可以预先注册一块大页内存池,避免运行时反复做page pin,这对存储服务很实用。
  • 对多线程共享ring的支持更加成熟:多个线程可以共用一个ring,也可以各自持有ring并对接同一个后端,调度灵活性大幅度提升。

我实际体验是,用liburing写一个高IOPS的随机读程序,比普通的pread在4K随机读场景下延迟能降低一半以上,CPU占用率更是肉眼可见地下降。下面这个例子很基础,但能说明使用方式:

#include <liburing.h> struct io_uring ring; io_uring_queue_init(32, &ring, 0); struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); io_uring_prep_read(sqe, fd, buf, len, offset); io_uring_submit(&ring); struct io_uring_cqe *cqe; io_uring_wait_cqe(&ring, &cqe); // 处理 cqe->res io_uring_cqe_seen(&ring, cqe);

需要注意:io_uring对内核版本敏感,老内核上很多高级特性根本编译不过去。如果你在6.1之前的内核上跑,建议至少升级到6.4以后再考虑使用注册大页之类的特性。另外,容器环境下要确认io_uring_setup权限是否被seccomp或cgroup白名单拦掉,很多安全加固的容器会禁止这个系统调用。

2.2 EEVDF:调度器从“公平”走向“确定性”

CFS在Linux内核里统治了十几年,它的核心思路是维护一个虚拟运行时间,让每个任务尽量按权重分摊CPU。但这个模型的毛病在于:它试图让所有任务“同时公平”,一旦系统里出现大量短任务和长任务混跑,延迟可能变得不可控。某个进程明明该醒了,却因为虚拟时间欠账要排队。

EEVDF(Earliest Eligible Virtual Deadline First)解决的问题就是延迟确定性。它的核心是给每个调度实体维护一个“虚拟截止时间”,每次选择“已经eligible(有资格运行)”且“截止时间最早”的任务。相比CFS的“最小虚拟时间”,EEVDF对短任务更友好,交互式应用在负载抖动时更容易被及时唤醒。

从实现上说,EEVDF仍然保留CFS的红黑树和各种fairness记账,所以升级不会让调度器推倒重来。但它换掉了选择任务的排序键和准入逻辑,带来的行为差异是真实的。

坦白说,EEVDF并不是“默认性能暴涨”的银弹。它在高并发短任务场景受益明显,比如Web服务器、网关、编译任务等;在纯CPU密集的长任务场景,和CFS的差异没有想象中大。对云原生节点这种“混部”环境,EEVDF配合cgroup调度,能让优先级更高的短任务抢到更多时间片。

我在生产环境调试的一个经验是:升级内核后如果发现某些流量型服务的P99延迟波动,先看是不是调度器行为变化导致的。对比CFS和EEVDF,最直接的方法是跑一个混合负载测试:

# 模拟CPU密集型长任务 stress-ng --cpu 4 --timeout 60s & # 同时跑延迟敏感短任务 ./latency_probe --interval=100ms

观察latency_probe的P99。EEVDF在多数情况下会让短任务的P99更稳定,但前提是cgroup的权重配置合理。如果之前你在CFS时代依赖nice或者cpu.shares做了精细调优,升级后需要重新校准,EEVDF对权重的响应曲线和CFS不完全一样。

2.3 BBRv3:拥塞控制的新默认候选

TCP拥塞控制是网络性能的隐形天花板。CUBIC靠丢包判断拥塞,BBRv1/v2靠实时带宽和往返时延探测。BBRv3在2023年进入6.4主线,是Google前两版在真实互联网流量上跑了大量实验后的收敛版本。

BBRv3改了什么?核心是把“容忍丢包”和“公平性”重新做了平衡。之前的BBR版本有一个广受吐槽的问题:它在深队列场景下会抢占过多带宽,影响同链路里的CUBIC流量。BBRv3加入更激进的带宽收敛逻辑,当检测到丢包或显式拥塞通知(ECN)时,会更快地降速,同时保留高带宽利用率的优点。

实际配置非常简单:

# 先确认内核支持 sysctl net.ipv4.tcp_available_congestion_control # 临时切换 sysctl -w net.ipv4.tcp_congestion_control=bbr # 永久生效 echo "net.ipv4.tcp_congestion_control = bbr" >> /etc/sysctl.d/99-bbr.conf sysctl -p /etc/sysctl.d/99-bbr.conf

注意:BBR是发送端算法,只有通信双方中你的服务器开启了才有效。跨公网传输时,如果对端用的是Windows默认的CUBIC变体,效果会打折扣,但依然比单纯CUBIC好。

我在某跨地域数据同步场景里实测,BBRv3相比CUBIC能把大文件传输时间缩短大概15%到25%,尤其在中等丢包率(0.1%到1%)的链路上收益最明显。丢包率超过5%的极差链路,BBRv3也没办法,拥塞算法不是物理层救世主。另外一个注意点:如果你们公司网络设备启用了ECN标记,BBRv3对ECN的响应比BBR v1更灵敏,建议配合net.ipv4.tcp_ecn=1一起使用,但这需要整个链路设备都支持。

3. 安全侧变革:Shadow Stack、LRNG、Rust接管

3.1 x86 Shadow Stack:当CPU主动阻止栈溢出攻击

栈溢出攻击的老套路是覆盖函数的返回地址,让程序跳转到攻击者控制的代码。过去防御思路是栈保护(stack canary)、ASLR、CFI这类软件方案,都有各自的绕过方法。2023年6.6内核合入了x86的Shadow Stack(SHSTK),这是Intel CET(Control-flow Enforcement Technology)的一部分,思路是把“返回地址”放到硬件隔离的影子栈里,普通写操作碰不到。

原理上,CPU在call时同时压栈到普通栈和影子栈,在ret时比较两个栈顶的返回地址,不一致就触发#CP异常。这样即使攻击者通过缓冲区溢出改掉了普通栈上的返回地址,CPU也会发现和影子栈对不上。

内核里启用这个功能,前提是:

  • CPU支持:grep -o shstk /proc/cpuinfo
  • 内核6.6以上,且开启CONFIG_X86_USER_SHADOW_STACK
  • 用户态程序和动态链接器需要适配,glibc 2.39+对SHSTK有较好支持

我们曾经在一台新CPU机器上试着全量启用SHSTK,结果一批老版本动态库在加载时直接崩溃。原因不难理解,老的汇编代码如果有非标准的栈操作(比如手动拼返回地址、setjmp/longjmp实现特殊),影子栈机制就会把它当成“控制流异常”。如果不是从头维护全部代码,建议先在非核心服务上灰度。检查内核和CPU支持:

grep -o shstk /proc/cpuinfo | head -1 uname -r cat /boot/config-$(uname -r) | grep X86_USER_SHADOW_STACK

启用影子栈之后,对ROP类攻击的防御效果是实打实的,属于“就算漏洞存在,你也很难稳定利用”的那种兜底能力。成本是每次函数调用/返回多一次影子栈访问,实测平均性能开销在2%到5%,对大部分Web/存储服务可以接受。

3.2 随机数生成器重写:LRNG终于摆脱历史包袱

内核随机数一直是安全基座,早年的/dev/random因为“熵耗尽”会阻塞,后来换成基于熵池的伪随机生成器后,又暴露出“熵估计不透明”的问题。2023年,内核的随机数子系统基本完成了向LRNG(Linux Random Number Generator)架构的重写,核心是用ChaCha20 DRBG作为确定性随机数生成核心,用硬件随机数源持续“喂”熵,同时减少了对传统熵估计的依赖。

这次重写带来的变化,大多数应用感知不到,但对安全体系影响很大:getrandom()的初始化更快,系统启动早期就能保证安全随机数可用;/dev/urandom和getrandom()的行为趋于一致,不再有“urandom不够安全”的玄学讨论;熵源管理清晰化,ARM和新硬件平台可以更自然地接入硬件随机数生成器。

我见过不少运维还在用cat /proc/sys/kernel/random/entropy_avail判断“随机数够不够”,这个思路在旧内核还有意义,在LRNG架构里已经不适合作为健康指标了。更准确的方式是检查/proc/sys/kernel/random/urandom_min_reseed_secs以及硬件熵源是否被内核识别。

实际落地时有一条经验:大量使用容器并依赖getrandom()的服务,最好升级到6.4以上内核。老内核在容器频繁创建时会因为熵估计问题出现启动延迟,LRNG架构下这个问题基本消失。如果你跑的是需要大量签发证书、生成密钥的服务,这个改进是实打实的。

3.3 Rust进内核:从“能编译”到“能干活”

Rust for Linux在6.1首次合入时只能算“基础设施地基”,像是编译器能跑、最小模块能编译。但2023年一年,Rust在内核里的成熟度肉眼可见地上升:更多核心抽象层(比如kernel::sync、kernel::ioctl)稳定下来;模块注册、设备驱动示例、网络PHY驱动这些“真实场景”陆续进入主线;社区开始讨论用Rust写文件系统和网络协议栈的可行性。

为什么内核社区对Rust这么上心?因为C语言写内核,内存安全完全靠人的纪律,而内核漏洞里UAF、越界访问占了很大比例。Rust的所有权、生命周期和unsafe隔离机制,能把这些错误从“运行时被攻击者利用”变成“编译期被拒绝”。

如果你想在2023年之后的(比如6.6)内核上尝试写一个Rust模块,比较顺的路径是:

  • 安装rustup并选择合适的工具链版本,通常主线内核绑定一个较新的stable工具链。
  • 开启内核参数CONFIG_RUST=y,同时准备bindgen来生成C绑定。
  • 用make rustavailable确认环境是否满足。
  • 参考内核源码samples/rust/目录下的例子起步,先写一个只注册并打印的模块,再逐步接设备驱动。

我个人判断,短期Rust不会替代C成为内核的主流语言,但未来五到十年,“新模块默认考虑Rust”会成为一个常态。对这个趋势,最值得关注的是抽象层的质量,而不是单纯语言热度。内核的Rust抽象不是简单的“把C API包装一遍”,它要用类型系统把“这个指针一定非空”“这个锁一定被持有”变成编译期约束,这项工作进展其实比大多数人想象得快。

4. 存储与内存架构:BCACHEFS与Maple Tree

4.1 BCACHEFS:终于进主线的下一代文件系统

BCACHEFS从立项到进主线经历了十几年,一度被认为是“永远不会合入”的个人项目。2023年12月的6.7终于把它并入了主线,这件事本身就有象征意义:一种基于B-tree、写时复制(COW)的文件系统,功能和设计理念上非常接近ZFS/Btrfs,但代码路径更清晰,性能预期更明确。

BCACHEFS的核心能力包括:

  • 多设备支持:可以把多块磁盘组成一个文件系统,内部自己做数据分布。
  • 数据校验和压缩:每块数据带checksum,支持zstd等压缩,和Btrfs/ZFS定位一致。
  • 快照与子卷:快照能力设计比较优雅,元数据开销低于Btrfs的某些场景。
  • 与BCache的渊源:原开发者还维护过块缓存层,所以BCACHEFS在缓存设备分层上天然有自己的思考。

对比一下三者的适用场景:

特性BCACHEFSBtrfsZFS(非主线)
主线内建是(6.7+)是否(需DKMS)
多设备是是是
写时复制是是是
快照是是是
压缩是是是
磁盘布局成熟度尚早成熟成熟
默认推荐度(生产)实验视场景经典但生态受限

我目前不会建议把核心生产库直接放进BCACHEFS,毕竟新文件系统进主线之后还需要多个版本迭代来修边缘case。但如果你有研发测试环境、缓存池、或者想跑新文件系统压测,6.7之后可以开始玩了:

# 格式化一个块设备(注意:会清空数据) mkfs.bcachefs /dev/sdX # 挂载 mount -t bcachefs /dev/sdX /mnt/bcachefs

有一个坑要提:早期BCACHEFS的mount默认选项可能没有打开discard,在SSD上会带来写入放大问题,建议仔细阅读mkfs.bcachefs --help,把--discard和--compression按场景显式配好。另一点,跨内核版本升级时,BCACHEFS的磁盘格式可能没完全冻结,做升级前一定留出备份。

4.2 Maple Tree:红黑树最后的退伍

Maple Tree是内核自己的一项“基建革新”,它解决的问题特别具体:虚拟内存区域(VMA)查找和修改的效率。

过去的进程地址空间管理用红黑树组织VMA,节点越小、操作越频繁,锁竞争越严重。红黑树的问题是每个节点只存一个VMA,查找和修改的路径长,而且多个线程同时在进程地址空间里做mmap/munmap时,树旋转会导致严重的锁竞争。

Maple Tree的设计思路是用一种类似B树的多路搜索树:一个节点可以保存多个VMA,查找路径变短,写锁范围变小。6.5内核开始完全用它替代红黑树来管理VMA。这件事对普通开发者几乎没有感知,但对大规模多线程服务影响很大。mmap/munmap高频的进程,比如JVM、内存数据库、某些图计算引擎,会明显感受到延迟改善。

运维侧不需要做什么额外配置,Maple Tree是内核内部实现细节,没有开关,升级内核即自动生效。想验证效果可以找一个大量使用共享内存和内存映射的程序,对比6.1和6.5以上内核在并发线程数放大时的系统CPU和锁等待。我之前调过一个分布式缓存,线程数从16调整到64时,旧内核对mm_sem的争用能把吞吐打掉三成,换新内核之后压力曲线平滑很多。

5. BPF与NUMA:基础设施的“软实力”革新

5.1 BPF的2023:从探针到调度策略

BPF在2023年的进化虽然不是单点大功能,但“可编程性”全面加深。传统的BPF主要被用来做可观测性和网络数据路径处理,比如tcpdump、bcc、Cilium底层都是它。2023年的进展更偏向内核子系统的可编程化:struct_ops让开发者可以在BPF里定义内核数据结构里的函数指针,像是用一种安全的方式向内核“注入”自定义策略;对bpf_arena、多CPU缓冲等机制的讨论,让BPF程序在复杂并发场景下的表现更强。

另一个绕不开的话题是sched_ext。它允许用BPF程序完全替换调度策略,虽然最终合入主线是2024年的事,但2023年BPF调度器的讨论就已经把“内核调度策略可以通过BPF定制”这件事带进了主流视野。想象一下,你可以根据自己业务特征写一个调度器,不用改内核主线代码,就能在BPF虚拟机里安全地控制任务调度,这对云厂商来说是巨大的诱惑。

对普通开发者的实操建议是:2023年之后,学习和使用BPF的门槛又降低了一些。用bpftool可以方便地枚举加载中的BPF程序:

bpftool prog list bpftool prog dump xlated id <id> bpftool net show

用BCC或libbpf写一个跟踪openat的小工具已经是很常规的操作。但也要注意BPF有严格的验证器限制:循环上界、指针访问范围、栈深度都要满足安全条件。写复杂BPF程序时,“验证器拒绝”是最常遇到的坑,处理方式通常是简化逻辑或把复杂计算留在用户态,BPF里只做轻量过滤和聚合。

5.2 NUMA感知的进一步下沉

NUMA(非统一内存访问)是大型服务器的标配。CPU访问本地内存快、访问远端内存慢,如果内核调度器不感知NUMA拓扑,会出现“线程跑在CPU0,内存却从CPU2的节点分配”这种尴尬场面。2023年的内核在NUMA-Balancing和调度域划分上做了不少细节改进:自动迁移线程到内存所在节点、改善NUMA节点间负载不均时的迁移策略、更智能的调度域层级。

这些改动对2路、4路服务器的性能稳定性影响很大。我们跑过一个40核心以上规模的数据库实例,未开启NUMA优化时,远端内存访问比例高的情况下P99延迟波动非常明显。新内核配合numad或系统默认的numa_balancing,能自动把热点线程往内存节点上收敛。检查当前状态:

cat /proc/sys/kernel/numa_balancing cat /sys/devices/system/node/online numactl --hardware

一个现实问题是:许多老运维习惯在BIOS里关闭NUMA以“简化问题”,理由是“让内存在一个域里”。对老内核这个办法能减少调度问题,但新内核里关闭NUMA等于主动放弃了调度器一大块智能。建议新内核上尽量保留NUMA并开启numa_balancing,让内核自己处理。

6. 想最快体验这些新功能?实操指南

6.1 快速定位当前内核有没有新特性

判断一个内核是否包含某项特性,最稳的办法不是看发行版名字,而是直接查当前内核配置:

# 当前运行内核 uname -r # 看内核编译配置 # 以io_uring、BBRv3、SHSTK、Rust、BCACHEFS为例 for opt in IO_URING TCP_CONG_BBR X86_USER_SHADOW_STACK RUST BCACHEFS_FS; do grep -w "$opt" /boot/config-$(uname -r) || echo "没有找到 $opt" done

如果是自己编译的内核,/boot/config-*可能不存在,可以用:

zcat /proc/config.gz | grep -w CONFIG_IO_URING

关于各特性的Kconfig对应关系,我整理了一个速查表:

特性关键配置项备注
io_uringCONFIG_IO_URING高版本基本默认开启
BBRv3CONFIG_TCP_CONG_BBR需TCP_CONG_ADVANCED
EEVDF没有独立开关6.6+自带,属于调度器内部重构
影子栈CONFIG_X86_USER_SHADOW_STACK还需CPU支持
Rust支持CONFIG_RUST还需rustc工具链
BCACHEFSCONFIG_BCACHEFS_FS6.7+
Maple Tree无开关内核内部替换,无需配置

6.2 从主线编译到实际验证三大功能

如果发行版的内核版本太老,体验新特性最直接的方式是自己编译主线内核。步骤如下:

# 1. 下载主线源码并解压 tar xf linux-6.6.tar.xz cd linux-6.6 # 2. 基于当前内核配置做增量 # 新版本通常会引入新配置项,olddefconfig会帮你选择默认值 make olddefconfig # 3. 开启你需要的功能(也可以直接改.config) ./scripts/config --enable IO_URING ./scripts/config --enable TCP_CONG_BBR ./scripts/config --enable X86_USER_SHADOW_STACK # 4. 编译 make -j$(nproc) # 5. 安装模块和内核 make modules_install make install

重启后验证BBR和io_uring最直接:

# 验证BBR sysctl net.ipv4.tcp_congestion_control # 输出应为 bbr # 验证io_uring(用fio) fio --name=iotest --ioengine=io_uring --rw=randread --bs=4k --size=1G --numjobs=4 # 验证影子栈CPU能力 grep -o shstk /proc/cpuinfo | head -1

如果你更想快速验证EEVDF,用perf sched观察高优先级短任务的唤醒延迟是一个办法:

perf sched record -- sleep 10 perf sched latency

perf sched latency输出的wait time和sched switch如果明显平稳,说明调度器对短任务的调度确定性是好的。这个指标不是EEVDF专有,但能反映调度延迟。

6.3 风险控制:别让新特性变成生产事故

升级内核本身就有风险,为了尝鲜几个新特性把生产搞挂不值得。我的几点建议:

  • 先在容器或虚机里跑一两个星期,观察dmesg里有没有异常trace,再动物理机。
  • 新内核和新特性是两回事:即使内核6.6里编译了SHSTK,默认不一定对所有进程开启。影子栈对用户态程序是逐步放量的,需要glibc、JIT类软件配合,千万不能“编译下就完事”。
  • BBR是全局sysctl,切换后会影响节点上所有TCP连接。建议先在小流量节点验证,并至少在sysctl配置里留好回滚方式:把net.ipv4.tcp_congestion_control改回cubic即可。

7. 我踩过的那些坑:2023年内核升级经验

7.1 EEVDF导致的延迟抖动排查

有一台部署了流量网关的服务器从6.1升到6.6之后,P99延迟比之前变高了。排查半天,不是网络问题,而是这台机器的cgroup配置很复杂:多个高优先进程共享一个cpu子系统的树形结构。EEVDF对短任务更友好,但不代表对低优先级长任务的“惩罚”和CFS一致。最后重新调整了cpu.weight,把网关进程的权重单独提了一档,问题解决。这个案例的教训是:升级内核后,一切基于调度策略的“历史调优”都需要复查。

7.2 BBRv3与网卡卸载的兼容性

在某次测试中,开启BBRv3后反而出现带宽利用率下降。原因是网卡开启了TCP分段卸载(TSO/GSO),而机器上的网络拓扑又有QoS标记不一致的问题,BBRv3对ECN信号的反应比旧算法快,一旦链路误标记ECN,它会立刻降速。排查方法是临时设置sysctl -w net.ipv4.tcp_ecn=0,再观察带宽曲线。如果恢复正常,基本说明是ECN误报。生产链路不能保证所有交换机都正确支持ECN的话,我建议BBRv3初期保持ECN关闭。

7.3 io_uring在高权限容器里的尴尬

容器环境里跑io_uring,最常见的错误是io_uring_setup() failed: Operation not permitted。原因是容器的seccomp配置没有放行这个系统调用,或者cgroup对io_uring的资源控制没有配置。解决方式不是关掉seccomp,而是在安全策略里按需放行io_uring_setup、io_uring_enter、io_uring_register这三个系统调用。注意这三个syscall号码在不同架构上不同,规则不要写死。

7.4 Shadow Stack与动态加载器的“相爱相杀”

前面提到过老glibc在SHSTK启用后会崩,这里补一个更隐蔽的坑:SHSTK对用dlopen动态加载大量插件的服务影响尤其大。插件如果含有手写汇编的跳转表或老式setjmp,会频繁触发控制流异常。所以在SHSTK上,先跑Nginx/Redis这类成熟软件通常没问题,但带JIT的、带私有加载器的应用要格外谨慎。我的建议是分批次启用,内核层先把CONFIG_X86_USER_SHADOW_STACK编进去,但用户态通过arch_prctl的启用时机和应用适配成熟以后再开。

最后再分享一个关于“跟进内核新特性”的个人经验:不要急着把所有新功能在同一次升级里全部打开。内核这些变革之间是有依赖关系的,比如EEVDF和NUMA优化可能相互影响,BBRv3和网络卸载特性也相关。最好每两个版本追一次主线,新功能一个一个地开,每开一个就盯几天监控。这一轮看下来,2023年内核最大的价值不是某一项单项技术,而是整个系统在“性能确定性”和“内存安全”两条主线上同时往前走了一大步。看起来只是内核版本号从6.1跳到6.7,实际上的思维变化,值得每个做基础设施的人花时间认真追一遍。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进