談談Linux內核驅動的編碼風格
最近在向Linux內核提交一些驅動程序,在提交的過程中,發現自己的代碼離Linux內核的coding style要求還是差很多。當初自己對內核文檔里的CodingStyle一文只是粗略的瀏覽,真正寫代碼的時候在很多細節上會照顧不周。不過, 在不遵守規則的程序員隊 伍里,我并不是孤獨的。如果去看drivers/staging下的代碼,就會發現很多驅動程序都沒有嚴格遵守內核的coding style,而且在很多驅動程序的TODO文件里,都會把”checkpatch.pl fixes”作為自己的目標之一(checkpatch.pl是用來檢查代碼是否符合coding style的腳本)。
不可否認,coding style是仁者見仁、智者見智的事情。比如Microsoft所推崇的匈牙利命名法,在Linus看來就是及其腦殘(brain damaged)的做法。也許您并不贊成Linus制定的coding style,但在提交內核驅動這件事上,***還是以大局為重。對于這么一個龐大的集市式的開發來說,隨意書寫代碼必將帶來嚴重的可維護性的災難。
一些輔助工具
當代碼量達到一定程度時,手動去檢查和修改coding style是非常繁瑣的工作,幸好,我們還有一些工具可以使用。
scripts/checkpatch.pl
這是一個檢查代碼是否符合內核編碼規范的的腳本。顧名思義,checkpatch是用來檢查patch的,默認的調用也確實如此。如果用來檢查原文件,需要加上“-f”的選項。
我們來看一段無聊的代碼(文件名為print_msg.c):
- void print_msg(int a)
- {
- switch (a) {
- case 1:
- printf("a == 1\n");
- break;
- case 2:
- printf("a == 2\n");
- break;
- }
- }
這段代碼的coding style是否有問題呢?用checkpatch.pl來檢查一下:
scripts/checkpatch.pl -f print_msg.c
檢查的結果是:
- ERROR: switch and case should be at the same indent
- #3: FILE: switch.c:3:
- + switch (a) {
- + case 1:
- [...]
- + case 2:
- total: 1 errors, 0 warnings, 12 lines checked
- switch.c has style problems, please review. If any of these errors
- are false positives report them to the maintainer, see
- CHECKPATCH in MAINTAINERS.
在Linux內核的coding style里,switch和case要求有相同的縮進。本例的代碼很少,錯誤也只有這一個,手動修改很方便。如果類似的縮緊錯誤很多怎么辦?
scripts/Lindent
scripts目錄下的工具Lindent可以用來自動修改縮進問題。提醒一下,使用Lindent要求系統安裝indent這個工具。
對于上面這個例子,執行Lindent命令:
scripts/Lindent print_msg.c
得到的新代碼是:
- void print_msg(int a)
- {
- switch (a) {
- case 1:
- printf("a == 1\n");
- break;
- case 2:
- printf("a == 2\n");
- break;
- }
- }
sed
sed是一個流編輯器,其強大的功能可以幫助我們處理很多重復性的工作。比如,Linux內核的coding style要求,行尾不能有空格(包括Tab),去除這些空格就可以借助sed。
我自己的習慣很差,經常在代碼的行尾留下一些空格。比如一行代碼過長需要換行時,總是下意識的在換行的地方敲一個空格。另外,我常用的編輯器之一的Kate,為了對齊的需要,經常在空行的前面留上幾個縮進的Tab(如下圖)。
手動去除這些行尾的空格是一件頭大的事情,但對于sed來說不過是舉手之勞。命令格式如下:
- sed ‘s/[ \t]*$//g’ your_code.c
一些需要注意的Coding Style
縮進
1、除了注釋、文檔和Kconfig之外,使用Tab縮進,而不是空格,并且Tab的寬度為8個字符;
2、switch … case …語句中,switch和case具有相同的縮進(參考上文);
花括號
3、花括號的使用參考K&R風格。
如果是函數,左花括號另起一行:
- int function(int x)
- {
- body of function
- }
否則,花括號緊接在語句的***:
- if (x is true) {
- we do y
- }
如果只有一行語句,則不需要用花括號:
- if (condition)
- action();
但是,對于條件語句來說,如果一個分支是一行語句,另一個分支是多行,則需要保持一致,使用花括號:
- if (condition) {
- do_this();
- do_that();
- } else {
- otherwise();
- }
空格
4、在關鍵字“if, switch, case, for, do, while”之后需要加上空格,如:
if (something)
5、在關鍵字“sizeof, typeof, alignof, or __attribute__”之后不要加空格,如:
sizeof(struct file)
6、在括號里的表達式兩邊不要加空格,比如,下面是一個反面的例子:
sizeof( struct file )
7、大多說的二元和三元運算符兩邊需要空格,如“= + – < > * / % | & ^ <= >= == != ? :”;
8、一元運算符后面不要空格,如“& * + – ~ ! sizeof typeof alignof __attribute__ defined”;
9、在前綴自增自減運算符之后和后綴自增自減運算符之前不需要空格(“++”和“–”);
10、結構成員運算符(“.”和“->”)的兩邊不需要空格;
11、行尾不需要空格;
注釋
12、使用C89的“/* … */”風格而不是C99的“// …”風格;
13、對于多行注釋,可以參考下例:
- /*
- * 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.
- */
Kconfig
14、“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.
宏
15、多行的宏定義需要用“do .. while”封裝,如:
- #define macrofun(a, b, c) \
- do { \
- if (a == 5) \
- do_this(b, c); \
- } while (0)
函數返回值
16、函數返回值的定義***也要遵循一定的章法。
如果函數的名稱是一種動作或者命令式的語句,應該以錯誤代碼的形式返回(通常是0表示成功,-Exxx這種形式的負數表示錯誤),如:
do_something()
如果函數的名稱是判斷語句,則返回值應該類似與布爾值(通常1表示成功,0表示錯誤),如:
something_is_present()