文章

Linux内核学习笔记之编码风格

几个推荐的网站或邮件列表:

Linux 的编码风格记录在源码树的 <Documentation/CodingStyle>

缩进

缩进风格是用制表符(Tab)每次缩进8 个字符长度。

1
2
3
4
5
6
static void get_new_ship(const char *name)
{
	if (!name)
		name = DEPAULT_SHIP_NAME;
	get_new_ship with_name(name);
}

8 个字符长度的缩进能让不同的代码块看起来一目了然,特别是在连续几个小时的开发之后,效果更加明显。

随着缩进层数的增加,8 字符制表位的左侧可用空间就所剩不多了。所以 Linus Torvalds 要求代码中的缩进不应超过三层。如果层级较多,需要重构你的代码,把复杂的层次关系(为此形成多层缩进)分解为独立的功能。

Linux 内核的核心代码(非驱动或体系特定代码)中几乎没有超过三层(3 个 tab)的代码。

switch 语句

case 与 switch 齐平来减少缩进层次。如果 case 内不使用 break 离开,而是继续进入下一个 case 则需要注释说明

1
2
3
4
5
6
7
8
9
10
11
12
13
switch (animal){
case ANIMAL_CAT:
	handle_cats();
	break;
case ANIMAL_WOLF:
	handle_wolves();
	/* fall through */
case ANIMAL_DOG:
	handle_dogs();
	break;
default:
	printk(KERN_WARNING "Unknown animal %d!\n", animal);
}

空格

关键字和左圆括号间加空格:

1
2
3
4
5
6
7
if (foo)

while (foo)

for (i = 0; i < NR_CPUS; i++)

switch (foo)

函数以及与函数相像的关键字(例如 sizeof、typeof 以及 alignof)在关键字和圆括号之间没有空格:

1
2
3
4
5
6
wake_up_process(task);
size_t nlongs = BITS_TO_LONG(nbits);
int len = sizeof(struct task_struct);
typeof(*p)
__alignof__(struct sockaddr *)
__attribute__((packed))

对于大多数二元或者三元操作符(+,-,?,&),在操作符的两边加上空格:

1
2
3
4
5
6
7
8
9
int sum = a + b;
int product = a * b;
int mod = a % b;
int ret = (bar) ? bar : 0;
return (ret ? 0 : size);
int nr = nr ? : 1; /* allowed shortcut, same as "nr ? nr: 1" */
if (x < y)
if (tsk->flags & PF_SUPERPRIV)
mask = POLLIN | POLLRDNORM;

对于大多数一元操作符(++,–,!,&,~),在操作符和操作数之间不加空格:

1
2
3
4
5
6
if (!foo)
int len = foo.len;
struct work_struct *work = &dwork->work;
foo++;
--bar;
unsigned long inverted = ~mask;

提领运算符(*)应该贴近变量,而不是类型(TODO:有待商榷,个人还是比较喜欢贴近类型):

1
char *strcpy(char *dest, const char *src)

花括号

左花括号和标识符放在同一行;如果右花括号后还有同一语句块的另一个标识符,则都放在同一行:

1
2
3
4
5
6
7
if (ret) {
	sysctl_sched_xt_period = old_period;
	sysctl_sched_rt_runtime = old_runtime;
} else {
	def_rt_bandwidth.rt_runtime = global_rt_runtime();
	def_rt_bandwidth.rt_period = ns_to_ktime(global_rt_period());
}
1
2
3
do {
	percpu_counter_add(&ca->cpustat[idx], val); ca = ca->parent;
} while (ca);

如果是函数,则左花括号另起单独一行:

1
2
3
4
void read_persistent_clock(struct timespec *ts)
{
	__read_persistent_clock(ts);
}

每行代码的长度

每行代码的长度不超过 80 个字符。

圆括号内参数可以分行,一般会让参数对齐,但没有明确规定(如果使用 tab 制表符无法对齐,可以使用空格来微调):

1
2
3
static void get_new_parrot(const char *name,
			   unsigned long disposition,
			   unsigned long feather_quality)

命名规范

不允许大小写混合,如驼峰命名 theLoopIndex。

变量和函数使用小写字母,必要时使用下划线(_)区分单词。而且尽量要表达含义(如get_active_tty()),不要使用难以理解的缩写(如atty())

函数

函数的代码长度不应该超过两屏(按照 80x24 的标准终端,应该是 48 行),局部变量不应超过 10 个。

一个函数应该功能单一并且实现精准,如果功能过于复杂,可以将一个函数分解成一些更短小的函数的组合。

如果你担心函数调用导致的开销,可以使用inline关键字。

注释

注释的话只要按照 C 风格就行(/* */),具体的内部定义可以按自己喜好

注意单行注释不要使用 C++ 风格的双斜杠 //

多行注释示例:

1
2
3
4
5
/*
 * get_ship_speed() - return the current speed of the pirate ship
 * We need this to calculate the ship coordinates.As this function can sleep,
 * do not call while holding a spinlock
 */

typedef

内核开发者们强烈反对使用 typedef 语句。他们的理由是:

  • typedef 掩盖了数据的真实类型。
  • 由于数据类型隐藏起来了,所以很容易因此而犯错误,比如以传值的方式向栈中推入结构(本应该是指针,如果结构较大,传值的方式会导致栈溢出)。
  • 使用 typedef 往往是因为想要偷懒

尽量少用 typedef ,必要的时候再用,内核在 <linux/types.h> 中使用了较多的 typedef。对于 struct 的定义,不使用 typedef:

1
2
3
4
struct completion {
	unsigned int done;
	wait_queue_head_t wait;
};

多用现成的东西

对于已有的功能不要自己造轮子。内核本身就提供了字符串操作函数、压缩函数和一个链表接口,所以请使用它们。

不要为了让接口通用化而封装内核提供的接口。隐藏实现有时不一定是好事,特别是操作系统级别的编程时。

在源码中减少使用 ifdef

我们不赞成在源码中使用 ifdef 预处理指令。(TODO:实际上用的还挺多的)

结构初始化

使用 C99 风格,允许不明确定义初值,使用默认值(如指针被设为 NULL,整型被设为 0,浮点数被设置为 0.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct foo {
	int a;
	char b;
	long c;
}

// C99 风格
struct foo my_foo = {
	.a = INITIAL_A,
	.b = INITIAL_B
};

// GNU 风格
struct foo my_foo = {INITIAL_A, INITIAL_B};

本例中 c 未被明确赋予初值,使用默认值 0。

代码格式化工具

可以使用各种工具,比如 indent:

1
indent -kr -i8 -ts8 -sob -180 -ss -bs -psl <file>

也可以直接使用源码目录中的 scripts/Lindent 脚本

参考

本文由作者按照 CC BY 4.0 进行授权

© Kai. 保留部分权利。

浙ICP备20006745号-2,本站由 Jekyll 生成,采用 Chirpy 主题。