Linux Kernel 6z系列主要发布记录
2026-01-06 16:00:05 阿炯

本站赞助商链接,请多关照。 上接《Linux Kernel 6y系列主要发布记录》。


将支持 Mac 电池与电源状态监测

Linux 内核在2026年1月上旬迎来了新的 Apple Silicon SMC 电源驱动补丁,用于将 MacBook 的电池电量、功耗以及电源适配器状态完整暴露给 Linux 用户空间,使运行 Linux 的苹果笔记本在电源管理和电池监控方面迈出关键一步。

这个名为 macsmc-power 的驱动旨在通过 Apple 系统管理控制器(SMC)向 Linux 提供以下数据:
当前电池容量
电压与电流
充电状态
AC 电源适配器连接状态

这些指标将通过 Linux 标准的电源框架对外展示,这意味着现有 Linux 工具可直接读取这些信息,无需额外定制。该驱动基于 Asahi Linux 社区长期维护的非主线代码改造而来,并已提交至内核评审流程。主要开发者对新版固件和无电池设备做了兼容性改进。

对 Linux 用户来说,这将是长期以来的一个重要补足 —— 过去在 Apple Silicon 上运行 Linux 时,电池与电源信息无法被系统识别,使得电源管理体验不完整。随着这个驱动被合并,用户可以更方便地监控电力使用与续航情况。目前该驱动主要针对 Apple M1 / M2 / M3 系列芯片。对于更高一代的 M4 和 M5,目前 Linux 支持仍在推进中,相关功能尚未完善。

Greg Kroah-Hartman 荣获 2026 年开源卓越奖

2026年1月下旬在比利时布鲁塞尔举行的欧洲开源学院(European Open Source Academy)年度颁奖典礼上,Linux 内核重要维护者 Greg Kroah-Hartman 获得了 “开源卓越奖(Prize for Excellence in Open Source)” 这一年度最高荣誉。

欧洲开源学院主席、curl 创始人兼核心维护者 Daniel Stenberg 为他颁发了该奖项:


这一奖项由欧洲开源学院主办,旨在表彰对开源生态做出长期卓越技术贡献的人士与项目,包括技术领导力、持续贡献及对全球开源社区的深远影响。

Greg Kroah-Hartman 是 Linux 内核的稳定分支(stable kernel)维护者之一,他负责:
每周发布稳定版 Linux 内核的关键漏洞修复与安全补丁;
同时维护多个长期支持(LTS)版本;
确保 Linux 在全球范围内的稳定性、安全性和可用性。

这项工作虽然不像新功能开发那样华丽,但对于全球数十亿台设备(包括服务器、移动终端、嵌入式设备等)的正常运行至关重要,被视为 “支撑全球数字基础设施的核心贡献”。

在颁奖致辞中,Daniel Stenberg 强调 Greg 的工作 “让医院、银行、政府和个人用户可以安心地依赖 Linux”,并表示他的贡献体现了开源社区中 “稳定性与长期维护同样是基础性创新” 的价值。

Linux 内核将迎来 7.0 大版本

Linus Torvalds 在2026年2月上旬宣布下一代内核版本将命名为 Linux 7.0,并非传统意义上的 6.20。这标志着 Linux 内核版本号进入新的整数阶段。Linux 7.0 开发周期的合并窗口已经开启,并预计在 2026 年 4 月中旬与 Ubuntu 26.04 LTS 等主流发行版同步推出。他在邮件列表中提到由于自身对更高数字不太方便记忆,他选择将下一个主要内核版本命名为 7.0。Linux 的版本号通常在 x.19 之后跳到下一个整数,这一次也是遵循同样的传统。合并窗口将持续约两周,之后进入一系列 Release Candidate 阶段。

Linux 7.0 不只是在版本号上跨越,也预计带来多项性能与硬件支持改进,包括但不限于:
新的硬件支持:如针对 AMD 新 GPU IP 和 Intel Xe3P 系列显卡的新驱动准备。
内核子系统性能优化,如 IO_uring IOPOLL 改进,用更高效的数据结构提升 I/O 性能表现。
多队列图形 / 加速器支持(如 Intel 多队列支持,有助于 AI 推理和高并发图形任务)。
更深度的 Rust 语言与 LTO(Link-Time Optimization)内核构建支持,助推现代内核开发灵活性与性能。

其推出将为服务器、桌面及嵌入式平台带来更广泛的硬件兼容性和调度优化,这对于追求最新平台支持和性能优化的开发者及企业用户来说,是一个重要的内核迭代节点。同时伴随 7.0 发布的还有大量社区贡献的驱动及子系统更新。

7.x的几个主要特点

1. 硬件兼容全面升级
深度适配Intel Nova Lake/Diamond Rapids、AMD Zen 6架构,首次支持高通骁龙X2等ARM平台,扩展多设备生态覆盖。

2. 系统性能多维优化
调度器引入TIP机制提升任务连续性,内存管理与I/O子系统增强,显著改善高负载场景响应速度与资源效率。

3. Rust语言正式"转正"
结束两年实验阶段,确立为内核长期支持语言,提升代码安全性与可维护性,获开发者社区广泛认可。

4. 文件系统与存储革新
exFAT顺序读取提速约10%,EXT4/F2FS并发能力增强,并新增SPI NAND等新型存储技术适配。

5. 用户体验显著提升
游戏/桌面流畅度优化,支持编译时自定义启动Logo,完善硬件监控与标准化错误报告机制。

宣布延长多个 LTS 内核版本支持周期

稳定版内核维护者 Greg Kroah-Hartman 和 Sasha Levin 于2026年3月上旬宣布,Linux 项目将延长多个长期支持(LTS)内核版本的维护时间。其中,v6.6 的计划终止支持时间由原定的 2026 年 12 月延长至 2027 年 12 月,使其实际维护周期从 3 年延长为 4 年。这一调整被视为对此前 “LTS 版本支持期缩短至两年” 政策的一次修正,在业界和社区反馈下重新拉长了部分关键版本的生命周期。

在过去数年中,Linux LTS 版本的维护周期经历了多次调整:2017 年前通常为两年,此后在 Google 等厂商推动下延长至最长 6 年,但近年由于维护压力和资源不足,官方又将新 LTS 版本的标称支持期收紧到约两年到三年。此次延长 6.6 等版本寿命的决定,表明在实际执行过程中,Linux 内核维护者仍会根据软硬件厂商、测试者以及开源社区的投入程度,适时调整支持计划,为生态提供更灵活的长期稳定基础。

业内分析认为,对服务器、嵌入式设备及关键基础设施用户而言,LTS 支持期的微调关系到系统升级节奏与安全风险控制,延长 6.6 等版本维护时间,将在一定程度上缓解大规模部署环境中频繁内核迁移带来的成本和不确定性。另一方面,维护者此前多次强调超长生命周期带来的工作负担,未来新 LTS 版本是否能够获得超过 “名义两年” 的延长支持,仍取决于行业整体参与度和上游、下游厂商的实际承诺。

AMD终于在Linux上拿下HDMI 2.1-感谢Valve幕后推动

快科技2026年5月上旬据Phoronix报道,AMD已向Linux内核提交HDMI 2.1 FRL(Fixed Rate Link)补丁,Radeon显卡在Linux上终于迈出原生HDMI 2.1支持的关键一步。

HDMI 2.1在Linux上的缺席由来已久,根源并非技术能力不足,而是HDMI论坛的许可政策限制。该组织长期禁止开源驱动的上游实现,导致AMD的AMDGPU开源驱动只能停留在HDMI 2.0标准,如今经过多年谈判,AMD终于在Valve和社区开发者的协助下取得进展。此次提交的FRL补丁是HDMI 2.1传输层升级的核心组件,负责提供远超HDMI 2.0的链路带宽,是实现4K@120Hz和8K@60Hz输出的基础。补丁说明显示代码已通过部分HDMI合规测试,完整合规验证正在进行中。

不过完整的HDMI 2.1功能栈尚未全部到位,DSC仍在测试中,VRR(可变刷新率)等游戏增强特性也未包含在当前补丁集内,预计将在后续更新中逐步推送。这项突破的背后,Valve的持续推动功不可没,随着Steam Deck等Linux游戏平台的扩张,Valve对HDMI 2.1显示支持的需求日益迫切。Phoronix指出,Valve积极参与了AMD与HDMI论坛之间的谈判,社区开发者Jerry也承担了大量代码加固和合规测试工作。

与Windows NT IOCP的发展对比

前些日子Linux社区对 Jens Axboe 写的 io_uring 评价非常高,甚至有人把它称为“这是 Linux 三十年来最重要的 I/O 架构变化之一”。有个做 Windows 驱动的网友就不太服气,他说:“这不就是把 NT 很早以前的异步思想,加上共享内存环形缓冲区重新做了一遍吗?Windows 早就玩过了。”


Jens Axboe

如果翻一下 Windows NT 的历史,会发现微软很早就开始认真解决异步 I/O 的问题了。Windows NT 时代就已经有了 IOCP(Input/Output Completion Ports,I/O 完成端口)这样的设计。到了后来,Unix/Linux 世界长期使用 select、poll 等机制处理 I/O 事件,Linux 又在 2002 年引入 epoll。

它们解决问题的方式并不完全一样。

select、poll、epoll 更接近 Reactor:应用告诉内核自己关心哪些文件描述符,内核发现条件满足以后通知应用,由应用继续处理 I/O。Windows 的 IOCP 则更接近 Proactor:应用把 I/O 请求交给系统,内核和驱动负责把这次操作完成,最后再把完成结果通知应用。

于是问题就来了:
既然 Windows 很早就采用了异步 I/O 和完成端口这样的设计,为什么后来没有直接沿着 io_uring 的方向,把用户态和内核态之间的交互做成共享内存环形队列,把系统调用的次数进一步压下来?

是不是因为当时硬件不够快?还是说微软当年的工程师没有想到?

后来再看 NT 的设计,会发现事情没有这么简单。他们不是没想到,而是当时根本没有必要按照今天的硬件条件去设计。

要理解 Windows 为什么选择 IOCP,先得看看 io_uring 到底解决了什么问题。

传统的 read()、write() 之类的系统调用,需要从用户态进入内核态。单次调用的成本可能并不算什么,但在高速存储和高速网络环境里,如果每秒需要处理大量 I/O,请求数量一上去,这些固定开销就会开始变得明显。尤其是 NVMe SSD、100Gbps 甚至更高速的网络设备出现以后,硬件本身越来越快,CPU 花在 I/O 提交和完成通知上的时间,就显得没有以前那么划算了。

Jens Axboe 做 io_uring,一个很重要的思路就是减少这种频繁的用户态/内核态交互。

应用和内核之间建立共享的环形队列,其中有提交队列和完成队列。

应用把 I/O 请求放进 SQ,内核从里面取请求并执行;完成以后,内核把结果放进 CQ,应用再从 CQ 里取回来。

在合适的使用方式下,应用可以一次提交多个请求,而不是每完成一个小操作,就发生一次用户态到内核态的往返。

如果再配合 SQPOLL 等机制,部分场景下甚至可以进一步减少提交请求时的系统调用。

这种设计放到今天看很自然,但它背后其实有一个很重要的前提:硬件已经快到了值得我们花更多 CPU 资源,去换取更少的系统调用和更少的交互成本。而这个前提,在 Windows NT 开始设计的年代并不成立。

1988 年,微软把 Dave Cutler 从 DEC 公司请过来负责 NT 项目。微软希望这套新系统能够运行在 x86、MIPS、Alpha 等不同体系结构上,同时具备企业级操作系统需要的稳定性、安全性和硬件抽象能力。


Dave Cutler

Cutler 和他的团队面对的,是一个和今天完全不同的计算机世界。那时候的 CPU、内存和磁盘都非常昂贵,机器的计算能力也远没有今天这么充裕。

NT 的设计因此特别重视两个东西:硬件抽象,以及用户态和内核态之间的边界。Windows NT 的 I/O 系统就是围绕 IRP(I/O Request Packet)建立起来的。

应用调用 ReadFile 以后,系统会创建相应的 I/O 请求,经过 I/O Manager 和驱动程序栈逐层处理。对于驱动来说,一个 I/O 请求从哪里来、现在处于什么状态、最后怎么完成,都有比较明确的生命周期。用户态内存也不能随便让内核直接相信。

Windows 驱动中常见的 Direct I/O 和 Buffered I/O,就是两种不同的处理方式。前者通常通过 MDL 等机制锁定并描述用户缓冲区,后者则由系统分配内核缓冲区,在用户态和内核态之间进行数据复制。

这套设计看起来比较重,但有一个很现实的好处:内核不会轻易相信用户态交上来的东西。

如果在那个年代就设计一套今天类似 io_uring 的共享环形队列,当然不是说做不到,而是安全边界和工程复杂度都会随之变化。

例如,用户态进程可以修改自己能够写入的共享区域。如果内核在使用其中的数据之前没有做好校验,就可能出现典型的 TOCTOU(Time-of-Check to Time-of-Use)问题。

对于操作系统内核和驱动程序来说,这不是一个可以轻描淡写的问题。

一个错误的地址、一块已经失效的缓冲区,甚至一个被恶意修改的请求,都可能最终变成内核崩溃。而在 90 年代的硬件环境里,还有另外一笔账需要算。

CPU 只有一个或者很少几个核心,主频也远没有今天高。为了节省一些系统调用开销,让 CPU 长时间占着一个线程去轮询共享队列,在今天可能是可以接受的优化,在当时却未必划算。

所以 Windows 选择了另外一条路线:让内核负责调度,把完成的 I/O 请求交给合适的线程处理。

IOCP 的价值其实就在这里。它没有试图把系统调用从 I/O 过程中完全拿掉,而是把重点放在了另外一个问题上:怎么让大量 I/O 请求不会把大量线程同时拖进内核,又不会因为线程数量失控把 CPU 和内存消耗掉。

当应用使用 IOCP 发起异步 I/O 后,请求会进入内核和驱动体系进行处理。I/O 完成以后,完成通知会进入完成端口。工作线程从完成端口取出结果,然后继续处理后续任务。

这套机制最有价值的一点,是它把 I/O 完成和工作线程的调度结合了起来。假设系统里有很多 I/O 同时完成,并不意味着所有等待线程都必须同时醒来。

应用可以根据 CPU 数量和业务特点控制并发程度,让有限数量的工作线程处理大量 I/O 完成事件。这也是为什么 IOCP 很适合高并发服务器。

当 Linux 还在不断解决“如何高效处理大量文件描述符事件”这类问题时,Windows Server 上的 IIS、数据库和其他服务器程序已经大量使用 IOCP 来处理网络 I/O。

IOCP 的确非常成功。但问题也恰恰出在这里。

一个被大量程序、驱动和框架使用了很多年的接口,一旦成为生态的一部分,想重新设计它就不是单纯改一个内核模块那么简单。随后二十多年,硬件开始发生变化。NVMe 固态硬盘出现以后,存储延迟从传统机械硬盘时代的毫秒级,下降到了微秒级。网络也一样。

100Gbps、200Gbps、400Gbps 的网卡开始进入数据中心,服务器每秒处理的数据包数量越来越高。

这时候,一些过去并不显眼的成本开始重新进入工程师的视野。以前设备比较慢,系统调用的成本被磁盘和网络延迟轻松盖过去。现在设备本身已经很快,反而是软件栈里的固定开销开始显得突出。于是 io_uring 出现了。

它没有简单地重复 IOCP 的设计,而是从今天的硬件条件重新考虑用户态和内核态之间应该怎样交换 I/O 请求。在大量、小粒度、高并发的 I/O 场景里,减少系统调用次数、批量提交请求,以及在合适的情况下使用轮询机制,都可能带来明显收益。这也是 io_uring 受到关注的重要原因。如果把 Windows 的 IOCP 和 Linux 的 io_uring 放在一起看,会发现两者其实没有简单的“谁先进、谁落后”。
评估维度Windows IOCPLinux io_uring
设计核心哲学严格的内核/用户态隔离,线程调度与并发控制彻底榨干硬件性能,尽量消除内核上下文切换
交互机制基于系统调用(Syscall)提交,内核队列通知 completion基于共享内存无锁环形队列(SQ/CQ),可完全避开 Syscall
安全与防御强校验,天然防御 TOCTOU,对恶意用户态极其警惕依赖复杂的内核安全审计与内存映射约束,初期 Bug 较多
硬件背景前提昂贵的单核/少核 CPU,慢速机械硬盘和低速网络极其廉价的多核算力,微秒级 NVMe 固态与百G网卡
历史工程负担绑定了极其庞大的 Win32 API 与驱动生态,极难重构后发优势,可以直接在 Linux 现代化内核上重构蓝图

看一下微软后来推出的技术,会发现微软并不是不知道这种思路。Windows 后来也加入了 I/O Ring 相关能力。它同样使用了环形队列这样的设计,用来减少传统 I/O 路径中的一些固定开销。这其实挺有意思。

三十年前,Windows 选择了 IOCP。几十年后,硬件环境发生变化,Windows 自己也开始吸收新的 I/O 设计。所以技术演进并不是简单的“新技术把旧技术淘汰”。Windows 当年没有选择 io_uring 这样的方案,很大程度上是因为当时面对的问题和今天并不一样。

在单核或者少核 CPU、机械硬盘和低速网络占主导的年代,把系统调用、线程调度、安全隔离和驱动模型组织起来,本身就是一项非常复杂的工作。

IOCP 解决的是那个时代最重要的问题,而且它后来也确实支撑了 Windows Server 很长时间。io_uring 则是在另一套硬件条件下出现的。

CPU 核心越来越多,内存越来越便宜,NVMe 把存储延迟压到了微秒级,高速网卡又把每秒 I/O 请求数量推到了新的量级。

这时候,过去可以接受的系统调用和调度开销,就重新变成了值得优化的问题。所以 Linux 开发者并不是突然比 Windows 工程师聪明了。

很多时候,所谓“先进的架构”,只是因为它出生的时候,硬件和软件环境已经变了。

这也是操作系统设计很有意思的地方。你今天看一个三十年前的设计,会觉得它为什么不这样做。但如果把自己放回三十年前,面对当时的 CPU、内存、磁盘、安全要求和软件生态,答案可能完全不同。

很多技术后来变成了历史包袱,并不是因为当初选错了,而是因为当初那个正确的选择,活得太久了。而这可能才是 Windows IOCP 和 Linux io_uring 之间最有意思的地方。Linux 是从 io_uring 逐渐构建新一代异步 I/O 模型,Windows 则是在 IOCP 体系上持续演进。

Linux 内核里最常见的 GNU C 扩展

Linux内核主要是C语言开发的,不过它不是用的标准C,在内核源码里的 Documentation/process/programming-language.rst 开头就写着:内核用 C 写,具体说是用 gcc 的 -std=gnu11 编译,也就是 ISO C11 的 GNU 版本;clang 也支持。

这个GNU C里塞了一大堆标准 C 没有的扩展, 下面这些是内核里出现频率最高的一些。

一、一些有用的宏
1. 语句表达式 ({ ... })
一道老掉牙的面试题:写个 min 宏。

大部分人第一反应是这样:
#define min(x, y) ((x) < (y) ? (x) : (y))

能用,但坑有三个。

x 和 y 各求值两次,要是写 min(i++, j++),就只能等着加班了。没有类型检查,传两个结构体进去它一样"编译通过"。

宏里还放不下中间变量。逻辑稍微复杂一点就写不开了。GNU C 的语句表达式就是来治这个的:
#define min(x, y) ({        \
    typeof(x) _x = (x);     \
    typeof(y) _y = (y);     \
    _x < _y ? _x : _y;      \
})

一对圆括号里嵌一对花括号,块里面可以声明变量,最后一个表达式的值,就是整个块的值。

求值一次、类型安全、还能放临时变量,三个坑一次填平。内核里的 container_of 就是靠它撑起来的。

2. typeof
上面那段代码里有个 typeof(x),这是它能成立的前提。

typeof 取一个表达式的类型,但不求值:typeof(((struct foo *)0)->bar) tmp;

这行什么意思?

把一个空指针强转成 struct foo *,再取它的 bar 成员。看着像要解引用,其实不会。typeof 只要类型,不会真去读那块内存。

有了它,宏才知道自己手里捏的是什么类型,才能声明出类型正确的临时变量。内核里的 container_of、READ_ONCE、WRITE_ONCE 都离不开它。


3. 可变参数宏与 ##VA_ARGS
C99 就有可变参数宏了,... 配 __VA_ARGS__。

但有个尴尬的地方:参数为空的时候,前面那个逗号还不能去掉。

#define pr_debug(fmt, ...) printk(KERN_DEBUG fmt, __VA_ARGS__)
pr_debug("hello");   // 展开成 printk(KERN_DEBUG "hello", ),多一个逗号

GNU C 的解法是在 VA_ARGS 前面加两个井号:
#define pr_debug(fmt, ...) \
    printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)

## 的作用是:后面要是没参数,它就把前面那个逗号一起吃掉。

这一手在内核里随处可见,pr_、dev_、trace_* 整个工具全靠它撑着。

二、数据摆放属性
4. attribute
这个关键字是给编译器下命令的:对齐、打包、归到哪个段、要不要内联、格式化字符串做不做检查。

一次性能写一串:
__attribute__((packed, aligned(8), section(".init.text"), always_inline))

内核嫌这么写太长,全都套了一层马甲:
__init → __attribute__((section(".init.text")))
__packed → packed
__aligned(n) → aligned(n)
__always_inline → always_inline
__printf(a, b) → 编译期检查格式化字符串

这里头最值得聊的是 __init。

标了 __init 的函数,会被编译器单独扔进 .init.text 段。这段内存的特别之处在于:内核启动完成后会调用 free_initmem(),把整段还给 buddy 系统。

也就是说,这些函数的使命只有开机那几秒钟,用完就还。


.init.text 和 .init.data 是"一次性"内存,启动结束就被回收,一个函数标了 __init,内核跑起来之后就绝对不能再调它,那块地早就分给别人种了。

5. 零长度数组
把结构体最后一个成员写成 char data[0]:
struct msg {
    int len;
    char data[0];
};

sizeof(struct msg) 只算 len 那 4 个字节,data 本身不占地方,但你能顺着它访问紧跟在结构体后面的内存。

用法是这样:
struct msg *m = kmalloc(sizeof(struct msg) + payload_len, GFP_KERNEL);
m->len = payload_len;
memcpy(m->data, payload, payload_len);

一次分配,len 和数据在内存里紧挨着,访问 m->data 也不用再跳一次指针。要是写成 char *data,那就是两次分配、一次指针跳转,释放的时候还得多 free 一次。

网络包、变长消息这类东西,内核里全是这个套路。

补一句:char data[0] 是 GNU 的写法,标准写法是 char data[],也就是 C99 的柔性数组。内核里两种都有。

6. 范围指定初始化器
C99 有指定初始化器,GNU C 把它扩展成了范围:
static int table[] = {
    [0 ... 9]   = 1,
    [10 ... 19] = 2,
    [20 ... 29] = 3,
};

三个点,0 到 9 一口气填 1。比手写循环初始化省事,而且这些值是编译期就填进 .data 段的,运行时一点开销都没有。

内核里的查找表、默认值表,用得很顺手。

三、更多代码交给编译器
7. _builtin* 家族
gcc 和 clang 都提供了一批内建函数,内核挑着用了一部分。

最有名的是这组:
#define likely(x)   __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

if (unlikely(ptr == NULL))
    return -EINVAL;
__builtin_expect 是在告诉编译器:这个条件大概率不成立。

编译器据此调整分支布局,把冷路径的代码挪开,让热路径的指令挨在一起,CPU 的分支预测也跟着舒服。

另外几个也用得不少:
__builtin_constant_p(x):判断 x 是不是编译期常量。BUILD_BUG_ON 靠它,条件成立就直接编译报错。
__builtin_types_compatible_p(a, b):判断两个类型兼不兼容,用来写类型检查。
__builtin_offsetof(type, member):取成员偏移。offsetof 就是它的马甲。
__builtin_unreachable():告诉编译器这行代码到不了,帮它优化。

8. case 范围
标准 C 的 case 只能一个一个排:
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':

GNU C 允许直接写范围:
switch (c) {
case '0' ... '9':
    return true;
case 'a' ... 'z':
case 'A' ... 'Z':
    return false;
}

一行顶十行。字符判断、错误码判断,用起来相当爽。

9. 省略中间操作数的 ?:
三目运算符的标准写法是 a ? b : c。

GNU C 允许你把中间那个 b 省掉:
a ?: b        // 等价于 a ? a : b

注意,a 只会求值一次,不会像宏那样算两遍。这个特性在内核的错误处理里比较多:
return ptr ?: ERR_PTR(-ENOMEM);

ptr 非空就返回 ptr,空了就返回一个错误指针。

x = val ?: default_val 这种"有就用,没有就用默认值"的写法也很常见。

四、汇编
10. 内联汇编
有些事 C 是真表达不了:内存屏障、读写特定寄存器、CPU 特有的原子指令。

这时候只能上 asm。最常见的一个,看上去啥也没干:
asm volatile("" ::: "memory");

引号里是空的,就带了个 memory clobber。
它是在告诉编译器:这行之后,内存里的东西都可能变过了,别想把内存访问往后挪、也别想优化掉。

这就是内核里的 barrier()。再比如读时间戳计数器:
asm volatile("rdtsc" : "=a"(lo), "=d"(hi));

结果分别输出到 lo 和 hi 两个寄存器。这种指令,C 里真的没法表达。

内核里的 arch_atomic_*、native_* 这些体系结构相关的代码,全在这一层。

可以看出GNU C怎么方便怎么来:能编译期算的,绝不留到运行时;能少写一行的绝不多写的实用主义精神。