Linux内核代码风格
Linux內(nèi)核代碼風(fēng)格
?
?? ? 這是一個(gè)簡短的文檔,描述了linux內(nèi)核的首選代碼風(fēng)格。代碼風(fēng)格是因人而異的,而且我不愿意把我的觀點(diǎn)強(qiáng)加給任何人,不過這里所講述的是我必須要維護(hù)的代碼所遵守的風(fēng)格,并且我也希望絕大多數(shù)其他代碼也能遵守這個(gè)風(fēng)格。請?jiān)趯懘a時(shí)至少考慮一下本文所述的風(fēng)格。
??首先,我建議你打印一份GNU代碼規(guī)范,然后不要讀它。燒了它,這是一個(gè)具有重大象征性意義的動作。
??不管怎樣,現(xiàn)在我們開始:
?
第一章:縮進(jìn)
?
????制表符是8個(gè)字符,所以縮進(jìn)也是8個(gè)字符。有些異端運(yùn)動試圖將縮進(jìn)變?yōu)?(乃至2)個(gè)字符深,這幾乎相當(dāng)于嘗試將圓周率的值定義為3。
???理由:縮進(jìn)的全部意義就在于清楚的定義一個(gè)控制塊起止于何處。尤其是當(dāng)你盯著你的屏幕連續(xù)看了20小時(shí)之后,你將會發(fā)現(xiàn)大一點(diǎn)的縮進(jìn)會使你更容易分辨縮進(jìn)。
???現(xiàn)在,有些人會抱怨8個(gè)字符的縮進(jìn)會使代碼向右邊移動的太遠(yuǎn),在80個(gè)字符的終端屏幕上就很難讀這樣的代碼。這個(gè)問題的答案是,如果你需要3級以上的縮進(jìn),不管用何種方式你的代碼已經(jīng)有問題了,應(yīng)該修正你的程序。
??簡而言之,8個(gè)字符的縮進(jìn)可以讓代碼更容易閱讀,還有一個(gè)好處是當(dāng)你的函數(shù)嵌套太深的時(shí)候可以給你警告。留心這個(gè)警告。
??在switch語句中消除多級縮進(jìn)的首選的方式是讓“switch”和從屬于它的“case”標(biāo)簽對齊于同一列,而不要“兩次縮進(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)格超級簡單。就是避免可能導(dǎo)致別人誤讀的表達(dá)式。
?除了注釋、文檔和Kconfig之外,不要使用空格來縮進(jìn),前面的例子是例外,是有意為之。
?選用一個(gè)好的編輯器,不要在行尾留空格。
?
第二章:把長的行和字符串打散
?
?代碼風(fēng)格的意義就在于使用平常使用的工具來維持代碼的可讀性和可維護(hù)性。每一行的長度的限制是80列,我們強(qiáng)烈建議您遵守這個(gè)慣例。長于80列的語句要打散成有意義的片段。每個(gè)片段要明顯短于原來的語句,而且放置的位置也明顯的靠右。同樣的規(guī)則也適用于有很長參數(shù)列表的函數(shù)頭。長字符串也要打散成較短的字符串。唯一的例外是超過80列可以大幅度提高可讀性并且不會隱藏信息的情況。
??void fun(int a, int b, int c)
?{
?? ? ? ?if (condition)
?? ? ? ? ? ? ??printk(KERN_WARNING "Warning this is a long printk with "
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??"3 parameters a: %u b: %u "
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??"c: %u \n", a, b, c);
?? ? ? ?else
?? ? ? ? ? ? ??next_statement;
?}
?
第三章:大括號和空格的放置
??C語言風(fēng)格中另外一個(gè)常見問題是大括號的放置。和縮進(jìn)大小不同,選擇或棄用某種放置策略并沒有多少技術(shù)上的原因,不過首選的方式,就像Kernighan和Ritchie展示給我們的,是把起始大括號放在行尾,而把結(jié)束大括號放在行首,所以:
????????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ù)的起始大括號放置于下一行的開頭,所以:
??? ? ? ?int function(int x)
?? ? ? ?{
?? ? ? ? ? ? ??body of function
?? ? ? ?}
?全世界的異端可能會抱怨這個(gè)不一致性是……呃……不一致的,不過所有思維健全的人都知道(a)K&R是_正確的_,并且(b)K&R是正確的。此外,不管怎樣函數(shù)都是特殊的(在C語言中,函數(shù)是不能嵌套的)。
?注意結(jié)束大括號獨(dú)自占據(jù)一行,除非它后面跟著同一個(gè)語句的剩余部分,也就是do語句中的“while”或者if語句中的“else”,像這樣:
??? ? ? ?do {
?? ? ? ? ? ? ??body of do-loop
?? ? ? ?} while (condition);
?和
? ? ? ?if (x == y) {
?? ? ? ? ? ? ?..
?? ? ? ?} else if (x > y) {
?? ? ? ? ? ? ??...
?? ? ? ?} else {
?? ? ? ? ? ? ??....
?? ? ? ?}
?
??理由:K&R。
?也請注意這種大括號的放置方式也能使空(或者差不多空的)行的數(shù)量最小化,同時(shí)不失可讀性。因此,由于你的屏幕上的新行是不可再生資源(想想25行的終端屏幕),你將會有更多的空行來放置注釋。
?只有一個(gè)單獨(dú)的語句的時(shí)候,不用加不必要的大括號。
?if (condition)
?? ? ? ?action();
??這點(diǎn)不適用于本身為某個(gè)條件語句的一個(gè)分支的單獨(dú)語句。這時(shí)需要在兩個(gè)分支里都使用大括號。
??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ù)(它們在Linux里也常常伴隨小括號而使用,盡管在C語言里這樣的小括號不是必需的,就像“struct fileinfo info”聲明過后的“sizeof info”)。?所以在這些關(guān)鍵字之后放一個(gè)空格:
???????if, switch, case, for, do, while
?但是不要在sizeof、typeof、alignof或者_(dá)_attribute__這些關(guān)鍵字之后放空格。例如,
???????s = sizeof(struct file);
?不要在小括號里的表達(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)體成員操作符前后不加空格。
?
?不要在行尾留空白。有些可以自動縮進(jìn)的編輯器會在新行的行首加入適量的空白,然后你就可以直接在那一行輸入代碼。不過假如你最后沒有在那一行輸入代碼,有些編輯器就不會移除已經(jīng)加入的空白,就像你故意留下一個(gè)只有空白的行。包含行尾空白的行就這樣產(chǎn)生了。
?當(dāng)Git發(fā)現(xiàn)補(bǔ)丁包含了行尾空白的時(shí)候會警告你,并且可以應(yīng)你的要求去掉行尾空白;不過如果你是正在打一系列補(bǔ)丁,這樣做會導(dǎo)致后面的補(bǔ)丁失敗,因?yàn)槟愀淖兞搜a(bǔ)丁的上下文。
???
第四章:命名?
?? C 是一個(gè)簡樸的語言,你的命名也應(yīng)該這樣。和 Modula-2 和 Pascal 程序員不同, C 程序員不使 用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字。 C 程序員會稱那個(gè)變量為 “tmp” ,這樣寫起來會更容易,而且至少不會令其難于理解。??? 不過,雖然混用大小寫的名字是不提倡使用的,但是全局變量還是需要一個(gè)具描述性的名字。稱一個(gè)全局函數(shù)為“foo”是一個(gè)難以饒恕的錯誤。
?全局變量(只有當(dāng)你真正需要它們的時(shí)候再用它)需要有一個(gè)具描述性的名字,就像全局函數(shù)。如果你有一個(gè)可以計(jì)算活動用戶數(shù)量的函數(shù),你應(yīng)該叫“count_active_users()”或者類似的名字,你不應(yīng)該叫它“cntuser()”。
??? 在函數(shù)名中包含函數(shù)類型(所謂的匈牙利命名法)是腦子出了問題 —— 編譯器知道那些類型而 且能夠檢查那些類型,這樣做只能把程序員弄糊涂了。難怪微軟總是制造出有問題的程序。??本地變量名應(yīng)該簡短,而且能夠表達(dá)相關(guān)的含義。如果你有一些隨機(jī)的整數(shù)型的循環(huán)計(jì)數(shù)器,它應(yīng)該被稱為“i”。叫它“l(fā)oop_counter”并無益處,如果它沒有被誤解的可能的話。類似的,“tmp”可以用來稱呼任意類型的臨時(shí)變量。
?如果你怕混淆了你的本地變量名,你就遇到另一個(gè)問題了,叫做函數(shù)增長荷爾蒙失衡綜合癥。請看第六章(函數(shù))。
?
?第五章:Typedef
?
不要使用類似“vps_t”之類的東西。對結(jié)構(gòu)體和指針使用typedef是一個(gè)錯誤。當(dāng)你在代碼里看到:
???????vps_t a;
?這代表什么意思呢?相反,如果是這樣
?? ? ? ?struct virtual_container *a;
?你就知道“a”是什么了。
?? ? 很多人認(rèn)為typedef“能提高可讀性”。實(shí)際不是這樣的。它們只在下列情況下有用:?(a)完全不透明的對象(這種情況下要主動使用typedef來隱藏這個(gè)對象實(shí)際上是什???? 么)。???例如:“pte_t”等不透明對象,你只能用合適的訪問函數(shù)來訪問它們。
??注意!不透明性和“訪問函數(shù)”本身是不好的。我們使用pte_t等類型的原因在于真的是完全沒有任何共用的可訪問信息。
?
? (b)? 清楚的整數(shù)類型,如此,這層抽象就可以幫助消除到底是 “int” 還是 “l(fā)ong” 的混淆。 8/u16/u32 是完全沒有問題的 typedef ,不過它們更符合類別 (d) 而不是這里。 再次注意!要這樣做,必須事出有因。如果某個(gè)變量是 “unsigned long“ ,那么沒有必要?? ? ? ?typedef unsigned long myflags_t;
?? ? ?不過如果有一個(gè)明確的原因,比如它在某種情況下可能會是一個(gè)“unsigned int”而在其他情況下可能為“unsigned long”,那么就不要猶豫,請務(wù)必使用typedef
?(c) 當(dāng)你使用sparse按字面的創(chuàng)建一個(gè)新類型來做類型檢查的時(shí)候。
?(d)?和標(biāo)準(zhǔn)C99類型相同的類型,在某些例外的情況下。
?????雖然讓眼睛和腦筋來適應(yīng)新的標(biāo)準(zhǔn)類型比如“uint32_t”不需要花很多時(shí)間,可是有些?人仍然拒絕使用它們。因此,Linux特有的等同于標(biāo)準(zhǔn)類型的“u8/u16/u32/u64”類型和它們的有符號類型是被允許的——盡管在你自己的新代碼中,它們不是強(qiáng)制要求要使用的。當(dāng)編輯已經(jīng)使用了某個(gè)類型集的已有代碼時(shí),你應(yīng)該遵循那些代碼中已經(jīng)做出的選擇。
??(e) 可以在用戶空間安全使用的類型。
?? ? ?在某些用戶空間可見的結(jié)構(gòu)體里,我們不能要求C99類型而且不能用上面提到的“u32”類型。因此,我們在與用戶空間共享的所有結(jié)構(gòu)體中使用__u32和類似的類型。可能還有其他的情況,不過基本的規(guī)則是永遠(yuǎn)不要使用typedef,除非你可以明確的應(yīng)用上述某個(gè)規(guī)則中的一個(gè)。
?? ?總的來說,如果一個(gè)指針或者一個(gè)結(jié)構(gòu)體里的元素可以合理的被直接訪問到,那么它們就不應(yīng)該是一個(gè)typedef。
?
第六章:函數(shù)
?
?????? 函數(shù)應(yīng)該簡短而漂亮,并且只完成一件事情。函數(shù)應(yīng)該可以一屏或者兩屏顯示完(我們都知 道 ISO/ANSI 屏幕大小是 80x24 ),只做一件事情,而且把它做好。 一個(gè)函數(shù)的最大長度是和該函數(shù)的復(fù)雜度和縮進(jìn)級數(shù)成反比的。所以,如果你有一個(gè)理論上 很簡單的只有一個(gè)很長(但是簡單)的 case 語句的函數(shù),而且你需要在每個(gè) case 里做很多很 小的事情,這樣的函數(shù)盡管很長,但也是可以的。?不過,如果你有一個(gè)復(fù)雜的函數(shù),而且你懷疑一個(gè)天分不是很高的高中一年級學(xué)生可能甚至搞不清楚這個(gè)函數(shù)的目的,你應(yīng)該嚴(yán)格的遵守前面提到的長度限制。使用輔助函數(shù),并為之取個(gè)具描述性的名字(如果你覺得它們的性能很重要的話,可以讓編譯器內(nèi)聯(lián)它們,這樣的效果往往會比你寫一個(gè)復(fù)雜函數(shù)的效果要好。)
?函數(shù)的另外一個(gè)衡量標(biāo)準(zhǔn)是本地變量的數(shù)量。此數(shù)量不應(yīng)超過5-10個(gè),否則你的函數(shù)就有問題了。重新考慮一下你的函數(shù),把它分拆成更小的函數(shù)。人的大腦一般可以輕松的同時(shí)跟蹤7個(gè)不同的事物,如果再增多的話,就會糊涂了。即便你聰穎過人,你也可能會記不清你2個(gè)星期前做過的事情。
?在源文件里,使用空行隔開不同的函數(shù)。如果該函數(shù)需要被導(dǎo)出,它的EXPORT*宏應(yīng)該緊貼在它的結(jié)束大括號之下。比如:
?int system_is_up(void)
?{
?? ? ? ?return system_state == SYSTEM_RUNNING;
?}
?EXPORT_SYMBOL(system_is_up);
??
在函數(shù)原型中,包含函數(shù)名和它們的數(shù)據(jù)類型。雖然 C 語言里沒有這樣的要求,在 Linux 里這 是提倡的做法,因?yàn)檫@樣可以很簡單的給讀者提供更多的有價(jià)值的信息。??
第七章:集中的函數(shù)退出途徑
?
?? 雖然被某些人聲稱已經(jīng)過時(shí),但是goto語句的等價(jià)物還是經(jīng)常被編譯器所使用,具體形式是無條件跳轉(zhuǎn)指令。當(dāng)一個(gè)函數(shù)從多個(gè)位置退出并且需要做一些通用的清理工作的時(shí)候,goto的好處就顯現(xiàn)出來了。
?理由是:
?-無條件語句容易理解和跟蹤
?-嵌套程度減小
?-可以避免由于修改時(shí)忘記更新某個(gè)單獨(dú)的退出點(diǎn)而導(dǎo)致的錯誤
?-減輕了編譯器的工作,無需刪除冗余代碼;)
?int fun(int a)
?{
?? ? ? ?int result = 0;
?? ? ? ?char *buffer = kmalloc(SIZE);
?? ? ? ?if (buffer == NULL)
?? ? ? ? ? ? ??return -ENOMEM;
?
? ? ? ?if (condition1) {
?? ? ? ? ? ? ??while (loop1) {
?? ? ? ? ? ? ? ? ? ? ?...
?? ? ? ? ? ? ??}
?? ? ? ? ? ? ??result = 1;
?? ? ? ? ? ? ??goto out;
?? ? ? ?}
?? ? ? ?...
?out:
?? ? ? ?kfree(buffer);
?? ? ? ?return result;
?}
?
??第八章:注釋
??? ? 注釋是好的,不過有過度注釋的危險(xiǎn)。永遠(yuǎn)不要在注釋里解釋你的代碼是如何運(yùn)作的:更好的做法是讓別人一看你的代碼就可以明白,解釋寫的很差的代碼是浪費(fèi)時(shí)間。一般的,你想要你的注釋告訴別人你的代碼做了什么,而不是怎么做的。也請你不要把注釋放在一個(gè)函數(shù)體內(nèi)部:如果函數(shù)復(fù)雜到你需要獨(dú)立的注釋其中的一部分,你很可能需要回到第六章看一看。你可以做一些小注釋來注明或警告某些很聰明(或者槽糕)的做法,但不要加太多。你應(yīng)該做的,是把注釋放在函數(shù)的頭部,告訴人們它做了什么,也可以加上它做這些事情的原因。
?? ?當(dāng)注釋內(nèi)核API函數(shù)時(shí),請使用kernel-doc格式。請看Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc以獲得詳細(xì)信息。
?Linux的注釋風(fēng)格是C89“/* ... */”風(fēng)格。不要使用C99風(fēng)格“// ...”注釋。
?長(多行)的首選注釋風(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.
?? ? ? ??*/
?
?注釋數(shù)據(jù)也是很重要的,不管是基本類型還是衍生類型。為了方便實(shí)現(xiàn)這一點(diǎn),每一行應(yīng)只聲明一個(gè)數(shù)據(jù)(不要使用逗號來一次聲明多個(gè)數(shù)據(jù))。這樣你就有空間來為每個(gè)數(shù)據(jù)寫一段小注釋來解釋它們的用途了。
?
?第九章:你已經(jīng)把事情弄糟了
?? ? 這沒什么,我們都是這樣。可能你的使用了很長時(shí)間Unix的朋友已經(jīng)告訴你“GNU emacs”能自動幫你格式化C源代碼,而且你也注意到了,確實(shí)是這樣,不過它所使用的默認(rèn)值和我們想要的相去甚遠(yuǎn)(實(shí)際上,甚至比隨機(jī)打的還要差——無數(shù)個(gè)猴子在GNU emacs里打字永遠(yuǎn)不會創(chuàng)造出一個(gè)好程序)(譯注:請參考Infinite Monkey Theorem)
所以你要么放棄GNU emacs,要么改變它讓它使用更合理的設(shè)定。要采用后一個(gè)方案,你可以把下面這段粘貼到你的.emacs文件里。
?(defun linux-c-mode ()
???"C mode with adjusted defaults for use with the Linux kernel."
???(interactive)
???(c-mode)
???(c-set-style "K&R")
???(setq tab-width 8)
???(setq indent-tabs-mode t)
???(setq c-basic-offset 8))
?這樣就定義了M-x linux-c-mode命令。當(dāng)你hack一個(gè)模塊的時(shí)候,如果你把字符串-*- linux-c -*-放在頭兩行的某個(gè)位置,這個(gè)模式將會被自動調(diào)用。如果你希望在你修改/usr/src/linux里的文件時(shí)魔術(shù)般自動打開linux-c-mode的話,你也可能需要添加
?(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\\.[ch]$" . linux-c-mode)
?? ? ? ? ? ? ? ? ? ? ?auto-mode-alist))
??到你的.emacs文件里。
不過就算你嘗試讓emacs正確的格式化代碼失敗了,也并不意味著你失去了一切:還可以用“indent”。不過,GNU indent也有和GNU emacs一樣有問題的設(shè)定,所以你需要給它一些命令選項(xiàng)。不過,這還不算太糟糕,因?yàn)榫退闶荊NU indent的作者也認(rèn)同K&R的權(quán)威性(GNU的人并不是壞人,他們只是在這個(gè)問題上被嚴(yán)重的誤導(dǎo)了),所以你只要給indent指定選項(xiàng)“-kr -i8”(代表“K&R,8個(gè)字符縮進(jìn)”),或者使用“scripts/Lindent”,這樣就可以以最時(shí)髦的方式縮進(jìn)源代碼。
“indent”有很多選項(xiàng),特別是重新格式化注釋的時(shí)候,你可能需要看一下它的手冊頁。不過記住:“indent”不能修正壞的編程習(xí)慣。
?
第十章:Kconfig配置文件
對于遍布源碼樹的所有Kconfig*配置文件來說,它們縮進(jìn)方式與C代碼相比有所不同。緊挨在“config”定義下面的行縮進(jìn)一個(gè)制表符,幫助信息則再多縮進(jìn)2個(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
?? ? ? ?...
??要查看配置文件的完整文檔,請看Documentation/kbuild/kconfig-language.txt。
?
第十一章:數(shù)據(jù)結(jié)構(gòu)
??如果一個(gè)數(shù)據(jù)結(jié)構(gòu),在創(chuàng)建和銷毀它的單線執(zhí)行環(huán)境之外可見,那么它必須要有一個(gè)引用計(jì)數(shù)器。內(nèi)核里沒有垃圾收集(并且內(nèi)核之外的垃圾收集慢且效率低下),這意味著你絕對需要記錄你對這種數(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ì)數(shù),它們通常有不同“類”的用戶。子類計(jì)數(shù)器統(tǒng)計(jì)子類用戶的數(shù)量,每當(dāng)子類計(jì)數(shù)器減至零時(shí),全局計(jì)數(shù)器減一。
?這種“多級引用計(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。
?
?第十二章:宏,枚舉和RTL
?
? 用于定義常量的宏的名字及枚舉里的標(biāo)簽需要大寫。??#define CONSTANT 0x12345
??在定義幾個(gè)相關(guān)的常量時(shí),最好用枚舉。宏的名字請用大寫字母,不過形如函數(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)避免的事情:
1)?影響控制流程的宏:
?
#define FOO(x)?????????????????????????????????? \?
??????? do {???????????????????????????? \?
?????????????? if (blah(x) < 0)????????????? \?
????????????????????? return -EBUGGERED;?? \?
??????? } while(0)?
?非常不好。它看起來像一個(gè)函數(shù),不過卻能導(dǎo)致“調(diào)用”它的函數(shù)退出;不要打亂讀者大腦里的語法分析器。
?2) 依賴于一個(gè)固定名字的本地變量的宏:
?#define FOO(val) bar(index, val)
?可能看起來像是個(gè)不錯的東西,不過它非常容易把讀代碼的人搞糊涂,而且容易導(dǎo)致看起來不相關(guān)的改動帶來錯誤。
3)?作為左值的帶參數(shù)的宏:?FOO(x) = y;如果有人把FOO變成一個(gè)內(nèi)聯(lián)函數(shù)的話,這種用法就會出錯了。
4)?忘記了優(yōu)先級:使用表達(dá)式定義常量的宏必須將表達(dá)式置于一對小括號之內(nèi)。帶參數(shù)的宏也要注意此類問題。
#define CONSTANT 0x4000
#define CONSTEXP (CONSTANT | 3)
??cpp手冊對宏的講解很詳細(xì)。Gcc internals手冊也詳細(xì)講解了RTL(譯注:registermtransfer language),內(nèi)核里的匯編語言經(jīng)常用到它。
??
?
第十三章:打印內(nèi)核消息?
? ?內(nèi)核開發(fā)者應(yīng)該是受過良好教育的。請一定注意內(nèi)核信息的拼寫,以給人以好的印象。不要用不規(guī)范的單詞比如“dont”,而要用“do not”或者“don't”。保證這些信息簡單、明了、無?歧義。
內(nèi)核信息不必以句號(譯注:英文句號,即點(diǎn))結(jié)束。在小括號里打印數(shù)字(%d)沒有任何價(jià)值,應(yīng)該避免這樣做。
<linux/device.h> 里有一些驅(qū)動模型診斷宏,你應(yīng)該使用它們,以確保信息對應(yīng)于正確的 設(shè)備和驅(qū)動,并且被標(biāo)記了正確的消息級別。這些宏有: dev_err(), dev_warn(), dev_info() 等等。對于那些不和某個(gè)特定設(shè)備相關(guān)連的信息, <linux/kernel.h> 定義了 pr_debug() 和 pr_info() 。?寫出好的調(diào)試信息可以是一個(gè)很大的挑戰(zhàn);當(dāng)你寫出來之后,這些信息在遠(yuǎn)程除錯的時(shí)候就會成為極大的幫助。當(dāng)DEBUG符號沒有被定義的時(shí)候,這些信息不應(yīng)該被編譯進(jìn)內(nèi)核里(也就是說,默認(rèn)地,它們不應(yīng)該被包含在內(nèi))。如果你使用dev_dbg()或者pr_debug(),就能自動達(dá)到這個(gè)效果。很多子系統(tǒng)擁有Kconfig選項(xiàng)來啟用-DDEBUG。還有一個(gè)相關(guān)的慣例是使用VERBOSE_DEBUG來添加dev_vdbg()消息到那些已經(jīng)由DEBUG啟用的消息之上。
?
??
第十四章:分配內(nèi)存?
?內(nèi)核提供了下面的一般用途的內(nèi)存分配函數(shù):kmalloc(),kzalloc(),kcalloc()和vmalloc()。請參考API文檔以獲取有關(guān)它們的詳細(xì)信息。
?傳遞結(jié)構(gòu)體大小的首選形式是這樣的:
?? ? ? ?p = kmalloc(sizeof(*p), ...);
??? ?另外一種傳遞方式中,sizeof的操作數(shù)是結(jié)構(gòu)體的名字,這樣會降低可讀性,并且可能會引入bug。有可能指針變量類型被改變時(shí),而對應(yīng)的傳遞給內(nèi)存分配函數(shù)的sizeof的結(jié)果不變。
?強(qiáng)制轉(zhuǎn)換一個(gè)void指針返回值是多余的。C語言本身保證了從void指針到其他任何指針類型的轉(zhuǎn)換是沒有問題的。
?
??第十五章:內(nèi)聯(lián)弊病
??
有一個(gè)常見的誤解是內(nèi)聯(lián)函數(shù)是gcc提供的可以讓代碼運(yùn)行更快的一個(gè)選項(xiàng)。雖然使用內(nèi)聯(lián)函數(shù)有時(shí)候是恰當(dāng)?shù)?#xff08;比如作為一種替代宏的方式,請看第十二章),不過很多情況下不是這樣。inline關(guān)鍵字的過度使用會使內(nèi)核變大,從而使整個(gè)系統(tǒng)運(yùn)行速度變慢。因?yàn)榇髢?nèi)核會占用更多的指令高速緩存(譯注:一級緩存通常是指令緩存和數(shù)據(jù)緩存分開的)而且會導(dǎo)致pagecache的可用內(nèi)存減少。想象一下,一次pagecache未命中就會導(dǎo)致一次磁盤尋址,將耗時(shí)5毫秒。5毫秒的時(shí)間內(nèi)CPU能執(zhí)行很多很多指令。
一個(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,如此不會有任何損失,因?yàn)闆]有什么好權(quán)衡的。雖然從技術(shù)上說這是正確的,但是實(shí)際上這種情況下即使不加inline gcc也可以自動使其內(nèi)聯(lián)。而且其他用戶可能會要求移除inline,由此而來的爭論會抵消inlin自身的潛在價(jià)值,得不償失。
?
?第十六章:函數(shù)返回值及命名
??? ? ?函數(shù)可以返回很多種不同類型的值,最常見的一種是表明函數(shù)執(zhí)行成功或者失敗的值。這樣的一個(gè)值可以表示為一個(gè)錯誤代碼整數(shù)(-Exxx=失敗,0=成功)或者一個(gè)“成功”布爾值(0=失敗,非0=成功)。
?? ?混合使用這兩種表達(dá)方式是難于發(fā)現(xiàn)的bug的來源。如果C語言本身嚴(yán)格區(qū)分整形和布爾型變量,那么編譯器就能夠幫我們發(fā)現(xiàn)這些錯誤……不過C語言不區(qū)分。為了避免產(chǎn)生這種bug,請遵循下面的慣例:
???????如果函數(shù)的名字是一個(gè)動作或者強(qiáng)制性的命令,那么這個(gè)函數(shù)應(yīng)該返回錯誤代碼整數(shù)。如果是一個(gè)判斷,那么函數(shù)應(yīng)該返回一個(gè)“成功”布爾值。
??? ?比如,“add work”是一個(gè)命令,所以add_work()函數(shù)在成功時(shí)返回0,在失敗時(shí)返回-EBUSY。類似的,因?yàn)椤癙CI device present”是一個(gè)判斷,所以pci_dev_present()函數(shù)在成功找到一個(gè)匹配的設(shè)備時(shí)應(yīng)該返回1,如果找不到時(shí)應(yīng)該返回0。
???? 所有導(dǎo)出(譯注: EXPORT )的函數(shù)都必須遵守這個(gè)慣例,所有的公共函數(shù)也都應(yīng)該如此。私 有( static )函數(shù)不需要如此,但是我們也推薦這樣做。??返回值是實(shí)際計(jì)算結(jié)果而不是計(jì)算是否成功的標(biāo)志的函數(shù)不受此慣例的限制。一般的,他們通過返回一些正常值范圍之外的結(jié)果來表示出錯。典型的例子是返回指針的函數(shù),他們使用NULL或者ERR_PTR機(jī)制來報(bào)告錯誤。
?
??第十七章:不要重新發(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)在你的代碼里 自己重新定義。?
??第十八章:編輯器模式行和其他需要羅嗦的事情
?
?有一些編輯器可以解釋嵌入在源文件里的由一些特殊標(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)的巧妙方法。
總結(jié)
以上是生活随笔為你收集整理的Linux内核代码风格的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PPT如何让多对象排列整齐
- 下一篇: 从头学习linux C 冒泡法排序