我是靠谱客的博主 等待舞蹈,最近开发中收集的这篇文章主要介绍LINUX KERNEL CODING STYLE,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

---------------------------------
Linux内核编码风格
---------------------------------

这篇短小的文档描述了linux内核编程中应该采用的编码风格。编码风格是很个人化的东西,因此我不想把我的观点_强加_给任何人,但是至少在我维护的代码中必须遵守这些规则,当然了,我也强烈推荐你在除了Linux内核之外的其它地方采用这些规则。即使你不打算采用,但也至少请你考虑一下这里的观点。

在开始之前,我建议你先打印一份GNU的编码规范文档,但是不要去读,而是直接把它烧掉,这绝对是个很酷的姿态。

不废话了,下面就让我们开始吧:

---------------------------------
第一章:缩进
---------------------------------

制表符(tabs)占8个字符,所以缩进也是8个字符。但是现在却有一股变态的风气想要使用4个字符(甚至是2个字符)的缩进,这简直和把PI(圆周率) 定为3没什么两样。

缩进的根本目的在于清晰地标识一个控制块的起始。如果你已经连续盯着屏幕看了20个小时,这时你就会体会到更长缩进的好处了。

有些人提出8字符缩进会使得代码太偏向右边,当使用80字符的终端时很难阅读。我的回答是如果你需要三层以上的缩进,那么你已经完蛋了,你应该改改 你的程序了。

总而言之,8字符缩进使得阅读代码更为容易,当你的代码缩进层次过深时会出现警告,你应该留心这样的警告。

除非你需要隐藏一些东西,否则不要将多条语句放在同一行:

if (condition) do_this;
do_something_everytime;

另外,除了注释,文档以及Kconfig文件,不应该使用空格来缩进,上面的例子是对这两条规则的有意破坏。

最后,你需要找一个好点的编辑器并且不要在行尾留下空格。

---------------------------------
第二章:最大行宽
---------------------------------

指定编码风格的主要作用就是增强代码在常用工具中的可读性和易维护性。

因此,行的长度应该严格限制在80字符以内,这是一条硬性规定。

如果一条语句超过了80个字符,那么应该将它分成多行,每一个子行的长度都不应该超过父行,并且子行应该相对于父行进行充分的向右缩进。这条规则同样适用 于参数列表过场的函数头。过长的字符串也应该被分割成多个较短的字符串。

void fun(int a, int b, int c)
{
if (condition)
printk(KERN_WARNING "Warning this is a long printk with "
"3 parameters a: %u b: %u "
"c: %u n", a, b, c);
else
next_statement;
}

---------------------------------
第三章:括号的位置
---------------------------------

括号位置的问题在C编码风格中也经常被提出。和缩进大小不同,括号位置的选择并没有太多技术上的原因,而更多的是个人的喜好。比如Kernighan和 Ritchie的 弟子们都喜欢把左括号放在一行的最后,而把右括号放在一行的开始,就像这样:

if (x is true) {
we do y
}

但是,函数是一种特殊的情况,函数的左括号放在下一行的开始,就像这样:

int function(int x)
{
body of function
}

那些异议人士总是指出这是种不一致的做法 ...嗯...确实是不太一致,但是所有思维正常的人都知道K&R是对的。而且,函数确实是特殊的(你在C 中无法对函数进行嵌套)。

除非右括号的后面还有未完成的语句,否则右括号应该完全占有单独的一行。比如do语句中的"while"或者if语句中的"else",就像这样:

do {
body of do-loop
} while (condition);

或者这样:

if (x == y) {
..
} else if (x > y) {
...
} else {
....
}

除了K&R的原因外,这种括号的布局方法还减少了空行(或者说几乎是空行)的数目,但是没有减小可读性。因为你屏幕上的空行是不可回收资源(这里 想一下25行的 终端屏幕),这样你会有更多的空行用于加注释。

---------------------------------
第四章:命名
---------------------------------

C是个斯巴达式(崇尚简洁风格的)语言,所以你的命名方法也应该如此。与Modula-2和Pascal程序员不同,C程序员不使用 ThisVariableIsATemporaryCounter这样可爱的名字。一个C程序员会把一个变量叫做"tmp",这样的变量名更容易写,而且理解起来也不算太难。

但是,尽管人人都会对大小写混杂的名字皱眉头,全局变量名却必须如此。管一个全局函数叫"foo"是故意找碴。

全局变量(只有在_真正_需要时才使用)需要有个描述性强的名字,这点和全局函数一样。如果你有个函数用于对活跃用户进行计数,你应该叫它 "count_active_users()",而不是"cntusr()"。

把函数的类型加入到名字中(所谓的匈牙利命名法)是脑损伤的表现,编译器知道类型,能够对它进行检查,所以这种命名法只会把程序员自己搞晕。这也就难怪微 软做 了那么多充满bug的程序。

局部变量应该短小扼要。如果你有个随机的整数循环变量,你最好叫它"i"。把它叫做"loop_counter"是效率低下的,在不会发生混淆的情况下。 类似地, "tmp"可以被用于任何类型的存储临时值的变量。

如果你担心混淆你的局部变量,那么你就会有另一个问题,所谓的函数膨胀荷尔蒙失衡综合症,请看下一章。

---------------------------------
第五章:函数
---------------------------------

函数应该短小而甜美,而且只应该做一件事。他们应该只用一两屏幕(我们都知道,ISO/ANSI标准屏幕大小是80x24)就能装下,只做并且做好一件事。

函数的最大长度应该与函数的复杂性和缩进层次成反比。所以,如果你有个只有一个很长(但很简单)的case语句的函数,对许多case做一些很少的操作, 那么 这个函数长点也没有关系。

但是,如果你有一个复杂的函数,你担心一个中等智力的高一学生可能无法理解,那么你应该更严格地遵守最大长度限制,使用有描述性名字的帮助函数(如果你认 为性能很重要的话, 你可以让编译器in-line这些帮助函数,而且编译器恐怕
会比你做的要好)。

函数的另一个指标是局部变量的数目,局部变量的数目不应超过10个,否则一定是哪里有问题了。再设计一下这个函数,把它分解得更小一些。人的大脑一般可以同时跟踪7个不同的东西,超过了7个就会晕菜。虽然你很聪明,不过可能你有时也会想理解一下两星期前所写的代码。

---------------------------------
第六章: goto语句
---------------------------------

虽然有很多人反对,但是事实上编译器仍然在以无条件转移指令的形式使用goto语句。

当函数需要从多个位置退出,并且需要做一些通用的清理工作时,goto语句就显得很方便。

使用goto有以下的好处:

- 无条件转移指令更容易理解和跟踪
- 减少了嵌套
- 防止修改代码时因为没有更新某个退出点而产生的错误。
- 减少了编译器优化冗余代码的工作量:)

int fun(int )
{
int result = 0;
char *buffer = kmalloc(SIZE);

if (buffer == NULL)
return -ENOMEM;

if (condition1) {
while (loop1) {
...
}
result = 1;
goto out;
}
...
out:
kfree(buffer);
return result;
}

---------------------------------
第七章:注释
---------------------------------

注释是好东西,不过存在过分注释的危险。永远不要在注释中解释你的代码是如何工作的:更好的做法是写出工作方式显而易见的代码,解释糟糕的代码是浪 费时间。

一般来说,注释应该说明代码在做什么,而不是怎么做。并且,不要把注释加在函数主体中:如果函数太复杂以至于必须对各个部分进行注释,那么你可能要再去读读第四章。你可以加入一些短小的注释来提醒或警告一些聪明(或难看)的做法,但不要太过度。更好的选择是,把注释放在函数头,说明函数在做什么,当然也可以包括它为什么做。

---------------------------------
第八章:你的代码乱七八糟
---------------------------------

这没什么,我们都遇到过。你可能从老Unix用户那里听说过"GNU emacs"会自动对齐C源代码,但缺省的设置不是很好(事实上,缺省设置比胡乱敲打还糟糕, 一群使用GNU emacs的猴子永远不会做出漂亮的程序)。

所以,你或者彻底仍掉GNU emacs,或者采用更理智的设置。如果选择后者,你可以把下面的代码加到你的.emacs文件中:

(defun linux-c-mode ()
"C mode with adjusted defaults for use with the Linux kernel."
(interactive)
(c-mode)
(c-set-style "K&R")
(setq c-basic-offset cool)

这会定义 M-x linux-c-mode命令。当编写Linux模块时,如果你把字符串"-*- linux-c -*-"放在文件的头两行中,这个模式就会被自动激活。当然,如果你想在编辑/usr/src/linux目录下的源文件时linux -c-mode被自动激活,那么只需要在你的. emacs文件中加入下面的语句就OK了。

(setq auto-mode-alist (cons ''("/usr/src/linux.*/.*.[ch]$" .
linux-c-mode)
auto-mode-alist))

但是即使你用不了emacs,也并不是世界末日:你还可以使用"indent"。

又一次,GNU indent使用了和GNU emacs一样的脑死亡设置,所以你需要给它一些命令行选项。但是,这不算太坏,因为即使是GNU
indent的作者们也意识到了K&R的权威性(GNU的人也不是魔鬼,他们只是在这件事上被误导了),所以你可 以使用选项"-kr-i8"(表示"K&R,8字符缩进")运行indent。

"indent"有很多选项,特别是注释布局部分,你可能想看看它的man手册。但是请记住:"indent"不能修改糟糕的程序。

---------------------------------
第九章:配置文件
---------------------------------

配置选项(比如arch/xxx/Kconfig,以及其它所有的Kconfig文件)使用了有些不同的缩进方式。

帮助文本使用2字符的缩进。

if CONFIG_EXPERIMENTAL
tristate CONFIG_BOOM
default n
help
Apply nitroglycerine inside the keyboard (DANGEROUS)
bool CONFIG_CHEER
depends on CONFIG_BOOM
default y
help
Output nice messages when you explode
endif

通常来说,所有不稳定的选项都应改由CONFIG_EXPERIMENTAL包括,而所有有可能损坏数据的选项(比如文件系统中实验性的写操作)都应改被 标记为(DANGEROUS),其它的实验性选项应该标记为 (EXPERIMENTAL)。

---------------------------------
第十章:数据结构
---------------------------------

供多线程使用的数据结构应该采用引用计数(reference counts)。在内核中,垃圾回收(garbage collection)是不存在的(内核之外的垃圾回收效率不高),这意味着你_必须_使用引用计数。

引用计数的使用能避免锁的使用,使不同的用户能够并行使用数据结构- 不需要担心结构会因为睡眠而突然消失。

注意到加锁_不是_引用计数的替代物。加锁用于保证数据结构的完整性,而引用计数是一个内存管理技术。通常你两个都需要,不应该有任何混淆不清的地方。

一些数据结构可能使用两层的引用计数,当对不同的"类"都有使用的时候。子类的计数统计所有子类用户的数目,当子类的计数为零时只对总计数减一。

这种"多层引用计数"的例子可以在内存管理代码("struct mm_struct":mm_users和mm_cout)和文件系统代码("struct super_block":s_count和s_active)中找到。

记住:如果另一个线程能够看见你的数据结构,而你却没有对它使用引用计数,那么几乎可以肯定会有bug存在。

---------------------------------
第十一章: 宏,枚举,内联函数和RTL
---------------------------------

宏,常量以及枚举中的标签都应该以大写的形式出现。

#define CONSTANT 0x12345

推荐使用枚举来定义一组相关联的常量。

推荐使用大写的宏名称,但是对于功能类似于函数的宏也可以使用小写。

通常来讲,我们推荐使用内联函数来代替类似函数的宏。

有多行语句的宏应该包含在一个do-while块内:

#define macrofun(a, b, c)
do {
if (a == 5)
do_this(b, c);
} while (0)

在使用宏时应该避免以下几种情况:

1) 宏会影响控制流:

#define FOO(x)
do {
if (blah(x) < 0)
return -EBUGGERED;
} while(0)

上面的例子是一个很坏的做法。它看起来像是一个函数,但是它却有可能使调用它的函数提前退出。因此,永远不要在宏中打断原有函数的执行。

2) 宏依赖于局部变量:

#define FOO(val) bar(index, val)

这条语句看起来或许没什么错,但是阅读代码的人却很容易对此产生混淆。并且有可能你不经意间的一个改动也会使代码产生错误。

3) 以左值方式使用的带参数的宏:

FOO(x) = y;

如果你像上面这样定义了一个宏,一旦有人想要将FOO转成一个内联函数,麻烦就来了。

4) 忘记运算优先级:以宏方式定义的常量表达式应该包含在括号内。宏的参数同样如此。

#define CONSTANT 0x4000
#define CONSTEXP (CONSTANT | 3)

cpp的手册中包含有使用宏应该注意的所有知识,gcc的手册同样包含内核中经常与汇编一起使用的RTL方面的知识。

---------------------------------
第十二章: 打印内核消息
---------------------------------

内核开发者应该都喜欢被看成是有文化的,因此一定不要忘记内核消息的拼写检查,这是给用户留下良好印象的基础。不要使用不规范的拼写,比如"dont",而应该使用"do not"或者"don''t"。

内核消息不必在一段时间内中止。

打印内核消息时应该避免将数字放在括号内,比如(%d),这会导致打印出一个空值。

最后

以上就是等待舞蹈为你收集整理的LINUX KERNEL CODING STYLE的全部内容,希望文章能够帮你解决LINUX KERNEL CODING STYLE所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部