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