Linux内核编程规范与代码风格

source: https://www.kernel.org/doc/html/latest/process/coding-style.html
translated by trav, travmymail@gmail.comhtml

这是一篇阐述Linux内核编程代码风格的文档,译者以学习为目的进行翻译。linux

1 缩进

Tab的宽度是八个字符,所以缩进的宽度也是八个字符。有些异教徒想让缩进变成四个字符,甚至是两个字符的宽度,这些人和那些把 PI 定义为 3 的人是一个路子的。程序员

注意:缩进的所有意义在于清晰地定义语句块的开始与结束,特别是当你盯着屏幕20个小时以后,你会发现长的缩进宽度的做用。编程

如今有些人说八个字符的宽度太宽了,这会让代码往右移很远,在一块八十字符宽的屏幕上,这样的代码会很难阅读。对此的回答是,若是你写的代码须要超过三层的缩进,那么你把一切都搞砸了,你应该修复你的程序。vim

简而言之,八个字符宽度的缩进让代码更容易阅读,而且额外的好处就是提醒你,不要在一个函数里写太多层的嵌套逻辑。请记住这个警示。数组

switch语句的缩进方式是让case与switch对齐:缓存

switch (suffix) {
case 'G':
case 'g':
        mem <<= 30;
        break;
case 'M':
case 'm':
        mem <<= 20;
        break;
case 'K':
case 'k':
        mem <<= 10;
        /* fall through */
default:
        break;
}

不要在单独一行里写多个语句,除非你想干什么鲜为人知的事:安全

if (condition) do_this;
  do_something_everytime;

对了,不要把多个赋值语句放在同一行,内核的代码风格是十分简洁的,请尽可能避免使用复杂的表达式。数据结构

除了在注释、文档和Kconfig中,永远不要使用空格做为缩进,上面的例子是故意犯的错误。app

找一个像样的编辑器,不要在行末留有空格。

2 换行

规范代码风格的目的是提升代码的可读性和维护性。

单行的宽度限制为八十列,这是强烈推荐的设置。

任何一行超过八十列宽度的语句都应该拆分红多个行,除非超过八十列的部分能够提升可读性且不会隐藏信息。拆分出来的子句长度老是应该比其主句要短,而且应该尽可能靠右。这条法则一样适用于一个有很长的参数列表的函数头。然而,千万不要把用户可见的字符串,好比 printk 的信息,拆分红多行,由于这样会致使使用 grep 的时候找不到这些信息。

3 括号与空格

另外一个关于 C 代码风格的议题就是大括号的位置。这个问题不像缩进那么具备技术性,咱们并不能说某一种风格要在技术上优于另外一种风格。可是咱们更推荐的,就是有远见的 Kernighan 和 Ritchie 展现的方式,把左括号放在行末,把右括号放在行首:

if (x is true) {
        we do y
}

这一样适用于其余非函数的语句块 (if, switch, for, while, do) :

switch (action) {
case KOBJ_ADD:
        return "add";
case KOBJ_REMOVE:
        return "remove";
case KOBJ_CHANGE:
        return "change";
default:
        return NULL;
}

然而,有一个特殊的例子,就是函数:函数的左括号应该放在行首:

int function(int x)
{
        body of function
}

异教徒们会认为这样的风格是不一致的,可是全部有脑子的人都知道尽管是 K&R 也是不一致的(译者注:K&R这本书的初版和第二版有不一致的地方)。除此以外,咱们知道函数是很特殊的,在 C 语言中,你不能有嵌套函数。

注意到,右括号通常是单独成一行的,除非右括号以后紧随着紧密结合的语句,例如 do-while 语句和 if 语句:

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

以及

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

依据:K&R

注意到,这种风格应该在不下降可读性的前提下尽量减小空行的数量。想想,在一块只有 25 行的屏幕上,无用的换行少了,那么就有更多的空行来写注释。

当单行语句能够解决的时候,不要使用不必的括号:

if (condition)
        action();

以及

if (condition)
        do_this();
else
        do_that();

这一点不适用于只有一个 case 有单行,其余 case 有多行的状况:

if (condition) {
        do_this();
        do_that();
} else {
        otherwise();
}

在一个循环中超过一个语句的状况也一样须要使用括号:

while (condition) {
        if (test)
                do_something();
}

3.1 空格

Linux 内核风格的空格主要用在一些关键字上,即在关键字以后添一个空格。值得关注的例外是一些长得像函数的关键字,好比:sizeof, typeof, alignof, attribute,在 Linux 中,这些关键字的使用都会带上一对括号,尽管在 C 语言的使用上并不须要带上括号。

因此在下面这些关键字以后添加一个空格:

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);
char *match_strdup(substring_t *s);

在二元操做符和三元操做符周围添加一个空格,例如:

=  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=  ?  :

可是不要在一元操做符以后添加空格:

&  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined

不要在后缀的自增自减一元操做符以前添加空格:

++  --

不要在前缀的自增自减一元操做符以后添加空格:

++  --

不要在结构体成员操做符周围添加空格:

.  ->

不要在行末添加多余的空格。一些编辑器的“智能”缩进会帮你在行首添加一些空格,好让你在下一行能够当即写代码。可是某些编辑器不会帮你把多余的空格给删掉,尽管你已经写完了一行代码。好比你只想留一行空行,可是编辑器却“好心”地帮你填上了一些空格。这样一来,你就在行末添加了多余的空格。

Git 一般会警告你,让你除去这些多余的空格,而且能够帮你删掉这些东西。可是,若是你让 Git 一直帮你这样修补你的代码,这极可能致使代码行的上下错乱,以后的自动修补的失败。

4 命名

C 是一种简洁粗旷的语言,所以,你的命名也应该是简洁的。C 程序员不会像 Modula-2 和 Pascal 程序员那样使用 ThisVariableIsATemporaryCounter 这种“可爱”的名字,一个 C 程序员会把这种变量命名为 tmp ,如此简洁易写。

尽管看到一个混合大小写的名字让人皱眉,不过对于全局变量来讲,一个具备描述性的名字仍是颇有必要的。去调用一个名为 foo 的全局函数一样让人难以接受。

全局变量(只有当你真正须要的时候才用它)和全局函数须要使用描述性的名字。若是你有一个计算活跃用户数量的函数,你应该起这样一个名字 count_active_users() 或者相似的,而不是这样一个名字 cntusr()

起一个包含函数类型的名字(匈牙利命名法)是摧残大脑的行为,编译器知道函数的类型而且会检查类型,这样的名字不会起到任何帮助,它仅仅会迷惑程序员。因此,也难怪微软作出了那么多充满了 bug 的程序。

局部变量名应该简短,若是你须要写一个循环,定义一个计数器,在不产生歧义的状况下,你大可命名为 i ,命名为 loop_counter 是生产力很低的行为。一样地,tmp 能够是任何类型的临时变量。

若是你担忧会弄混变量名,那么你遇到了另外一个问题,你患上了函数增加荷尔蒙失调综合症。

5 Typedefs

请不要使用 vps_t 这种东西,这是 typedef 的错误用法,当你看到

vps_t a;

这种写法时,它到底是个什么东西?相反,若是是这样的写法

struct virtual_container *a;

你就很容易知道 a 表明着什么。

不少人认为 typedef 是用来帮助提升可读性的,可是事实每每不是这样的。typedef 仅仅有以下用处:

a. 封装对象(typedef 能够方便的隐藏对象)

例如,pte_t 会把对象封装起来,你仅仅只能经过合适的“访问函数”(成员函数)来访问这个对象。

注意:封装和“访问函数”(成员函数)自己就不是好东西,咱们使用 pte_t 这种东西的理由就是,它指向的对象自己绝对没有东西能够访问(咱们压根儿不使用封装和成员函数那一套)。

b. 指明整数类型,这种抽象能够帮助咱们避免一些使用 int 和 long 的疑虑

u8/u16/u32 是完美的使用 typedef 的例子。

注意:你必需要有明确的理由来使用这些用法,若是一些地方使用的自己就是 unsigned long ,那么你没有任何理由这样作

typedef unsigned long myflags_t;

可是若是你有明确的理由来解释为何在某种状况下使用 unsigned int,而在其余状况下使用 unsigned long,那么大可以使用 typedef。

c. 使用 sparse 去新建一个类型来作类型检查

d. 在某些状况下新建一个与 C99 标准相等的类型

尽管只须要花一小段眼睛和大脑的时间来适应新标准的类型,如 uint32_t,可是一些人仍是反对使用他们。

所以,你可使用 Linux 独有的 u8/u16/u32/u64 和他们的有符号版本,也可使用和他们等价的新标准的类型,他们的使用都不是强制的。

当你所编辑的代码已经使用了某一种版本时,你应该按照原样使用相同的版本。

e. 用户空间中的类型安全

用户空间中的某些特定的结构体中,咱们不能使用 C99 定义的新类型以及上述的 u32,取而代之,咱们统一使用 __u32 之类的类型。

也许还有其余状况,可是基本的规则就是,若是你不能知足上述其中一条状况,你就永远不要使用 typedef。

一般,一个指针或者一个有可访问元素的结构体,都不该该使用 typedef。

6 函数

函数应该短小精悍,一个函数只干一件事。一个函数的代码两个屏幕就应该装得下(ISO/ANSI标准屏幕大小是80x24),简单说就是,作一件事而且把它作好。
数的最大长度与函数的复杂度和缩进程度成反比,因此,若是你有一个简单的函数,函数里面只是须要处理一个又一个的 case,每一个 case 只是干一些小事,函数长度长一些也不要紧。

然而,若是你的函数十分复杂,你怀疑一个不像你同样天才的高中生看不懂,你应该遵照函数最大的长度的限制,使用一些有描述性名称的辅助函数。若是你认为函数的性能相当重要,你可让编译器把这些辅助函数编译成内联函数,通常状况下编译器能够比你作得更好。

另外一个测量函数的因素是局部变量的数量,他们不该该超出5-10个这个范围,不然你就犯了一些错误。从新思考这个函数,把它拆分红更小的几段。人类的大脑通常只能同时关注七件不一样的事,更多须要关注的事情意味着更多的困扰。尽管你认为你是个天才,可是你也但愿理解一段你两周以前写的代码。

在源文件中,用一个空行分割不一样的函数,若是函数须要导出到外部使用,那么它对应的 EXPORT 宏应当紧随在函数以后,例如:

int system_is_up(void)
{
        return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);

函数原型中,参数名应该与参数类型引发写出来,尽管 C 语言容许只写上参数类型,可是咱们更推荐参数名,由于这是一种为读者提供有价值信息的简单方式。

不要在函数原型以前使用extern关键字,由于这是没必要要且多余的。

7 集中函数出口

尽管许多人反对,可是 goto 语句频繁地以无条件跳转的形式被编译器使用。

当函数有多个出口,而且返回以前须要作不少类似的工做时,好比清理空间,这时候 goto 语句是十分方便的。固然了,若是没有相似的清理工做要在返回以前作,那么直接返回便可。

根据 goto 的做用来决定一个 label 的名字,若是 goto 语言要去释放缓存,那么out_free_buffer:会是一个好名字。避免使用 GW-BASIC 的命名方式,好比 err1: err2:,由于当你须要新加或者删除某些函数出口时,你就须要从新排列标签数字,这会让代码的正确性难以获得保证。

使用 goto 的理由以下:

无条件跳转易于理解和阅读

能够减小嵌套

能够减小修改个别函数出口代码所形成的错误

算是帮助编译器作了一些优化的工做

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

        buffer = kmalloc(SIZE, GFP_KERNEL);
        if (!buffer)
                return -ENOMEM;

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

一个常见的 bug 被称做 one err bug,它长得像这样:

err:
        kfree(foo->bar);
        kfree(foo);
        return ret;

bug 在于某些 goto 语句跳转到此时,foo 仍然是 NULL,修复此 bug 的简单方式就是将一个 label 拆分红两个,err_free_bar:err_free_foo:

err_free_bar:
       kfree(foo->bar);
err_free_foo:
       kfree(foo);
       return ret;

事实上,你应该进行测试,模拟错误状况的发生,测试全部的出口代码。

8 注释

注释是好的,可是要避免过度注释。永远不要去尝试解释你的代码如何工做,而是花时间在写出好的代码来,解释一段烂代码是浪费时间。

通常来讲,你应该去说明你的代码作了什么,而不是怎么作。一样地,尽可能避免在函数体内写注释,若是你的函数如此复杂,以至于你须要在函数体内分几段注释来解释,那么你应该回到第六节去看看。你能够写一小段的注释来标记或者提醒你们哪些地方写得真聪明(或者真烂),可是不要作得太过度。除此以外,你应该把注释写在函数开头,告诉人们这个函数干了什么,为何要这样干。

当你给 kernel API 进行注释的时候,请你使用 kernel-doc 的格式。具体参见 https://www.kernel.org/doc/html/latest/doc-guide/index.html#doc-guide

多行注释推荐的格式以下:

/*
 * This is the preferred style for multi-line
 * comments in the Linux kernel source code.
 * Please use it consistently.
 *
 * Description:  A column of asterisks on the left side,
 * with beginning and ending almost-blank lines.
 */

对于在 net/ 和 drivers/net/ 中的文件,推荐的多行注释格式以下:

/* The preferred comment style for files in net/ and drivers/net
 * looks like this.
 *
 * It is nearly the same as the generally preferred comment style,
 * but there is no initial almost-blank line.
 */

对一些数据和变量进行注释也是必要的,不管他们是基本类型的仍是派生类型的。为了进行注释,你应该在一行内只声明一个变量,不要使用逗号进行多个声明,这让你有地方对每个变量进行注释。

9 你已经弄得一团糟

不要紧,咱们都犯过错。你的那些 Unix 的老手朋友们可能会告诉你,GNU emacs 能帮你自动地对 C 代码进行排版,你也注意到它确实能够。可是它默认的排版方式真的很糟糕,事实上,即使是在键盘上乱敲也比它来的好看。相信我,无数的猴子在 GNU emacs 上乱敲是不会作出好的程序的。

所以,你能够选择直接把 GNU emacs 给删了,或者修一修它,让它恢复正常。若是你选择了后者,那么请把下面的东西拷贝到你的 .emacs 文件中:

(defun c-lineup-arglist-tabs-only (ignored)
  "Line up argument lists by tabs, not spaces"
  (let* ((anchor (c-langelem-pos c-syntactic-element))
         (column (c-langelem-2nd-pos c-syntactic-element))
         (offset (- (1+ column) anchor))
         (steps (floor offset c-basic-offset)))
    (* (max steps 1)
       c-basic-offset)))

(add-hook 'c-mode-common-hook
          (lambda ()
            ;; Add kernel style
            (c-add-style
             "linux-tabs-only"
             '("linux" (c-offsets-alist
                        (arglist-cont-nonempty
                         c-lineup-gcc-asm-reg
                         c-lineup-arglist-tabs-only))))))

(add-hook 'c-mode-hook
          (lambda ()
            (let ((filename (buffer-file-name)))
              ;; Enable kernel mode for the appropriate files
              (when (and filename
                         (string-match (expand-file-name "~/src/linux-trees")
                                       filename))
                (setq indent-tabs-mode t)
                (setq show-trailing-whitespace t)
                (c-set-style "linux-tabs-only")))))

这会让你的 emacs 更好地知足内核的代码风格。

可是即便你不能让你的 emacs 恢复正常,也有解救方法:使用 indent 。

一样的问题出现了,GNU indent 和 GNU emacs 有一样的问题,所以你须要一些命令行选项来进行配置。可是事情也没那么糟,由于 GNU indent 的制造者认可 K&R 的权威性,因此你只须要添加命令行参数 -kr -i8 (表示 K&R,8个字符宽的缩进),或者使用 scripts/Lindent 也能够。

indent 有不少命令行选项,特别是注释的格式化方面,你能够经过 man 帮助页面来查看,不过请记住:indent 不是用来修复烂程序的。

注意:你也可使用 clang-format 来完成这些格式化的工做,具体参见 https://www.kernel.org/doc/html/latest/process/clang-format.html#clangformat

10 Kconfig 配置文件

对于 Linux 中的 Kconfig 配置文件,他们的缩进是有所不一样的。在 config 定义下的缩进是一个 tab,而里面的 help 文本是两个空格,例如:

config AUDIT
      bool "Auditing support"
      depends on NET
      help
        Enable auditing infrastructure that can be used with another
        kernel subsystem, such as SELinux (which requires this for
        logging of avc messages output).  Does not do system-call
        auditing without CONFIG_AUDITSYSCALL.

而对于有可能致使危险的动做(好比特定文件系统的写支持),你应该在提示文本中直接指出:

config ADFS_FS_RW
      bool "ADFS write support (DANGEROUS)"
      depends on ADFS_FS
      ...

具体细节参见 Documentation/kbuild/kconfig-language.txt

11 数据结构

对于单线程环境里建立和销毁的一些数据结构,若是他们对于线程外是可见的,那么老是应该有引用计数。在内核里,垃圾收集器(GC)是不存在的,这意味着你必须对你使用过的数据进行引用计数。

进行引用计数意味着你能够避免死锁,容许多个用户并行访问数据,而且不用担忧数据由于睡眠或者其余缘由而找不到。

注意,锁不是引用计数的替代品。锁是为了保持数据的一致性,而引用计数是一种内存管理计数。一般这两种技术都是须要的,咱们不要把他们搞混。

当有多个不一样类的使用者时,不少数据结构会使用二级引用计数。第二级的引用计数会统计第二级使用者的数量,只有当第二级引用计数递减至零时,全局的第一级引用计数才会减一。

这种多级引用计数在内存管理(struct mm_struct: mm_users and mm_count)和文件系统(struct super_block: s_count and s_active)中都有使用。

记住,若是其余线程能够发现并使用你的数据结构,而你却没有引用计数,那么这基本就是一个 bug。

12 宏、枚举与RTL(Real Time Linux)

常量宏和枚举的命名都是大写的。

#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)

用宏来定义常量的时候,必需要括上括号,带有参数的宏也要注意。

5) 在定义宏函数时发生命名冲突

#define FOO(x)                          \
({                                      \
        typeof(x) ret;                  \
        ret = calc_ret(x);              \
        (ret);                          \
})

ret 是一个很容易和局部变量发生冲突的名字,而 __foo_ret 这样的名字则不多会发生冲突。

C++ 手册全面地阐述了宏定义的细节,gcc 手册一样也阐述了汇编语言使用的 RTL 规则,具体请自行查看。

13 打印内核信息

内核开发者喜欢被视为有素养的,好的英文拼写和准确的内核信息能给人留下好的印象,所以,不要使用一些单词的缩写,好比 dont,而是 do not 或者 don't。把提示信息写得尽量准确、清晰、无二义。

内核信息不须要在末尾加上句号

在圆括号中打印数字(%d)没有任何意义,应该避免这样干。

在<linux/device.h>中有许多驱动模型的诊断宏,你应该使用这些宏来确保消息匹配正确的设备和驱动,并正确的标记它们的级别:dev_err(), dev_warn(), dev_info(), and so forth。对于没有关联特定设备的消息,<linux/printk.h>中定义了 pr_notice(), pr_info(), pr_warn(), pr_err(), etc。

编写好的调试信息是一项巨大的挑战,一旦你完成了,这些信息会对远程调试产生巨大帮助。调试信息与普通讯息不一样,pr_XXX() 函数在任何条件下都会进行打印,而 pr_debug() 却不是,这些与调试有关的函数默认都不会被编译,除非你定义了一个 DEBUG 宏或者 CONFIG_DYNAMIC_DEBUG 宏来显式地让编译器编译他们。还有一个惯例就是使用 VERBOSE_DEBUG 为那些已经开启 DEBUG 的用户添加 dev_vdbg() 消息。

不少子系统在对应的 makefile 里都有 Kconfig 调试选项来打开 -DDEBUG,或者是在文件里定义宏 #define DEBUG。当调试信息能够被无条件打印,或者说已经编译了和调试有关的 #ifdef 段,那么 printk(KERN_DEBUG ...) 就能够用来打印调试信息。

14 分配内存

内核提供了下面这些通用的内存分配器:kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc(), and vzalloc()。具体细节参见 API 文档。

为一个结构体分配内存的形式最好是这样的:

p = kmalloc(sizeof(*p), ...);

另外一种写出结构体名字的方式(sizeof(struct name))会破坏可读性而且给 bug 制造了机会:修改结构体名字却忘了修改对于的 sizeof 语句。

另外,在 malloc 以前添加上一个强制的类型转换,把空类型的指针转换为特定类型的指针,这些是画蛇添足的操做,他们应当交给编译器来干,而不是你。

分配一个数组的形式最好是这样的:

p = kmalloc_array(n, sizeof(...), ...);

分配一个零数组的形式最好是这样的:

p = kcalloc(n, sizeof(...), ...);

两种形式都会检查溢出,而且溢出发生时返回一个空指针 NULL。

15 内联之灾

一个很常见的误解就是,人们认为 gcc 有一种让他们的程序跑得更快的魔法,就是内联。然而,内联每每也有不合适的用法(例如第十二节提到的替换宏)。inline 关键字的泛滥,会使内核变大,从而使整个系统运行速度变慢,由于大内核会占用更多的CPU高速缓存,同时会致使可用内存页缓存减小。想象一下,一次页缓存未命中就会致使一次磁盘寻址,这至少耗费5毫秒。5毫秒足够CPU运行不少不少的指令。

一个基本的原则就是,若是一个函数有3行以上的代码,就不要把它变成内联函数。有一个例外,若某个参数是一个编译时常数,且你肯定由于这个常量,编译器在编译时能优化掉函数的大部分代码,那么加上 inline 关键字。kmalloc() 就是个很好的例子。

人们常常主张能够给只用一次的静态函数加上 inline 关键字,这样不会有任何损失。虽然从技术上来讲这样没错,可是实际上 gcc 会自动内联这些函数。

16 函数返回值与名称

函数能够返回不一样种类的值,可是最广泛的就是表示运行成功或失败的值。这样的值能够用预先定义好的错误码表示(-Exxx = failure, 0 = success),或者一个布尔值(0 = failure, non-zero = success)

混合两种方式会使代码变得复杂,而且很难找到 bug。若是C语言能明确区分整型和布尔型,那么编译器会替咱们发现这个问题……可是它不会那么作。为了不这种问题,必定要谨记以下约定:

若是函数名是一个短语,表示的是一个动做,或者一个命令,那么返回值应该使用错误码的方式。
若是函数名是一句话,表示的是一个断言,那么应该使用布尔值的方式。

例如,add work 是一个动做,那么 add_work() 返回值为0则表示成功,-EBUSY表示失败。PCI device present是一个断言,那么 pci_dev_present() 返回值为1表示成功,0表示失败。

可导出(EXPORT)的函数都应该遵照这个约定,私有(static)函数不须要,不过我建议你仍是遵照。

若是返回值是一些计算结果,那么固然不须要管这些东西。通常来讲,计算结果出错了就表示失败了。典型的例子就是返回一个指针:使用 NULL 或者 ERR_PTR 来表示错误。

17 不要从新发明内核宏

include/linux/kernel.h 头文件里定义了一些你可使用的宏,你应该直接使用他们,而不是从新再定义一些新的宏。例如,若是你须要计算数组长度,使用提供的宏:

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

一样地,若是你须要计算结构体中某个成员的大小,使用:

#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))

若是须要,里面还有作类型检查的 min() 和 max() 宏。仔细看看头文件中还定义了那些东西,若是里面有了,你就不要在本身的代码中从新定义了。

18 画蛇添足的编辑器

有些编辑器能够识别源文件中的配置信息,例如 emacs 能够识别这样的标记:

-*- mode: c -*-

或者这样的:

/*
Local Variables:
compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
End:
*/

Vim 能够识别:

/* vim:set sw=8 noet */

不要在源代码中包含任何相似的内容。每一个人都有本身的编辑器配置,你的源文件不该该影响他们。

19 内联汇编

在写一些与体系结构有关的代码中,你可能须要使用一些内联汇编调用CPU相关的接口或者和平台有关的功能,若是有这种需求,你大可以使用汇编。可是若是C语言能够干的事,不要使用汇编。你应该尽量地使用C语言来控制硬件。

尽量写一些辅助函数来实现相同的功能,而不是重复地写一些相同的代码,同时记住,内联汇编也可使用C函数的参数。

大的、重要的汇编函数应该独自写在一个 .S 文件中,而且编写对应的C头文件和函数原型,相应的函数原型应该添加 asmlinkage 关键字。

你也许须要标记某些汇编代码为 volatile,避免 gcc 误把一些汇编移除掉。通常状况下,你不须要这样干,不必的标记会影响优化。

当一条汇编语句里包含多个指令时,每一个指令分行写,而且除了最后一行外,在其余行的行末添加 \n\t 进行缩进和对齐:

asm ("magic %reg1, #42\n\t"
     "more_magic %reg2, %reg3"
     : /* outputs */ : /* inputs */ : /* clobbers */);

20 条件编译

不管在哪,不要在 .c 文件中使用条件编译命令(#if, #ifdef),这样干会致使代码可读性下降而且代码逻辑混乱。取而代之,应该在 .c 文件对应的头文件中使用这些条件编译,而且在每一个 #else  分支注明对应的版本信息。

把同一个版本的全部函数都写在一个 #ifdef 中,不要在其中写一部分,而又在外部写一部分。

在 #endif 以后写上一个注释,注明这个 #ifdef 块对应的内容:

#ifdef CONFIG_SOMETHING
...
#endif /* CONFIG_SOMETHING */

References

The C Programming Language, Second Edition by Brian W. Kernighan and Dennis M. Ritchie. Prentice Hall, Inc., 1988. ISBN 0-13-110362-8 (paperback), 0-13-110370-9 (hardback). The Practice of Programming by Brian W. Kernighan and Rob Pike. Addison-Wesley, Inc., 1999. ISBN 0-201-61586-X. GNU manuals - where in compliance with K&R and this text - for cpp, gcc, gcc internals and indent, all available from http://www.gnu.org/manual/ WG14 is the international standardization working group for the programming language C, URL: http://www.open-std.org/JTC1/SC22/WG14/ Kernel process/coding-style.rst, by greg@kroah.com at OLS 2002: http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/

相关文章
相关标签/搜索