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报错是否清零;命名是否具备子系统辨识度;注释是否解释了设计决策;提交信息是否能让半年后的自己快速理解。规范不是束缚,而是驱动代码长期可维护的基石。

内核编码规范落地流程 编写代码 Tab缩进 / 80列 / 命名 checkpatch.pl ERROR / WARNING / CHECK 注释与提交信息 kernel-doc / 摘要50字符 提交 git commit checkpatch.pl 典型检查项 ERROR: 空格/括号/缩进错误 WARNING: 行超80列 / 函数过长 CHECK: 对齐 / 注释格式建议 命令示例:git diff | ./scripts/checkpatch.pl --strict - 命名约定速查 局部变量: ret, buf_len 全局函数: i2c_transfer() 宏: MAX_RETRY_CNT 结构体: sensor_dev 规范落地 = 编写约束 + 自动检查 + 人工审查 + 持续复盘