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

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

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 打印内核信息

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfpsf.html