缩进
8个字符
switch和case在一列
把长的行和字符串打散,
- 列不超过80
- 包括长的函数头,参数如果过长,需要拆分为多行
- 不要打散对用户可见的字符串,例如 printk 信息,因为这样就很难对它们 grep。
大括号和空格的放置
起始大括号放在行尾,而把结束大括号放在行首
当只有一个单独的语句的时候,不用加不必要的大括号。
这并不适用于只有一个条件分支是单语句的情况;这时所有分支都要使用大括号:
空格
(1)内核的空格使用方式 (主要) 取决于它是用于函数还是关键字。(大多数) 关键字后要加一个空格。
比如:if, switch, case, for, do, while要放关键字,而sizeof, typeof, alignof 或者 __attribute__后面不放关键字
(2)定义指针或函数返回指针时,*
的首选使用方式是使之靠近变量名或者函数名,而不是靠近类型名。
(3)在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符::
= + - < > * / % | & ^ <= >= == != ? :
(4)但是一元操作符后不要加空格::
命名
typedef
- 注意编辑器在行尾加的空格
- typedef只是为了来隐藏某个对象,如果不需要隐藏,就不要用它
- Linux 特有的等同于标准类型的
u8/u16/u32/u64
- uint32_t来自于C99,而u32来自于内核
函数
函数应该简洁,在一屏或两屏内写完
函数拆为内联函数比只写一个复杂的函数更好
在源文件里,使用空行隔开不同的函数。
int system_is_up(void)
{
return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up); //导出函数,使之能在内核模块之间共享
函数原型声明的顺序
__init void * __must_check action(enum magic value, size_t size, u8 count,
char *fmt, ...) __printf(4, 5) __malloc;
推荐的函数原型元素顺序是:
- 储存类型(下方的 ``static __always_inline`` ,注意 ``__always_inline``
技术上来讲是个属性但被当做 ``inline`` )
- 储存类型属性(上方的 ``__init`` ——即节声明,但也像 ``__cold`` )
- 返回类型(上方的 ``void *`` )
- 返回类型属性(上方的 ``__must_check`` )
- 函数名(上方的 ``action`` )
- 函数参数(上方的 ``(enum magic value, size_t size, u8 count, char *fmt, ...)`` ,
注意必须写上参数名)
- 函数参数属性(上方的 ``__printf(4, 5)`` )
- 函数行为属性(上方的 ``__malloc`` )
集中的函数退出途径
- 多个退出点到一个统一的goto语句中(如果需要做一些清理操作,否则,直接return就行)
- goto行为需要有一个表示其存在意义的标签名
- 单 err 错误:需要将其分离为两个标签
长注释的首选风格
使用工具修正代码风格
-
GNU emacs
-
GNU indent
-
clang-format
Kconfig 配置文件
紧挨着config
定义的行,用一个制表符缩进,然而 help 信息的缩进则额外增加 2 个空
格。
数据结构
如果一个数据结构,在创建和销毁它的单线执行环境之外可见,那么它必须要有一个引用计数器。记录其使用情况。
上锁可以避免数据结构只是因为暂时不用就被释放了,注意上锁 不能 取代引用计数。上锁是为了保持数据结构的一致性,而引用计数是一个内存管理技巧。通常二者都需要,不要把两个搞混了。
很多数据结构实际上有 2 级引用计数,它们通常有不同 类
的用户。子类计数器统
计子类用户的数量,每当子类计数器减至零时,全局计数器减一。
宏,枚举和RTL
-
用于定义常量的宏的名字及枚举里的标签需要大写。
-
通常如果能写成内联函数就不要写成像函数的宏。
-
含有多个语句的宏应该被包含在一个 do-while 代码块里:
-
#define macrofun(a, b, c) \ do { \ if (a == 5) \ do_this(b, c); \ } while (0) PS:while后面没有; 行尾\ /* Common ARM WFI state */ #define ARM_CPUIDLE_WFI_STATE_PWR(p) {\ .enter = arm_cpuidle_simple_enter,\ .exit_latency = 1,\ .target_residency = 1,\ .power_usage = p,\ .name = "WFI",\ .desc = "ARM WFI",\ }
使用宏的时候应避免的事情:
-
影响控制流程的宏:
-
依赖于一个固定名字的本地变量的宏:
-
作为左值的带参数的宏: FOO(x) = y;如果有人把 FOO 变成一个内联函数的话,这种用法就会出错了。
-
忘记了优先级:使用表达式定义常量的宏必须将表达式置于一对小括号之内。带参数的宏也要注意此类问题。
-
在宏里定义类似函数的本地变量时命名冲突:(加上前綴)
-
打印内核消息
<linux/device.h> 里有一些驱动模型诊断宏,你应该使用它们,以确保信息对应于正确的设备和驱动,并且被标记了正确的消息级别。这些宏有:dev_err(), dev_warn(),dev_info() 等等。对于那些不和某个特定设备相关连的信息,<linux/printk.h> 定义了 pr_notice(), pr_info(), pr_warn(), pr_err() 和其他。
分配内存
内核提供了下面的一般用途的内存分配函数:
kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc() 和 vzalloc()。
强制转换一个 void 指针返回值是多余的。C 语言本身保证了从 void 指针到其他任何指针类型的转换是没有问题的。
如果溢出返回 NULL,通用分配函数都会在失败时发起堆栈转储
struct myStruct *ptr;
ptr = malloc(sizeof(struct myStruct)); // 使用结构体类型
// 后续代码中,如果改变了ptr的类型,但忘记更改malloc中的sizeof参数,会导致bug
struct myStruct *ptr;
ptr = malloc(sizeof(*ptr)); // 使用指针变量,提高了代码的可维护性
// 如果ptr的类型变了,sizeof(*ptr) 会自动调整为新类型的大小
内联弊病
inline的使用可能使内核变大
inline修饰的函数代码行数不要超过3行,这个原则的一个例外是,如果你知道某个参数是一个编译时常量,而且因为这个常量你确定编译器在编译时能优化掉你的函数的大部分代码,那仍然可以给它加上 inline 关键字。
kmalloc() 内联函数就是一个很好的例子。
函数返回值及命名
如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代码整数。如果是一个判断,那么函数应该返回一个“成功”布尔值。
比如, add work
是一个命令,所以 add_work() 在成功时返回 0,在失败时返回-EBUSY。类似的,因为 PCI device present
是一个判断,所以 pci_dev_present()在成功找到一个匹配的设备时应该返回 1,如果找不到时应该返回 0。
使用布尔值
使用布尔值时,应使用true和false定义,而不是1和0。
针对对齐和大小进行优化的结构体不应使用布尔。
如果一个结构体有多个true/false值,请考虑将它们合并为具有1比特成员的位域,或使用适当的固定宽度类型,如u8。
在结构体和参数中有限地使用布尔可以提高可读性。
不要重新发明内核宏
头文件 include/linux/kernel.h 包含了一些宏,你应该使用它们,而不要自己写一些
它们的变种。比如,如果你需要计算一个数组的长度,使用这个宏
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
编辑器模式行和其他需要罗嗦的事情
内联汇编
汇编函数的 C 原型应该使用 asmlinkage
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
你可能需要把汇编语句标记为 volatile
在写一个包含多条指令的单个内联汇编语句时,把每条指令用引号分割而且各占一行,除了最后一条指令外,在每个指令结尾加上 \n\t
,让汇编输出时可以正确地缩进下一条指令:
asm ("magic %reg1, #42\n\t"
"more_magic %reg2, %reg3"
: /* outputs */ : /* inputs */ : /* clobbers */);
#define ATOMIC_OP_RETURN(op, asm_op) \
static inline int arch_atomic_##op##_return_relaxed(int i, atomic_t *v) \
{ \
long temp, result; \
__asm__ __volatile__( \
"1: ldl_l %0,%1\n" \
" " #asm_op " %0,%3,%2\n" \
" " #asm_op " %0,%3,%0\n" \
" stl_c %0,%1\n" \
" beq %0,2f\n" \
".subsection 2\n" \
"2: br 1b\n" \
".previous" \
:"=&r" (temp), "=m" (v->counter), "=&r" (result) \
:"Ir" (i), "m" (v->counter) : "memory"); \
smp_mb(); \
return result; \
}
PS:每行\ 没写完的语句\n,输入和输出部不需要
条件编译
只要可能,就不要在 .c 文件里面使用预处理条件 (#if, #ifdef);这样做会让代码更难阅读并且更难去跟踪逻辑。替代方案是,在头文件中用预处理条件提供给那些 .c 文件使用,再给 #else 提供一个空桩 (no-op stub) 版本,然后在 .c 文件内无条件地调用
那些 (定义在头文件内的) 函数。
// foo.h
#ifdef FEATURE_ENABLED
static inline void doSomething() {
// 实现特定功能
}
#else
static inline void doSomething() {
// 空桩函数
}
#endif
// foo.c
#include "foo.h"
void mainFunction() {
doSomething(); // 无条件调用
}
最好倾向于编译整个函数,而不是函数的一部分或表达式的一部分。与其放一个 ifdef在表达式内,不如分解出部分或全部表达式,放进一个单独的辅助函数,并应用预处理
条件到这个辅助函数内。
如果你有一个在特定配置中,可能变成未使用的函数或变量,编译器会警告它定义了但未使用,请把它标记为 __maybe_unused 而不是将它包含在一个预处理条件中。
static int __maybe_unused acpi_button_state_seq_show(struct seq_file *seq,
void *offset)
#ifdef CONFIG_CPU_IDLE
extern int arm_cpuidle_simple_enter(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index);
#define __cpuidle_method_section __used __section("__cpuidle_method_of_table")
#else
static inline int arm_cpuidle_simple_enter(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index) { return -ENODEV; }
#define __cpuidle_method_section __maybe_unused /* drop silently */
#endif
标记为使用过的(__used),这可以防止编译器优化掉这部分代码。
__maybe_unused,意味着如果这部分代码没有被使用,编译器不应产生“未使用警告”。
在代码中,尽可能地使用 IS_ENABLED 宏来转化某个 Kconfig 标记为 C 的布尔表达式,并在一般的 C 条件中使用它:
if (IS_ENABLED(CONFIG_SOMETHING)) {
...
}
在任何有意义的 #if 或 #ifdef 块的末尾 (超过几行的),在 #endif 同一行的后面写下注解,注释这个条件表达式。
#ifdef CONFIG_SOMETHING
...
#endif /* CONFIG_SOMETHING */
参考文献:
linux/Documentation/translations/zh_CN/process/coding-style.rst