Linux 内核编码规范(Kernel Coding Style)完整版详解
简介
本文基于 Linux 官方Documentation/process/coding-style.rst整理,完整梳理 Linux 内核强制遵循的 C 语言编码风格、缩进、括号、命名、函数、宏定义、内存分配、注释等全套规范,适合嵌入式 Linux 驱动、内核开发、内核源码阅读、企业 Linux 项目编码规范参考,可直接作为团队编码规约使用。
Linux 内核编码风格极具个人色彩但已成行业标准,不强制个人审美,但所有内核提交、维护代码必须严格遵守;同时官方建议直接忽略 GNU 编码标准,以 K&R 风格 + Linux 定制规则为准。
一、缩进规范(Indentation)
- Tab 固定 8 个字符,缩进层级同样为 8 字符;禁止使用 4/2 字符缩进,类比「把 π 定义为 3」一样不专业。
- 设计初衷:长时间调试时,大缩进能清晰区分代码块起止;若缩进层级超过 3 层,说明代码逻辑臃肿,需要重构。
- switch-case 对齐规则:
switch和case同列对齐,不二次缩进 case。
switch (suffix) { case 'G': case 'g': mem <<= 30; break; case 'M': case 'm': mem <<= 20; break; case 'K': case 'k': mem <<= 10; fallthrough; default: break; }- 禁止单行多条语句、禁止单行多赋值,内核风格拒绝晦涩技巧表达式。
- 除注释、文档、Kconfig 外,缩进只允许用 Tab,禁止空格;代码行末尾严禁保留空白字符。
二、长行与字符串换行(Breaking long lines and strings)
- 单行代码建议严格 80 列宽度限制。
- 超过 80 列需合理拆分,仅当超宽能显著提升可读性时例外。
- 换行后缩进层级明显靠右,常用对齐到函数左括号的风格;函数长参数列表遵循同样规则。
- 用户态可见字符串(printk 日志等)禁止拆分换行,会导致 grep 检索失效。
三、括号与空格规范(Braces and Spaces)
3.1 括号位置(K&R 风格)
- if/switch/for/while/do等语句:左大括号放在行尾,右大括号独占一行。
if (x is true) { we do y }- 函数特殊规则:函数左大括号另起一行顶格。
int function(int x) { body of function }- 特殊接续场景:
do-while、if-else if-else右大括号不独占空行,紧跟后续关键字。
do { body of do-loop } while (condition); if (x == y) { .. } else if (x > y) { ... } else { .... }- 单语句可省略大括号;但条件分支只要有一个分支是多语句,所有分支必须加括号;循环体内即使简单嵌套也建议加括号。
3.2 空格使用规则
- 关键字后加空格:
if、switch、case、for、do、while。 - 关键字特例不加空格:
sizeof、typeof、alignof、__attribute__。
// 正确 s = sizeof(struct file); // 错误:括号内侧加空格 s = sizeof( struct file );- 指针
*紧贴变量 / 函数名,不紧贴类型:
char *linux_banner; unsigned long long memparse(char *ptr, char **retptr);- 二元 / 三元运算符两侧加单个空格:
= + - < > * / % | & ^ <= >= == != ? :。 - 一元运算符不加空格:
& * + - ~ ! sizeof typeof等;自增自减++ --前后无多余空格。 - 结构体成员符
.->前后不加空格。 - 代码行末尾严禁尾随空格。
四、命名规范(Naming)
- C 语言崇尚简洁,拒绝冗长驼峰命名,临时变量用
tmp、i、j等简短表意名即可。 - 全局变量 / 全局函数必须语义完整,禁止
foo、cntusr这类模糊命名,如统计在线用户应命名count_active_users()。 - 严禁匈牙利命名法:编译器可做类型检查,无需在名字中编码类型。
- 局部变量简短精准,循环计数器直接用
i即可;函数臃肿才需要刻意拉长局部变量名。 - 术语替换规范:
master/slave替换:primary/secondary、leader/follower、controller/device等blacklist/whitelist替换:denylist/allowlist、blocklist/passlist- 仅兼容旧 ABI / 硬件协议时可保留旧术语,新代码强制使用替代词。
五、Typedef 使用规范
原则:能不用 typedef 就坚决不用,仅允许以下 5 种场景:
- 完全透明隐藏的对象:如
pte_t,只能通过官方接口访问内部成员。 - 明确整型类型:规避
int/long平台差异,如u8/u16/u32/u64。 - 配合 sparse 做静态类型检查,创建独立类型。
- 兼容 C99 标准类型的内核自定义别名。
- 用户态内核交互结构体:使用
__u32等兼容类型。
禁止场景:结构体、普通指针不要 typedef,会降低代码可读性、隐藏真实类型。
六、函数设计规范(Functions)
- 函数短小精悍、单一职责,尽量控制在 1~2 个 80x24 屏幕内。
- 函数复杂度越高、缩进越深,长度限制越严格;复杂逻辑拆分为辅助小函数,可由编译器自动内联。
- 局部变量数量建议不超过 5~10 个,过多说明函数逻辑需要拆分。
- 源文件中函数之间用一个空行分隔;导出函数
EXPORT_SYMBOL紧跟函数右大括号。 - 函数原型必须携带参数名,不使用多余
extern关键字。
七、函数集中退出与 GOTO 规范
- 内核不排斥 goto,多出口且需要统一资源清理时优先用 goto。
- 标签命名语义化:如
out_free_buffer,禁止err1、err2无意义编号标签。 - 优势:减少嵌套层级、统一清理逻辑、避免修改时漏改分支、简化编译器优化。
- 资源释放要分层标签,避免空指针释放 bug,尽量覆盖所有异常退出路径。
int fun(int a) { int result = 0; char *buffer; buffer = kmalloc(SIZE, GFP_KERNEL); if (!buffer) return -ENOMEM; if (condition1) { result = 1; goto out_free_buffer; } out_free_buffer: kfree(buffer); return result; }八、注释规范(Commenting)
- 注释只说明做什么、为什么,绝不解释代码怎么实现;烂代码不要靠注释补救,直接重构。
- 函数内部尽量少加注释,复杂逻辑拆函数,注释写在函数头部。
- 内核 API 函数必须使用
kernel-doc规范注释。 - 多行注释标准格式:
/* * This is the preferred style for multi-line * comments in the Linux kernel source code. * Please use it consistently. */- 网络驱动目录
net/、drivers/net/多行注释省略首行空行。 - 变量单行定义、单行注释,不要逗号连写多个变量,预留注释空间。
九、编辑器配置与代码格式化
- Emacs 默认格式不符合内核规范,需自定义
.emacs配置 8 字符缩进、仅 Tab 缩进、显示尾随空格。 - 可使用
indent -kr -i8或 内核脚本scripts/Lindent一键格式化。 - 推荐
clang-format工具:自动格式化、排序头文件、对齐变量、检测风格错误。
十、Kconfig 配置文件规范
config下内容缩进 1 个 Tab,help 文本额外缩进 2 个空格。- 高危功能必须在提示中标注
(DANGEROUS)。 - 详细语法参考
Documentation/kbuild/kconfig-language.rst。
十一、数据结构规范
- 跨线程访问的数据结构必须做引用计数,内核无 GC 全靠手动管理生命周期。
- 引用计数 ≠ 锁:锁保证数据一致性,引用计数防止结构体被提前释放,通常二者搭配使用。
- 支持多级引用计数,如
mm_struct的mm_users / mm_count。 - 只要其他线程能获取到该结构体指针,不加引用计数基本必有内存野指针 bug。
十二、宏、枚举与 RTL 规范
- 常量宏、枚举标签全大写;功能类宏可小写,优先用 inline 函数替代函数式宏。
- 多语句宏必须包裹
do-while(0)。 - 宏四大禁忌:
- 影响函数控制流(随意 return)
- 依赖局部魔法变量名
- 宏参数作为左值赋值
- 忽略运算符优先级,表达式不套括号
- 宏内部局部变量加前缀,避免命名冲突。
十三、内核打印信息规范
- 日志拼写严谨、语句简洁无歧义,句尾不加点号。
- 优先使用内核标准打印接口:
- 设备相关:
dev_err()、dev_warn()、dev_info() - 通用日志:
pr_err()、pr_warn()、pr_info()
- 设备相关:
pr_debug()、dev_dbg()默认不编译,需定义DEBUG或开启动态调试;调试日志和普通日志分开处理。
十四、内存分配规范
- 优先使用内核标准分配器:
kmalloc、kzalloc、kmalloc_array、kcalloc、vmalloc、vzalloc。 - 结构体分配标准写法(避免类型修改漏改 sizeof):
p = kmalloc(sizeof(*p), GFP_KERNEL);- 数组分配用
kmalloc_array,清零数组用kcalloc,自动检测溢出。 - 无需强制转换
void*返回值,C 语言自动隐式转换。 - 分配失败默认栈打印日志,无需额外手动打印失败信息。
十五、Inline 内联函数禁忌
- 不要滥用 inline,过度内联会增大内核镜像、降低 CPU 缓存命中率,整体系统变慢。
- 常规规则:超过 3 行代码不建议加 inline。
- 仅编译期常量参数、替代宏的场景适合用 inline;静态单例函数 GCC 会自动内联,无需手动加关键字。
十六、函数返回值与命名约定
- 动作命令式函数:返回错误码(0 成功、负数失败),如
add_work()。 - 谓词判断式函数:返回布尔(0 失败、非 0 成功),如
pci_dev_present()。 - 指针类函数:用
NULL或ERR_PTR标识失败,返回计算结果本身。 - 导出函数严格遵守该约定,私有静态函数建议遵循。
十七、Bool 类型使用规范
- 内核
bool基于 C99_Bool,仅存 0/1,推荐用true/false替代 1/0。 - 适合存储布尔状态、提升可读性;缓存行对齐、结构体大小敏感场景禁用 bool。
- 多布尔位域优先用位域或
u8统一存储;大量布尔参数合并为 flags 位传参。
十八、禁止重复造内核宏
复用include/linux/kernel.h现有宏,不要自己重写:
- 数组长度:
ARRAY_SIZE(x) - 结构体成员大小:
sizeof_field(t, f) - 类型安全
min/max宏等
十九、禁止编辑器魔性配置
源码中禁止写入 Emacs/Vim 编辑器行配置、模式标记,每个人本地编辑器配置独立,不要污染工程代码。
二十、内联汇编规范
- 仅架构相关底层代码使用,能用 C 实现绝不随便嵌汇编。
- 通用汇编逻辑封装为辅助函数,复杂汇编单独写
.S文件,C 头文件用asmlinkage声明原型。 - 多指令内联汇编每行单独字符串,加
\n\t格式化汇编输出;合理使用volatile防止编译器优化删除。
二十一、条件编译规范
.c文件尽量少用#ifdef,改为头文件定义空桩函数,C 文件无条件调用,编译器自动优化。- 优先整函数条件编译,不要在表达式中间加预编译判断。
- 未使用变量 / 函数用
__maybe_unused修饰,而非嵌套#ifdef。 - 推荐用
IS_ENABLED(CONFIG_XXX)替代#ifdef,编译器常量折叠,同时做语法检查。 #endif后注释对应宏名,提升可读性。
