4. 内核编码规范
Linux内核代码库拥有超过三千万行代码、数千名贡献者,如果没有统一的编码规范,代码将迅速退化为难以维护的“拼贴画”。内核编码规范不是审美偏好,而是降低协作成本、减少审查摩擦、提升代码可读性的工程约束。本章围绕四个核心维度展开:Linux内核编码风格、checkpatch.pl工具使用、命名约定、注释与提交信息规范。
4.1 为什么驱动开发者必须遵守内核风格
驱动代码最终要进入内核主线或企业内部分支,维护者每天面对大量补丁。风格不一致的补丁会在邮件列表中被打回,浪费作者与审查者双方的时间。更深层的原因是:内核风格直接服务于“可读性优先”这一目标。缩进、括号位置、空格使用、函数长度限制等规则,都是为了让代码在终端、邮件和diff中保持一致的视觉结构,使审查者能快速定位逻辑变化而非格式噪音。
核心原则:内核代码追求“一眼看懂”。风格规则是手段,不是目的。当规则与可读性冲突时,以可读性为准,但需在提交信息中说明理由。
4.2 Linux内核编码风格要点
内核风格文档位于 Documentation/process/coding-style.rst,以下是最常被检查的规则:
- 缩进:使用8个字符的制表符(Tab),而非空格。8字符缩进看似过宽,但能有效限制嵌套层级,迫使开发者拆分函数。
- 行宽:建议不超过80列,超过时需合理换行,换行后对齐参数或运算符。
- 大括号:函数开括号独占一行;
if/for/while等语句开括号不换行,闭括号独占一行(除非后续是else或do)。 - 空格:关键字后加空格(
if (x)),函数名与括号间不加空格(foo(x)),逗号后加空格,运算符两侧加空格。 - 函数长度:单个函数建议不超过50行,局部变量不超过5~10个。过长函数应拆分。
- goto使用:内核允许使用
goto进行集中错误清理,这是驱动代码中常见的合法模式。
/* 符合内核风格的错误处理示例 */
static int sensor_probe(struct platform_device *pdev)
{
struct sensor_dev *sdev;
int ret;
sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
if (!sdev)
return -ENOMEM;
ret = sensor_init_hw(sdev);
if (ret)
goto err_free;
ret = sensor_register(sdev);
if (ret)
goto err_hw;
return 0;
err_hw:
sensor_deinit_hw(sdev);
err_free:
devm_kfree(&pdev->dev, sdev);
return ret;
}
4.3 checkpatch.pl:自动化风格守门员
checkpatch.pl是内核源码树中scripts/目录下的Perl脚本,用于检查补丁或文件是否符合编码风格。它不能替代人工审查,但能拦截大量低级格式错误。
常用命令如下:
# 检查单个文件
./scripts/checkpatch.pl --no-tree -f drivers/misc/my_driver.c
# 检查git提交补丁
git format-patch -1 --stdout | ./scripts/checkpatch.pl -
# 检查工作区未提交改动
git diff | ./scripts/checkpatch.pl -
# 严格模式,将警告视为错误
./scripts/checkpatch.pl --strict --no-tree -f drivers/misc/my_driver.c
输出中常见类型包括:ERROR(必须修复)、WARNING(建议修复)、CHECK(可选优化)。例如:
ERROR: space required after that ',' (ctx:VxV)
WARNING: line over 80 characters
CHECK: Alignment should match open parenthesis
实践建议:将checkpatch.pl集成到Git钩子或CI流水线中,在提交前自动运行。对于历史遗留代码,可先使用--ignore忽略特定类型,逐步收敛。
4.4 命名约定与注释规范
内核命名强调简洁、一致、可搜索。局部变量宜短,全局符号需带子系统前缀。以下是常见约定:
| 对象类型 | 命名风格 | 示例 | 说明 |
|---|---|---|---|
| 局部变量 | 小写+下划线 | ret, buf_len | 避免无意义缩写 |
| 全局函数 | 子系统前缀+动词 | i2c_transfer() | 便于grep定位 |
| 静态函数 | 小写+下划线 | sensor_read_reg() | 无需前缀 |
| 宏定义 | 全大写 | MAX_RETRY_CNT | 避免与函数冲突 |
| 结构体 | 小写+下划线 | struct sensor_dev | 不用驼峰 |
| 枚举 | 小写+下划线 | enum sensor_state | 成员全大写或前缀 |
注释方面,内核要求“解释为什么,而非做什么”。函数头推荐使用kernel-doc格式,便于自动生成文档:
/**
* sensor_read_reg - 读取传感器寄存器
* @sdev: 传感器设备指针
* @reg: 寄存器地址
* @val: 输出值指针
*
* 返回0表示成功,负值表示错误码。
* 调用者需持有sdev->lock。
*/
static int sensor_read_reg(struct sensor_dev *sdev, u8 reg, u8 *val)
4.5 提交信息规范
内核提交信息有严格格式:第一行是简短摘要(不超过50字符),空一行后是详细描述。摘要格式通常为子系统: 动词+对象。详细描述需说明问题背景、修改原因、影响范围。若修复bug,应包含Fixes:标签;若关联邮件列表讨论,可加Link:。
i2c: sensor: fix null pointer dereference in probe
The sensor_probe() function calls sensor_init_hw() before
checking the return value of devm_kzalloc(). If allocation
fails, a null pointer dereference occurs.
Add a null check and return -ENOMEM immediately.
Fixes: 3a7b1c2d4e5f ("i2c: sensor: add initial driver")
Signed-off-by: Zhang San <zhangsan@example.com>
常见误区:提交信息写“fix bug”“update code”这类无信息量摘要;一个补丁混合多个不相关修改;注释重复代码字面含义;使用驼峰命名;缩进混用Tab和空格。这些都会导致补丁被拒或审查周期拉长。
4.6 规范落地流程与复盘
将规范落地为可执行流程:编写代码时遵循风格;提交前运行checkpatch.pl;使用git commit --amend修正提交信息;推送前用git rebase -i整理补丁序列。团队可维护一份.checkpatch.conf统一检查选项。
复盘时重点关注:checkpatch报错是否清零;命名是否具备子系统辨识度;注释是否解释了设计决策;提交信息是否能让半年后的自己快速理解。规范不是束缚,而是驱动代码长期可维护的基石。