C in the Linux Kernel
摘要
本文深入介绍了 Linux 内核中 C 语言与普通用户空间 C 的区别,涵盖资源管理、错误处理、并发、日志记录、静态分析等核心技巧,使用了大量 GNU C 扩展和内核特有模式。
<p><a href="https://lobste.rs/s/q7e7hp/c_linux_kernel">Comments</a></p>
查看缓存全文
缓存时间: 2026/06/27 17:56
**TL;DR:** Linux 内核中的 C 语言与普通用户空间 C 完全不同,它在资源管理、错误处理、并发、日志和静态分析等方面使用了许多 GNU C 扩展和内核特有的模式与宏,以提升安全性和可维护性。
## 资源管理:从手动释放到自动清理
普通 C 语言没有 RAII,每次获取内存后都必须手动 `free`。但 Linux 内核使用带有 GNU 扩展的 C,其中 `__attribute__((cleanup))` 可以让编译器在函数退出时自动释放变量。此外,早期驱动使用大量 `goto` 标签来组织错误处理路径;从内核 2.6.21 起,引入了托管设备 API,自动管理每次分配的内存,使驱动的错误处理大大简化。
## 并发与锁:守卫和安全作用域
内核高度并行,多用户空间应用可能同时访问设备。驱动中通常使用互斥锁(mutex)保护共享数据。旧模式是手动加锁/解锁并配合 `goto` 清理;现代驱动则使用 `guard` 和 `scoped guard`。`guard` 在当前函数结束时自动解锁,`scoped guard` 在离开作用域时解锁,特别适合需要多次加解锁的场景。
## 错误处理:IS_ERR、ERR_PTR 的神奇玩法
由于没有 Rust 的 `Result` 枚举,内核利用保留地址空间来实现指针同时携带错误码。内核保留了最高 4095 个地址(访问会导致内核 panic),`ERR_PTR` 将负数错误码转换为对应的保留地址,`IS_ERR` 检查指针是否落在该范围内。这样同一个函数就能既返回有效指针又返回错误码。而在内核 Rust 代码中,则正常使用 `Result` 枚举并转换错误码。
## 日志记录:时间戳、驱动名与可读的错误码
日志是调试的主要手段。现代内核日志输出具有漂亮的时间戳、驱动名称、SPI 总线位置和自定义消息。错误码不再显示数字,而是转换为可读文本。同时,内核开发者热衷于重构旧代码,在简化逻辑的同时修复常见的错误处理 bug。
## 断言:从 BUG_ON 到 WARN_ON
Linus 强烈反对使用 `BUG_ON` 直接杀死内核,尤其是驱动中。现在推荐使用 `WARN_ON`,它会打印堆栈跟踪并继续运行,尽力保持系统稳定。
## 编译时检查:__must_check 与静态分析器
许多内核函数标记了 `__must_check` 属性,强制调用者检查返回值,否则编译器会警告。此外,内核内置了两个静态分析器:
- **sparse**:检测 `__user` 标记的指针错误(如直接解引用用户空间缓冲区),报告锁上下文不平衡、RCU 解引用等问题。
- **smatch**:传统分析器,能捕获空指针解引用、数组越界、内存泄漏、忘记解锁等。
## 分支预测:likely 和 unlikely
`likely()` 和 `unlikely()` 宏将重要代码保持在热路径中。如果某个条件极不可能发生,标记为 `unlikely` 后,编译器会生成汇编,把异常路径移到函数末尾,优先执行正常路径,从而利用 CPU 缓存行预取。但研究表明人类有 39% 的概率猜错 `likely` 分支,建议只在确信大概率有收益时使用(如 `WARN_ON`、`IS_ERR_VALUE`)。
## 内核数据结构:侵入式链表与 container_of
内核拥有自己的数据结构集合(链表、哈希映射、红黑树等),其中最常用的是侵入式链表(`list_head`)。需要直接将 `list_head` 成员嵌入结构体。初始化时用 `INIT_LIST_HEAD` 使头节点自指;添加元素时用 `list_add`。遍历使用 `list_for_each_entry` 宏,它通过 `container_of` 宏从链表节点指针反推出宿主结构体的起始地址。`container_of` 通过指针运算和 `offsetof` 实现,本质上是利用结构体内存布局来模拟泛型,是内核实现继承和多态的基础。
## 赞助环节:Let's Get Rusty
本视频由 Let's Get Rusty 赞助。自内核 6.1 起,可以用 Rust 编写驱动,且每个新版本增加更多子系统抽象。Let's Get Rusty 提供专注于系统编程的结构化 Rust 培训,已帮助数千开发者。有兴趣可访问 letsgetrusty.com/startwithmas 或查看视频下方置顶评论链接。
---
**Source:** [C in the Linux Kernel - YouTube](https://www.youtube.com/watch?v=iqqf8YWJhSs)
相似文章
https://www.youtube.com/watch?v=qRLyoP8zOyQ
一篇介绍如何编写自定义CUDA内核以突破深度学习框架瓶颈的技术文章/书籍摘要,涵盖从基础到优化的完整路径。
关于C扩展、可移植性和替代编译器
本文讨论了编写可移植C代码的实际挑战,这些挑战源于对非标准编译器扩展和glibc条件头文件的依赖,并通过构建C编译器的示例进行说明。
@tetsuoai: Linux Socket Programming in C: Cheat Sheet
分享了Linux套接字编程C语言速查表,这是一个面向网络编程开发者的资源。
@alswl: 你知道 Linux 内核有一个 1997 年就引入的特性,让 Apple Silicon Mac 能高效运行 x86 容器吗? 它叫 binfmt_misc,Linux 2.1.43 引入,距今快 30 年了。 原理极其简单: Linux…
文章介绍了Linux内核自1997年引入的binfmt_misc特性,它允许通过注册规则将二进制文件交给指定解释器执行,如今在Apple Silicon Mac上结合Rosetta实现高效运行x86容器,性能接近原生70-90%。
@bellicosiX:这篇读起来真是令人愉悦。写得充满爱意。我很感激。@abhi9u
一篇全面、详尽的博客文章(堪比书籍),涵盖 Linux 中的虚拟内存概念,包括页表、TLB、按需分页、写时复制、内存映射 I/O 以及性能影响,通过进程与内核之间的对话呈现。