吐血整理的萬字Linux內(nèi)核源碼規(guī)范
從編碼風(fēng)格錯(cuò)誤開始
快速修改編碼風(fēng)格的工具
scripts/checkpatch.pl
scripts/Lindent
astyle
Linux 內(nèi)核代碼風(fēng)格
1 縮進(jìn)
2 把長的行和字符串打散
3 大括號(hào)和空格的放置
4 命名
5 Typedef
6 函數(shù)
7 集中的函數(shù)退出途徑
8 注釋
9 你已經(jīng)把事情弄糟了
10 Kconfig 配置文件
11 數(shù)據(jù)結(jié)構(gòu)
12 宏,枚舉和RTL
13 打印內(nèi)核消息
14 分配內(nèi)存
15 內(nèi)聯(lián)弊病
16 函數(shù)返回值及命名
17 不要重新發(fā)明內(nèi)核宏
18 編輯器模式行和其他需要羅嗦的事情
19 內(nèi)聯(lián)匯編
20 條件編譯
從編碼風(fēng)格錯(cuò)誤開始
曾經(jīng)在開發(fā)Linux內(nèi)核驅(qū)動(dòng)的時(shí)候,創(chuàng)建了一個(gè)補(bǔ)丁文件,但是在把補(bǔ)丁打到主分支的時(shí)候提示很多編碼風(fēng)格的錯(cuò)誤問題,后來重做了補(bǔ)丁才解決了問題,這也是沒有嚴(yán)格按照的Linux編碼風(fēng)格從而導(dǎo)致的問題。因?yàn)楫?dāng)時(shí)代碼量不大,所以解決問題的時(shí)間相對(duì)較少。在代碼量增大的情況下可以借助工具進(jìn)行自動(dòng)修改。
快速修改編碼風(fēng)格的工具
scripts/checkpatch.pl
這是一個(gè)檢查patch是否符合內(nèi)核編碼規(guī)范的腳本。默認(rèn)的調(diào)用也確實(shí)如此。如果用來檢查原文件,需要加上-f
的選項(xiàng)。
scripts/Lindent
源碼路徑下的scripts
目錄中的工具Lindent
可以用來自動(dòng)修改縮進(jìn)問題。不過使用Lindent要求系統(tǒng)安裝indent
這個(gè)工具。Ubuntu系統(tǒng)下可以使用apt-get install indent
進(jìn)行「安裝」。
astyle
比較推薦使用這個(gè)工具,因?yàn)楸容^相當(dāng)方便,可以一鍵式轉(zhuǎn)換成linux
,java
,gnu
等等風(fēng)格。
下載地址項(xiàng)目地址文檔說明
如何使用,可以參考具體文檔說明,寫的比較詳細(xì)。
總而言之,應(yīng)該顧全大局,在進(jìn)行內(nèi)核開發(fā)和驅(qū)動(dòng)開發(fā)的時(shí)候,嚴(yán)格遵守Linux的編碼規(guī)范,避免由于編碼不規(guī)范帶來的種種問題,可以參考內(nèi)核路徑下Documentation/CodingStyle
文檔,以下轉(zhuǎn)自Linux內(nèi)核文檔,最權(quán)威的文檔路徑,很全很強(qiáng)大,原來在這個(gè)網(wǎng)站上已經(jīng)有中文版了,感謝萬分,具體的更新可以跳轉(zhuǎn)到你懂的網(wǎng)址。
「來自 Documentation/process/coding-style.rst 的中文翻譯」
Linux 內(nèi)核代碼風(fēng)格
這是一個(gè)簡(jiǎn)短的文檔,描述了 linux 內(nèi)核的首選代碼風(fēng)格。代碼風(fēng)格是因人而異的, 而且我不愿意把自己的觀點(diǎn)強(qiáng)加給任何人,但這就像我去做任何事情都必須遵循的原則 那樣,我也希望在絕大多數(shù)事上保持這種的態(tài)度。請(qǐng) (在寫代碼時(shí)) 至少考慮一下這里 的代碼風(fēng)格。
首先,我建議你打印一份 GNU 代碼規(guī)范,然后不要讀。燒了它,這是一個(gè)具有重大象征性意義的動(dòng)作。
不管怎樣,現(xiàn)在我們開始:
1 縮進(jìn)
制表符是 8 個(gè)字符,所以縮進(jìn)也是 8 個(gè)字符。有些異端運(yùn)動(dòng)試圖將縮進(jìn)變?yōu)?4 (甚至 2!) 字符深,這幾乎相當(dāng)于嘗試將圓周率的值定義為 3。
理由:縮進(jìn)的全部意義就在于清楚的定義一個(gè)控制塊起止于何處。尤其是當(dāng)你盯著你的 屏幕連續(xù)看了 20 小時(shí)之后,你將會(huì)發(fā)現(xiàn)大一點(diǎn)的縮進(jìn)會(huì)使你更容易分辨縮進(jìn)。
現(xiàn)在,有些人會(huì)抱怨 8 個(gè)字符的縮進(jìn)會(huì)使代碼向右邊移動(dòng)的太遠(yuǎn),在 80 個(gè)字符的終端 屏幕上就很難讀這樣的代碼。這個(gè)問題的答案是,如果你需要 3 級(jí)以上的縮進(jìn),不管用 何種方式你的代碼已經(jīng)有問題了,應(yīng)該修正你的程序。
簡(jiǎn)而言之,8 個(gè)字符的縮進(jìn)可以讓代碼更容易閱讀,還有一個(gè)好處是當(dāng)你的函數(shù)嵌套太 深的時(shí)候可以給你警告。留心這個(gè)警告。
在 switch 語句中消除多級(jí)縮進(jìn)的首選的方式是讓 switch
和從屬于它的 case
標(biāo)簽對(duì)齊于同一列,而不要 兩次縮進(jìn)
case
標(biāo)簽。比如:
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;
}
不要把多個(gè)語句放在一行里,除非你有什么東西要隱藏:
if?(condition)?do_this;
??do_something_everytime;
也不要在一行里放多個(gè)賦值語句。內(nèi)核代碼風(fēng)格超級(jí)簡(jiǎn)單。就是避免可能導(dǎo)致別人誤讀 的表達(dá)式。
除了注釋、文檔和 Kconfig 之外,不要使用空格來縮進(jìn),前面的例子是例外,是有意為 之。
選用一個(gè)好的編輯器,不要在行尾留空格。
2 把長的行和字符串打散
代碼風(fēng)格的意義就在于使用平常使用的工具來維持代碼的可讀性和可維護(hù)性。
每一行的長度的限制是 80 列,我們強(qiáng)烈建議您遵守這個(gè)慣例。
長于 80 列的語句要打散成有意義的片段。除非超過 80 列能顯著增加可讀性,并且不 會(huì)隱藏信息。子片段要明顯短于母片段,并明顯靠右。這同樣適用于有著很長參數(shù)列表 的函數(shù)頭。然而,絕對(duì)不要打散對(duì)用戶可見的字符串,例如 printk 信息,因?yàn)檫@樣就 很難對(duì)它們 grep。
3 大括號(hào)和空格的放置
C 語言風(fēng)格中另外一個(gè)常見問題是大括號(hào)的放置。和縮進(jìn)大小不同,選擇或棄用某種放 置策略并沒有多少技術(shù)上的原因,不過首選的方式,就像 Kernighan 和 Ritchie 展示 給我們的,是把起始大括號(hào)放在行尾,而把結(jié)束大括號(hào)放在行首,所以:
if?(x?is?true)?{
????????we?do?y
}
這適用于所有的非函數(shù)語句塊 (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;
}
不過,有一個(gè)例外,那就是函數(shù):函數(shù)的起始大括號(hào)放置于下一行的開頭,所以:
int?function(int?x)
{
????????body?of?function
}
全世界的異端可能會(huì)抱怨這個(gè)不一致性是... 呃... 不一致的,不過所有思維健全的人 都知道 (a) K&R 是 「正確的」 并且 (b) K&R 是正確的。此外,不管怎樣函數(shù)都是特 殊的 (C 函數(shù)是不能嵌套的)。
注意結(jié)束大括號(hào)獨(dú)自占據(jù)一行,除非它后面跟著同一個(gè)語句的剩余部分,也就是 do 語 句中的 “while” 或者 if 語句中的 “else”,像這樣:
do?{
????????body?of?do-loop
}?while?(condition);
和
if?(x?==?y)?{
????????..
}?else?if?(x?>?y)?{
????????...
}?else?{
????????....
}
理由:K&R。
也請(qǐng)注意這種大括號(hào)的放置方式也能使空 (或者差不多空的) 行的數(shù)量最小化,同時(shí)不 失可讀性。因此,由于你的屏幕上的新行是不可再生資源 (想想 25 行的終端屏幕),你 將會(huì)有更多的空行來放置注釋。
當(dāng)只有一個(gè)單獨(dú)的語句的時(shí)候,不用加不必要的大括號(hào)。
if?(condition)
????????action();
和
if?(condition)
????????do_this();
else
????????do_that();
這并不適用于只有一個(gè)條件分支是單語句的情況;這時(shí)所有分支都要使用大括號(hào):
if?(condition)?{
????????do_this();
????????do_that();
}?else?{
????????otherwise();
}
3.1 空格
Linux 內(nèi)核的空格使用方式 (主要) 取決于它是用于函數(shù)還是關(guān)鍵字。(大多數(shù)) 關(guān)鍵字 后要加一個(gè)空格。值得注意的例外是 sizeof, typeof, alignof 和 「attribute」,這 些關(guān)鍵字某些程度上看起來更像函數(shù) (它們?cè)?Linux 里也常常伴隨小括號(hào)而使用,盡管 在 C 里這樣的小括號(hào)不是必需的,就像 struct fileinfo info;
聲明過后的 sizeof info
)。
所以在這些關(guān)鍵字之后放一個(gè)空格:
if,?switch,?case,?for,?do,?while
但是不要在 sizeof, typeof, alignof 或者 「attribute」 這些關(guān)鍵字之后放空格。例如,
s?=?sizeof(struct?file);
不要在小括號(hào)里的表達(dá)式兩側(cè)加空格。這是一個(gè) 「反例」 :
s?=?sizeof(?struct?file?);
當(dāng)聲明指針類型或者返回指針類型的函數(shù)時(shí), *
的首選使用方式是使之靠近變量名 或者函數(shù)名,而不是靠近類型名。例子:
char?*linux_banner;
unsigned?long?long?memparse(char?*ptr,?char?**retptr);
char?*match_strdup(substring_t?*s);
在大多數(shù)二元和三元操作符兩側(cè)使用一個(gè)空格,例如下面所有這些操作符:
=??+??-???>??*??/??%??|??&??^??<=??>=??==??!=?????:
但是一元操作符后不要加空格:
&??*??+??-??~??!??sizeof??typeof??alignof??__attribute__??defined
后綴自加和自減一元操作符前不加空格:
++??--
前綴自加和自減一元操作符后不加空格:
++??--
.
和 ->
結(jié)構(gòu)體成員操作符前后不加空格。
不要在行尾留空白。有些可以自動(dòng)縮進(jìn)的編輯器會(huì)在新行的行首加入適量的空白,然后 你就可以直接在那一行輸入代碼。不過假如你最后沒有在那一行輸入代碼,有些編輯器 就不會(huì)移除已經(jīng)加入的空白,就像你故意留下一個(gè)只有空白的行。包含行尾空白的行就 這樣產(chǎn)生了。
當(dāng) git 發(fā)現(xiàn)補(bǔ)丁包含了行尾空白的時(shí)候會(huì)警告你,并且可以應(yīng)你的要求去掉行尾空白;不過如果你是正在打一系列補(bǔ)丁,這樣做會(huì)導(dǎo)致后面的補(bǔ)丁失敗,因?yàn)槟愀淖兞搜a(bǔ)丁的 上下文。
4 命名
C 是一個(gè)簡(jiǎn)樸的語言,你的命名也應(yīng)該這樣。和 Modula-2 和 Pascal 程序員不同, C 程序員不使用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字。C 程序員會(huì) 稱那個(gè)變量為 tmp
,這樣寫起來會(huì)更容易,而且至少不會(huì)令其難于理解。
不過,雖然混用大小寫的名字是不提倡使用的,但是全局變量還是需要一個(gè)具描述性的 名字。稱一個(gè)全局函數(shù)為 foo
是一個(gè)難以饒恕的錯(cuò)誤。
全局變量 (只有當(dāng)你 「真正」 需要它們的時(shí)候再用它) 需要有一個(gè)具描述性的名字,就 像全局函數(shù)。如果你有一個(gè)可以計(jì)算活動(dòng)用戶數(shù)量的函數(shù),你應(yīng)該叫它count_active_users()
或者類似的名字,你不應(yīng)該叫它 cntuser()
。
在函數(shù)名中包含函數(shù)類型 (所謂的匈牙利命名法) 是腦子出了問題——編譯器知道那些類 型而且能夠檢查那些類型,這樣做只能把程序員弄糊涂了。難怪微軟總是制造出有問題 的程序。
本地變量名應(yīng)該簡(jiǎn)短,而且能夠表達(dá)相關(guān)的含義。如果你有一些隨機(jī)的整數(shù)型的循環(huán)計(jì) 數(shù)器,它應(yīng)該被稱為 i
。叫它 loop_counter
并無益處,如果它沒有被誤解的 可能的話。類似的, tmp
可以用來稱呼任意類型的臨時(shí)變量。
如果你怕混淆了你的本地變量名,你就遇到另一個(gè)問題了,叫做函數(shù)增長荷爾蒙失衡綜 合癥。請(qǐng)看第六章 (函數(shù))。
5 Typedef
不要使用類似 vps_t
之類的東西。
對(duì)結(jié)構(gòu)體和指針使用 typedef 是一個(gè) 「錯(cuò)誤」 。當(dāng)你在代碼里看到:
vps_t?a;
這代表什么意思呢?
相反,如果是這樣
struct?virtual_container?*a;
你就知道 a
是什么了。
很多人認(rèn)為 typedef 能提高可讀性
。實(shí)際不是這樣的。它們只在下列情況下有用:
-
完全不透明的對(duì)象 (這種情況下要主動(dòng)使用 typedef 來 「隱藏」 這個(gè)對(duì)象實(shí)際上 是什么)。
例如:
pte_t
等不透明對(duì)象,你只能用合適的訪問函數(shù)來訪問它們。不透明性和 “訪問函數(shù)” 本身是不好的。我們使用 pte_t 等類型的原因在于真 的是完全沒有任何共用的可訪問信息。
-
清楚的整數(shù)類型,如此,這層抽象就可以 「幫助」 消除到底是
int
還是long
的混淆。u8/u16/u32 是完全沒有問題的 typedef,不過它們更符合類別 (d) 而不是這里。
要這樣做,必須事出有因。如果某個(gè)變量是
unsigned long
,那么沒有必要typedef unsigned long myflags_t
;不過如果有一個(gè)明確的原因,比如它在某種情況下可能會(huì)是一個(gè)
unsigned int
而在其他情況下可能為unsigned long
,那么就不要猶豫,請(qǐng)務(wù)必使用 typedef。 -
當(dāng)你使用 sparse 按字面的創(chuàng)建一個(gè) 「新」 類型來做類型檢查的時(shí)候。
-
和標(biāo)準(zhǔn) C99 類型相同的類型,在某些例外的情況下。
雖然讓眼睛和腦筋來適應(yīng)新的標(biāo)準(zhǔn)類型比如
uint32_t
不需要花很多時(shí)間,可 是有些人仍然拒絕使用它們。因此,Linux 特有的等同于標(biāo)準(zhǔn)類型的
u8/u16/u32/u64
類型和它們的有符號(hào) 類型是被允許的——盡管在你自己的新代碼中,它們不是強(qiáng)制要求要使用的。當(dāng)編輯已經(jīng)使用了某個(gè)類型集的已有代碼時(shí),你應(yīng)該遵循那些代碼中已經(jīng)做出的選 擇。
-
可以在用戶空間安全使用的類型。
在某些用戶空間可見的結(jié)構(gòu)體里,我們不能要求 C99 類型而且不能用上面提到的
u32
類型。因此,我們?cè)谂c用戶空間共享的所有結(jié)構(gòu)體中使用 __u32 和類似 的類型。
可能還有其他的情況,不過基本的規(guī)則是 「永遠(yuǎn)不要」 使用 typedef,除非你可以明 確的應(yīng)用上述某個(gè)規(guī)則中的一個(gè)。
總的來說,如果一個(gè)指針或者一個(gè)結(jié)構(gòu)體里的元素可以合理的被直接訪問到,那么它們 就不應(yīng)該是一個(gè) typedef。
6 函數(shù)
函數(shù)應(yīng)該簡(jiǎn)短而漂亮,并且只完成一件事情。函數(shù)應(yīng)該可以一屏或者兩屏顯示完 (我們 都知道 ISO/ANSI 屏幕大小是 80x24),只做一件事情,而且把它做好。
一個(gè)函數(shù)的最大長度是和該函數(shù)的復(fù)雜度和縮進(jìn)級(jí)數(shù)成反比的。所以,如果你有一個(gè)理 論上很簡(jiǎn)單的只有一個(gè)很長 (但是簡(jiǎn)單) 的 case 語句的函數(shù),而且你需要在每個(gè) case 里做很多很小的事情,這樣的函數(shù)盡管很長,但也是可以的。
不過,如果你有一個(gè)復(fù)雜的函數(shù),而且你懷疑一個(gè)天分不是很高的高中一年級(jí)學(xué)生可能 甚至搞不清楚這個(gè)函數(shù)的目的,你應(yīng)該嚴(yán)格遵守前面提到的長度限制。使用輔助函數(shù), 并為之取個(gè)具描述性的名字 (如果你覺得它們的性能很重要的話,可以讓編譯器內(nèi)聯(lián)它 們,這樣的效果往往會(huì)比你寫一個(gè)復(fù)雜函數(shù)的效果要好。)
函數(shù)的另外一個(gè)衡量標(biāo)準(zhǔn)是本地變量的數(shù)量。此數(shù)量不應(yīng)超過 5-10 個(gè),否則你的函數(shù) 就有問題了。重新考慮一下你的函數(shù),把它分拆成更小的函數(shù)。人的大腦一般可以輕松 的同時(shí)跟蹤 7 個(gè)不同的事物,如果再增多的話,就會(huì)糊涂了。即便你聰穎過人,你也可 能會(huì)記不清你 2 個(gè)星期前做過的事情。
在源文件里,使用空行隔開不同的函數(shù)。如果該函數(shù)需要被導(dǎo)出,它的 「EXPORT」 宏 應(yīng)該緊貼在它的結(jié)束大括號(hào)之下。比如:
int?system_is_up(void)
{
????????return?system_state?==?SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);
在函數(shù)原型中,包含函數(shù)名和它們的數(shù)據(jù)類型。雖然 C 語言里沒有這樣的要求,在 Linux 里這是提倡的做法,因?yàn)檫@樣可以很簡(jiǎn)單的給讀者提供更多的有價(jià)值的信息。
7 集中的函數(shù)退出途徑
雖然被某些人聲稱已經(jīng)過時(shí),但是 goto 語句的等價(jià)物還是經(jīng)常被編譯器所使用,具體 形式是無條件跳轉(zhuǎn)指令。
當(dāng)一個(gè)函數(shù)從多個(gè)位置退出,并且需要做一些類似清理的常見操作時(shí),goto 語句就很方 便了。如果并不需要清理操作,那么直接 return 即可。
選擇一個(gè)能夠說明 goto 行為或它為何存在的標(biāo)簽名。如果 goto 要釋放 buffer
, 一個(gè)不錯(cuò)的名字可以是 out_free_buffer:
。別去使用像 err1:
和 err2:
這樣的GW_BASIC 名稱,因?yàn)橐坏┠闾砑踊騽h除了 (函數(shù)的) 退出路徑,你就必須對(duì)它們 重新編號(hào),這樣會(huì)難以去檢驗(yàn)正確性。
使用 goto 的理由是:
-
無條件語句容易理解和跟蹤 -
嵌套程度減小 -
可以避免由于修改時(shí)忘記更新個(gè)別的退出點(diǎn)而導(dǎo)致錯(cuò)誤 -
讓編譯器省去刪除冗余代碼的工作 ;)
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;
}
一個(gè)需要注意的常見錯(cuò)誤是 一個(gè) err 錯(cuò)誤
,就像這樣:
err:
????????kfree(foo->bar);
????????kfree(foo);
????????return?ret;
這段代碼的錯(cuò)誤是,在某些退出路徑上 foo
是 NULL。通常情況下,通過把它分離 成兩個(gè)錯(cuò)誤標(biāo)簽 err_free_bar:
和 err_free_foo:
來修復(fù)這個(gè)錯(cuò)誤:
err_free_bar:
???????kfree(foo->bar);
err_free_foo:
???????kfree(foo);
???????return?ret;
理想情況下,你應(yīng)該模擬錯(cuò)誤來測(cè)試所有退出路徑。
8 注釋
注釋是好的,不過有過度注釋的危險(xiǎn)。永遠(yuǎn)不要在注釋里解釋你的代碼是如何運(yùn)作的:更好的做法是讓別人一看你的代碼就可以明白,解釋寫的很差的代碼是浪費(fèi)時(shí)間。
一般的,你想要你的注釋告訴別人你的代碼做了什么,而不是怎么做的。也請(qǐng)你不要把 注釋放在一個(gè)函數(shù)體內(nèi)部:如果函數(shù)復(fù)雜到你需要獨(dú)立的注釋其中的一部分,你很可能 需要回到第六章看一看。你可以做一些小注釋來注明或警告某些很聰明 (或者槽糕) 的 做法,但不要加太多。你應(yīng)該做的,是把注釋放在函數(shù)的頭部,告訴人們它做了什么, 也可以加上它做這些事情的原因。
當(dāng)注釋內(nèi)核 API 函數(shù)時(shí),請(qǐng)使用 kernel-doc 格式。請(qǐng)看 Documentation/doc-guide/ 和 scripts/kernel-doc 以獲得詳細(xì)信息。
長 (多行) 注釋的首選風(fēng)格是:
/*
?*?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.
?*/
對(duì)于在 net/ 和 drivers/net/ 的文件,首選的長 (多行) 注釋風(fēng)格有些不同。
/*?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.
?*/
注釋數(shù)據(jù)也是很重要的,不管是基本類型還是衍生類型。為了方便實(shí)現(xiàn)這一點(diǎn),每一行 應(yīng)只聲明一個(gè)數(shù)據(jù) (不要使用逗號(hào)來一次聲明多個(gè)數(shù)據(jù))。這樣你就有空間來為每個(gè)數(shù)據(jù) 寫一段小注釋來解釋它們的用途了。
9 你已經(jīng)把事情弄糟了
這沒什么,我們都是這樣。可能你的使用了很長時(shí)間 Unix 的朋友已經(jīng)告訴你 GNU emacs
能自動(dòng)幫你格式化 C 源代碼,而且你也注意到了,確實(shí)是這樣,不過它 所使用的默認(rèn)值和我們想要的相去甚遠(yuǎn) (實(shí)際上,甚至比隨機(jī)打的還要差——無數(shù)個(gè)猴子 在 GNU emacs 里打字永遠(yuǎn)不會(huì)創(chuàng)造出一個(gè)好程序) (譯注:Infinite Monkey Theorem)
所以你要么放棄 GNU emacs,要么改變它讓它使用更合理的設(shè)定。要采用后一個(gè)方案, 你可以把下面這段粘貼到你的 .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")))))
這會(huì)讓 emacs 在 ~/src/linux-trees
下的 C 源文件獲得更好的內(nèi)核代碼風(fēng)格。
不過就算你嘗試讓 emacs 正確的格式化代碼失敗了,也并不意味著你失去了一切:還可 以用 indent
。
不過,GNU indent 也有和 GNU emacs 一樣有問題的設(shè)定,所以你需要給它一些命令選 項(xiàng)。不過,這還不算太糟糕,因?yàn)榫退闶?GNU indent 的作者也認(rèn)同 K&R 的權(quán)威性 (GNU 的人并不是壞人,他們只是在這個(gè)問題上被嚴(yán)重的誤導(dǎo)了),所以你只要給 indent 指定選項(xiàng) -kr -i8
(代表 K&R,8 字符縮進(jìn)
),或使用 scripts/Lindent
這樣就可以以最時(shí)髦的方式縮進(jìn)源代碼。
indent
有很多選項(xiàng),特別是重新格式化注釋的時(shí)候,你可能需要看一下它的手冊(cè)。不過記?。?code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">indent 不能修正壞的編程習(xí)慣。
10 Kconfig 配置文件
對(duì)于遍布源碼樹的所有 Kconfig* 配置文件來說,它們縮進(jìn)方式有所不同。緊挨著 config
定義的行,用一個(gè)制表符縮進(jìn),然而 help 信息的縮進(jìn)則額外增加 2 個(gè)空 格。舉個(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.
而那些危險(xiǎn)的功能 (比如某些文件系統(tǒng)的寫支持) 應(yīng)該在它們的提示字符串里顯著的聲 明這一點(diǎn):
config?ADFS_FS_RW
??????bool?"ADFS?write?support?(DANGEROUS)"
??????depends?on?ADFS_FS
??????...
要查看配置文件的完整文檔,請(qǐng)看 Documentation/kbuild/kconfig-language.txt。
11 數(shù)據(jù)結(jié)構(gòu)
如果一個(gè)數(shù)據(jù)結(jié)構(gòu),在創(chuàng)建和銷毀它的單線執(zhí)行環(huán)境之外可見,那么它必須要有一個(gè)引 用計(jì)數(shù)器。內(nèi)核里沒有垃圾收集 (并且內(nèi)核之外的垃圾收集慢且效率低下),這意味著你 絕對(duì)需要記錄你對(duì)這種數(shù)據(jù)結(jié)構(gòu)的使用情況。
引用計(jì)數(shù)意味著你能夠避免上鎖,并且允許多個(gè)用戶并行訪問這個(gè)數(shù)據(jù)結(jié)構(gòu)——而不需要 擔(dān)心這個(gè)數(shù)據(jù)結(jié)構(gòu)僅僅因?yàn)闀簳r(shí)不被使用就消失了,那些用戶可能不過是沉睡了一陣或 者做了一些其他事情而已。
注意上鎖 「不能」 取代引用計(jì)數(shù)。上鎖是為了保持?jǐn)?shù)據(jù)結(jié)構(gòu)的一致性,而引用計(jì)數(shù)是一 個(gè)內(nèi)存管理技巧。通常二者都需要,不要把兩個(gè)搞混了。
很多數(shù)據(jù)結(jié)構(gòu)實(shí)際上有 2 級(jí)引用計(jì)數(shù),它們通常有不同 類
的用戶。子類計(jì)數(shù)器統(tǒng) 計(jì)子類用戶的數(shù)量,每當(dāng)子類計(jì)數(shù)器減至零時(shí),全局計(jì)數(shù)器減一。
這種 多級(jí)引用計(jì)數(shù)
的例子可以在內(nèi)存管理 (struct mm_struct
: mm_users 和 mm_count),和文件系統(tǒng) (struct super_block
: s_count 和 s_active) 中找到。
記?。喝绻硪粋€(gè)執(zhí)行線索可以找到你的數(shù)據(jù)結(jié)構(gòu),但這個(gè)數(shù)據(jù)結(jié)構(gòu)沒有引用計(jì)數(shù)器, 這里幾乎肯定是一個(gè) bug。
12 宏,枚舉和RTL
用于定義常量的宏的名字及枚舉里的標(biāo)簽需要大寫。
#define?CONSTANT?0x12345
在定義幾個(gè)相關(guān)的常量時(shí),最好用枚舉。
宏的名字請(qǐng)用大寫字母,不過形如函數(shù)的宏的名字可以用小寫字母。
一般的,如果能寫成內(nèi)聯(lián)函數(shù)就不要寫成像函數(shù)的宏。
含有多個(gè)語句的宏應(yīng)該被包含在一個(gè) do-while 代碼塊里:
#define?macrofun(a,?b,?c)???????????????????????\
????????do?{????????????????????????????????????\
????????????????if?(a?==?5)?????????????????????\
????????????????????????do_this(b,?c);??????????\
????????}?while?(0)
使用宏的時(shí)候應(yīng)避免的事情:
-
影響控制流程的宏:
#define?FOO(x)??????????????????????????????????\
????????do?{????????????????????????????????????\
????????????????if?(blah(x)?0)????????????????\
????????????????????????return?-EBUGGERED;??????\
????????}?while?(0)
「非?!?/strong> 不好。它看起來像一個(gè)函數(shù),不過卻能導(dǎo)致 調(diào)用
它的函數(shù)退出;不要打 亂讀者大腦里的語法分析器。
-
依賴于一個(gè)固定名字的本地變量的宏:
#define?FOO(val)?bar(index,?val)
可能看起來像是個(gè)不錯(cuò)的東西,不過它非常容易把讀代碼的人搞糊涂,而且容易導(dǎo)致看起 來不相關(guān)的改動(dòng)帶來錯(cuò)誤。
-
作為左值的帶參數(shù)的宏:FOO(x) = y;如果有人把 FOO 變成一個(gè)內(nèi)聯(lián)函數(shù)的話,這 種用法就會(huì)出錯(cuò)了。 -
忘記了優(yōu)先級(jí):使用表達(dá)式定義常量的宏必須將表達(dá)式置于一對(duì)小括號(hào)之內(nèi)。帶參數(shù) 的宏也要注意此類問題。
#define?CONSTANT?0x4000
#define?CONSTEXP?(CONSTANT?|?3)
-
在宏里定義類似函數(shù)的本地變量時(shí)命名沖突:
#define?FOO(x)??????????????????????????\
({??????????????????????????????????????\
????????typeof(x)?ret;??????????????????\
????????ret?=?calc_ret(x);??????????????\
????????(ret);??????????????????????????\
})
ret 是本地變量的通用名字 - __foo_ret 更不容易與一個(gè)已存在的變量沖突。
cpp 手冊(cè)對(duì)宏的講解很詳細(xì)。gcc internals 手冊(cè)也詳細(xì)講解了 RTL,內(nèi)核里的匯編語 言經(jīng)常用到它。
13 打印內(nèi)核消息
內(nèi)核開發(fā)者應(yīng)該是受過良好教育的。請(qǐng)一定注意內(nèi)核信息的拼寫,以給人以好的印象。不要用不規(guī)范的單詞比如 dont
,而要用 do not
或者 don't
。保證這些信 息簡(jiǎn)單明了,無歧義。
內(nèi)核信息不必以英文句號(hào)結(jié)束。
在小括號(hào)里打印數(shù)字 (%d) 沒有任何價(jià)值,應(yīng)該避免這樣做。
里有一些驅(qū)動(dòng)模型診斷宏,你應(yīng)該使用它們,以確保信息對(duì)應(yīng)于正確 的設(shè)備和驅(qū)動(dòng),并且被標(biāo)記了正確的消息級(jí)別。這些宏有:dev_err()
, dev_warn()
, dev_info()
等等。對(duì)于那些不和某個(gè)特定設(shè)備相關(guān)連的信息,
定義了 pr_notice()
, pr_info()
, pr_warn()
, pr_err()
和其他。
寫出好的調(diào)試信息可以是一個(gè)很大的挑戰(zhàn);一旦你寫出后,這些信息在遠(yuǎn)程除錯(cuò)時(shí)能提 供極大的幫助。然而打印調(diào)試信息的處理方式同打印非調(diào)試信息不同。其他 pr_XXX() 函數(shù)能無條件地打印,pr_debug() 卻不;默認(rèn)情況下它不會(huì)被編譯,除非定義了 DEBUG 或設(shè)定了 CONFIG_DYNAMIC_DEBUG。實(shí)際這同樣是為了 dev_dbg(),一個(gè)相關(guān)約定是在一 個(gè)已經(jīng)開啟了 DEBUG 時(shí),使用 VERBOSE_DEBUG 來添加 dev_vdbg()。
許多子系統(tǒng)擁有 Kconfig 調(diào)試選項(xiàng)來開啟 -DDEBUG 在對(duì)應(yīng)的 Makefile 里面;在其他 情況下,特殊文件使用 #define DEBUG。當(dāng)一條調(diào)試信息需要被無條件打印時(shí),例如, 如果已經(jīng)包含一個(gè)調(diào)試相關(guān)的 #ifdef 條件,printk(KERN_DEBUG ...) 就可被使用。
14 分配內(nèi)存
內(nèi)核提供了下面的一般用途的內(nèi)存分配函數(shù):kmalloc()
, kzalloc()
, kmalloc_array()
, kcalloc()
, vmalloc()
和 vzalloc()
。請(qǐng)參考 API 文檔以獲取有關(guān)它們的詳細(xì)信息。
傳遞結(jié)構(gòu)體大小的首選形式是這樣的:
p?=?kmalloc(sizeof(*p),?...);
另外一種傳遞方式中,sizeof 的操作數(shù)是結(jié)構(gòu)體的名字,這樣會(huì)降低可讀性,并且可能 會(huì)引入 bug。有可能指針變量類型被改變時(shí),而對(duì)應(yīng)的傳遞給內(nèi)存分配函數(shù)的 sizeof 的結(jié)果不變。
強(qiáng)制轉(zhuǎn)換一個(gè) void 指針返回值是多余的。C 語言本身保證了從 void 指針到其他任何 指針類型的轉(zhuǎn)換是沒有問題的。
分配一個(gè)數(shù)組的首選形式是這樣的:
p?=?kmalloc_array(n,?sizeof(...),?...);
分配一個(gè)零長數(shù)組的首選形式是這樣的:
p?=?kcalloc(n,?sizeof(...),?...);
兩種形式檢查分配大小 n * sizeof(...) 的溢出,如果溢出返回 NULL。
15 內(nèi)聯(lián)弊病
有一個(gè)常見的誤解是 內(nèi)聯(lián)
是 gcc 提供的可以讓代碼運(yùn)行更快的一個(gè)選項(xiàng)。雖然使用內(nèi)聯(lián)函數(shù)有時(shí)候是恰當(dāng)?shù)?(比如作為一種替代宏的方式,請(qǐng)看第十二章),不過很多情 況下不是這樣。「inline 的過度使用會(huì)使內(nèi)核變大」,從而使整個(gè)系統(tǒng)運(yùn)行速度變慢。因?yàn)轶w積大內(nèi)核會(huì)占用更多的指令高速緩存,而且會(huì)導(dǎo)致 pagecache 的可用內(nèi)存減少。想象一下,一次 pagecache 未命中就會(huì)導(dǎo)致一次磁盤尋址,將耗時(shí) 5 毫秒。
「5 毫秒的 時(shí)間內(nèi) CPU 能執(zhí)行很多很多指令?!?/strong>
一個(gè)基本的原則是如果一個(gè)函數(shù)有 3 行以上,就不要把它變成內(nèi)聯(lián)函數(shù)。這個(gè)原則的一 個(gè)例外是,如果你知道某個(gè)參數(shù)是一個(gè)編譯時(shí)常量,而且因?yàn)檫@個(gè)常量你確定編譯器在 編譯時(shí)能優(yōu)化掉你的函數(shù)的大部分代碼,那仍然可以給它加上 inline 關(guān)鍵字。kmalloc() 內(nèi)聯(lián)函數(shù)就是一個(gè)很好的例子。
人們經(jīng)常主張給 static 的而且只用了一次的函數(shù)加上 inline,如此不會(huì)有任何損失, 因?yàn)闆]有什么好權(quán)衡的。雖然從技術(shù)上說這是正確的,但是實(shí)際上這種情況下即使不加 inline gcc 也可以自動(dòng)使其內(nèi)聯(lián)。而且其他用戶可能會(huì)要求移除 inline,由此而來的 爭(zhēng)論會(huì)抵消 inline 自身的潛在價(jià)值,得不償失。
16 函數(shù)返回值及命名
函數(shù)可以返回多種不同類型的值,最常見的一種是表明函數(shù)執(zhí)行成功或者失敗的值。這樣 的一個(gè)值可以表示為一個(gè)錯(cuò)誤代碼整數(shù) (-Exxx=失敗,0=成功) 或者一個(gè) 成功
布爾值 (0=失敗,非0=成功)。
混合使用這兩種表達(dá)方式是難于發(fā)現(xiàn)的 bug 的來源。如果 C 語言本身嚴(yán)格區(qū)分整形和 布爾型變量,那么編譯器就能夠幫我們發(fā)現(xiàn)這些錯(cuò)誤... 不過 C 語言不區(qū)分。為了避免 產(chǎn)生這種 bug,請(qǐng)遵循下面的慣例:
如果函數(shù)的名字是一個(gè)動(dòng)作或者強(qiáng)制性的命令,那么這個(gè)函數(shù)應(yīng)該返回錯(cuò)誤代
碼整數(shù)。如果是一個(gè)判斷,那么函數(shù)應(yīng)該返回一個(gè)?"成功"?布爾值。
比如, add work
是一個(gè)命令,所以 add_work() 在成功時(shí)返回 0,在失敗時(shí)返回 -EBUSY。類似的,因?yàn)?PCI device present
是一個(gè)判斷,所以 pci_dev_present() 在成功找到一個(gè)匹配的設(shè)備時(shí)應(yīng)該返回 1,如果找不到時(shí)應(yīng)該返回 0。
所有 EXPORTed 函數(shù)都必須遵守這個(gè)慣例,所有的公共函數(shù)也都應(yīng)該如此。私有 (static) 函數(shù)不需要如此,但是我們也推薦這樣做。
返回值是實(shí)際計(jì)算結(jié)果而不是計(jì)算是否成功的標(biāo)志的函數(shù)不受此慣例的限制。一般的, 他們通過返回一些正常值范圍之外的結(jié)果來表示出錯(cuò)。典型的例子是返回指針的函數(shù), 他們使用 NULL 或者 ERR_PTR 機(jī)制來報(bào)告錯(cuò)誤。
17 不要重新發(fā)明內(nèi)核宏
頭文件 include/linux/kernel.h
包含了一些宏,你應(yīng)該使用它們,而不要自己寫一些 它們的變種。比如,如果你需要計(jì)算一個(gè)數(shù)組的長度,使用這個(gè)宏
#define?ARRAY_SIZE(x)?(sizeof(x)?/?sizeof((x)[0]))
類似的,如果你要計(jì)算某結(jié)構(gòu)體成員的大小,使用
#define?FIELD_SIZEOF(t,?f)?(sizeof(((t*)0)->f))
還有可以做嚴(yán)格的類型檢查的 min() 和 max() 宏,如果你需要可以使用它們。你可以 自己看看那個(gè)頭文件里還定義了什么你可以拿來用的東西,如果有定義的話,你就不應(yīng) 在你的代碼里自己重新定義。
18 編輯器模式行和其他需要羅嗦的事情
有一些編輯器可以解釋嵌入在源文件里的由一些特殊標(biāo)記標(biāo)明的配置信息。比如,emacs 能夠解釋被標(biāo)記成這樣的行:
-*-?mode:?c?-*-
或者這樣的:
/*
Local?Variables:
compile-command:?"gcc?-DMAGIC_DEBUG_FLAG?foo.c"
End:
*/
Vim 能夠解釋這樣的標(biāo)記:
/*?vim:set?sw=8?noet?*/
不要在源代碼中包含任何這樣的內(nèi)容。
每個(gè)人都有他自己的編輯器配置,你的源文件不 應(yīng)該覆蓋別人的配置。這包括有關(guān)縮進(jìn)和模式配置的標(biāo)記。人們可以使用他們自己定制 的模式,或者使用其他可以產(chǎn)生正確的縮進(jìn)的巧妙方法。
19 內(nèi)聯(lián)匯編
在特定架構(gòu)的代碼中,你可能需要內(nèi)聯(lián)匯編與 CPU 和平臺(tái)相關(guān)功能連接。需要這么做時(shí) 就不要猶豫。然而,當(dāng) C 可以完成工作時(shí),不要平白無故地使用內(nèi)聯(lián)匯編。在可能的情 況下,你可以并且應(yīng)該用 C 和硬件溝通。
請(qǐng)考慮去寫捆綁通用位元 (wrap common bits) 的內(nèi)聯(lián)匯編的簡(jiǎn)單輔助函數(shù),別去重復(fù) 地寫下只有細(xì)微差異內(nèi)聯(lián)匯編。記住內(nèi)聯(lián)匯編可以使用 C 參數(shù)。
大型,有一定復(fù)雜度的匯編函數(shù)應(yīng)該放在 .S 文件內(nèi),用相應(yīng)的 C 原型定義在 C 頭文 件中。匯編函數(shù)的 C 原型應(yīng)該使用 asmlinkage
。
你可能需要把匯編語句標(biāo)記為 volatile,用來阻止 GCC 在沒發(fā)現(xiàn)任何副作用后就把它 移除了。你不必總是這樣做,盡管,這不必要的舉動(dòng)會(huì)限制優(yōu)化。
在寫一個(gè)包含多條指令的單個(gè)內(nèi)聯(lián)匯編語句時(shí),把每條指令用引號(hào)分割而且各占一行, 除了最后一條指令外,在每個(gè)指令結(jié)尾加上 nt,讓匯編輸出時(shí)可以正確地縮進(jìn)下一條 指令:
asm?("magic?%reg1,?#42\n\t"
?????"more_magic?%reg2,?%reg3"
?????:?/*?outputs?*/?:?/*?inputs?*/?:?/*?clobbers?*/);
20 條件編譯
只要可能,就不要在 .c 文件里面使用預(yù)處理?xiàng)l件 (#if, #ifdef);這樣做讓代碼更難 閱讀并且更難去跟蹤邏輯。替代方案是,在頭文件中用預(yù)處理?xiàng)l件提供給那些 .c 文件 使用,再給 #else 提供一個(gè)空樁 (no-op stub) 版本,然后在 .c 文件內(nèi)無條件地調(diào)用 那些 (定義在頭文件內(nèi)的) 函數(shù)。這樣做,編譯器會(huì)避免為樁函數(shù) (stub) 的調(diào)用生成 任何代碼,產(chǎn)生的結(jié)果是相同的,但邏輯將更加清晰。
最好傾向于編譯整個(gè)函數(shù),而不是函數(shù)的一部分或表達(dá)式的一部分。與其放一個(gè) ifdef 在表達(dá)式內(nèi),不如分解出部分或全部表達(dá)式,放進(jìn)一個(gè)單獨(dú)的輔助函數(shù),并應(yīng)用預(yù)處理 條件到這個(gè)輔助函數(shù)內(nèi)。
如果你有一個(gè)在特定配置中,可能變成未使用的函數(shù)或變量,編譯器會(huì)警告它定義了但 未使用,把它標(biāo)記為 __maybe_unused 而不是將它包含在一個(gè)預(yù)處理?xiàng)l件中。(然而,如 果一個(gè)函數(shù)或變量總是未使用,就直接刪除它。)
在代碼中,盡可能地使用 IS_ENABLED 宏來轉(zhuǎn)化某個(gè) Kconfig 標(biāo)記為 C 的布爾 表達(dá)式,并在一般的 C 條件中使用它:
if?(IS_ENABLED(CONFIG_SOMETHING))?{
????????...
}
編譯器會(huì)做常量折疊,然后就像使用 #ifdef 那樣去包含或排除代碼塊,所以這不會(huì)帶 來任何運(yùn)行時(shí)開銷。然而,這種方法依舊允許 C 編譯器查看塊內(nèi)的代碼,并檢查它的正 確性 (語法,類型,符號(hào)引用,等等)。因此,如果條件不滿足,代碼塊內(nèi)的引用符號(hào)就 不存在時(shí),你還是必須去用 #ifdef。
在任何有意義的 #if 或 #ifdef 塊的末尾 (超過幾行的),在 #endif 同一行的后面寫下 注解,注釋這個(gè)條件表達(dá)式。例如:
#ifdef?CONFIG_SOMETHING
...
#endif?/*?CONFIG_SOMETHING?*/
原創(chuàng)不易,歡迎轉(zhuǎn)發(fā)、留言、點(diǎn)贊、分享給你的朋友,感謝您的支持!
長按識(shí)別二維碼關(guān)注獲取更多內(nèi)容
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!