當(dāng)前位置:首頁 > 公眾號精選 > 小麥大叔
[導(dǎo)讀]曾經(jīng)在開發(fā)Linux內(nèi)核驅(qū)動的時候,創(chuàng)建了一個補(bǔ)丁文件,但是在把補(bǔ)丁打到主分支的時候提示很多編碼風(fēng)格的錯誤問題,后來重做了補(bǔ)丁才解決了問題,這也是沒有嚴(yán)格按照的Linux編碼風(fēng)格從而導(dǎo)致的問題。因為當(dāng)時代碼量不大,所以解決問題的時間相對較少。在代碼量增大的情況下可以借助工具進(jìn)行自動修改。


  • 從編碼風(fēng)格錯誤開始

  • 快速修改編碼風(fēng)格的工具

    • scripts/checkpatch.pl

    • scripts/Lindent

    • astyle

  • Linux 內(nèi)核代碼風(fēng)格

  • 1 縮進(jìn)

  • 2 把長的行和字符串打散

  • 3 大括號和空格的放置

  • 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)格錯誤開始

曾經(jīng)在開發(fā)Linux內(nèi)核驅(qū)動的時候,創(chuàng)建了一個補(bǔ)丁文件,但是在把補(bǔ)丁打到主分支的時候提示很多編碼風(fēng)格的錯誤問題,后來重做了補(bǔ)丁才解決了問題,這也是沒有嚴(yán)格按照的Linux編碼風(fēng)格從而導(dǎo)致的問題。因為當(dāng)時代碼量不大,所以解決問題的時間相對較少。在代碼量增大的情況下可以借助工具進(jìn)行自動修改。

快速修改編碼風(fēng)格的工具

scripts/checkpatch.pl

這是一個檢查patch是否符合內(nèi)核編碼規(guī)范的腳本。默認(rèn)的調(diào)用也確實如此。如果用來檢查原文件,需要加上-f的選項。

scripts/Lindent

源碼路徑下的scripts目錄中的工具Lindent可以用來自動修改縮進(jìn)問題。不過使用Lindent要求系統(tǒng)安裝indent這個工具。Ubuntu系統(tǒng)下可以使用apt-get install indent進(jìn)行「安裝」。

astyle

比較推薦使用這個工具,因為比較相當(dāng)方便,可以一鍵式轉(zhuǎn)換成linux,javagnu等等風(fēng)格。

下載地址項目地址文檔說明

如何使用,可以參考具體文檔說明,寫的比較詳細(xì)。

總而言之,應(yīng)該顧全大局,在進(jìn)行內(nèi)核開發(fā)和驅(qū)動開發(fā)的時候,嚴(yán)格遵守Linux的編碼規(guī)范,避免由于編碼不規(guī)范帶來的種種問題,可以參考內(nèi)核路徑下Documentation/CodingStyle文檔,以下轉(zhuǎn)自Linux內(nèi)核文檔,最權(quán)威的文檔路徑,很全很強(qiáng)大,原來在這個網(wǎng)站上已經(jīng)有中文版了,感謝萬分,具體的更新可以跳轉(zhuǎn)到你懂的網(wǎng)址。

「來自 Documentation/process/coding-style.rst 的中文翻譯」

Linux 內(nèi)核代碼風(fēng)格

這是一個簡短的文檔,描述了 linux 內(nèi)核的首選代碼風(fēng)格。代碼風(fēng)格是因人而異的, 而且我不愿意把自己的觀點強(qiáng)加給任何人,但這就像我去做任何事情都必須遵循的原則 那樣,我也希望在絕大多數(shù)事上保持這種的態(tài)度。請 (在寫代碼時) 至少考慮一下這里 的代碼風(fēng)格。

首先,我建議你打印一份 GNU 代碼規(guī)范,然后不要讀。燒了它,這是一個具有重大象征性意義的動作。

不管怎樣,現(xiàn)在我們開始:

1 縮進(jìn)

制表符是 8 個字符,所以縮進(jìn)也是 8 個字符。有些異端運動試圖將縮進(jìn)變?yōu)?4 (甚至 2!) 字符深,這幾乎相當(dāng)于嘗試將圓周率的值定義為 3。

理由:縮進(jìn)的全部意義就在于清楚的定義一個控制塊起止于何處。尤其是當(dāng)你盯著你的 屏幕連續(xù)看了 20 小時之后,你將會發(fā)現(xiàn)大一點的縮進(jìn)會使你更容易分辨縮進(jìn)。

現(xiàn)在,有些人會抱怨 8 個字符的縮進(jìn)會使代碼向右邊移動的太遠(yuǎn),在 80 個字符的終端 屏幕上就很難讀這樣的代碼。這個問題的答案是,如果你需要 3 級以上的縮進(jìn),不管用 何種方式你的代碼已經(jīng)有問題了,應(yīng)該修正你的程序。

簡而言之,8 個字符的縮進(jìn)可以讓代碼更容易閱讀,還有一個好處是當(dāng)你的函數(shù)嵌套太 深的時候可以給你警告。留心這個警告。

在 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;
}

不要把多個語句放在一行里,除非你有什么東西要隱藏:

if?(condition)?do_this;
??do_something_everytime;

也不要在一行里放多個賦值語句。內(nèi)核代碼風(fēng)格超級簡單。就是避免可能導(dǎo)致別人誤讀 的表達(dá)式。

除了注釋、文檔和 Kconfig 之外,不要使用空格來縮進(jìn),前面的例子是例外,是有意為 之。

選用一個好的編輯器,不要在行尾留空格。

2 把長的行和字符串打散

代碼風(fēng)格的意義就在于使用平常使用的工具來維持代碼的可讀性和可維護(hù)性。

每一行的長度的限制是 80 列,我們強(qiáng)烈建議您遵守這個慣例。

長于 80 列的語句要打散成有意義的片段。除非超過 80 列能顯著增加可讀性,并且不 會隱藏信息。子片段要明顯短于母片段,并明顯靠右。這同樣適用于有著很長參數(shù)列表 的函數(shù)頭。然而,絕對不要打散對用戶可見的字符串,例如 printk 信息,因為這樣就 很難對它們 grep。

3 大括號和空格的放置

C 語言風(fēng)格中另外一個常見問題是大括號的放置。和縮進(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;
}

不過,有一個例外,那就是函數(shù):函數(shù)的起始大括號放置于下一行的開頭,所以:

int?function(int?x)
{
????????body?of?function
}

全世界的異端可能會抱怨這個不一致性是... 呃... 不一致的,不過所有思維健全的人 都知道 (a) K&R 是 「正確的」 并且 (b) K&R 是正確的。此外,不管怎樣函數(shù)都是特 殊的 (C 函數(shù)是不能嵌套的)。

注意結(jié)束大括號獨自占據(jù)一行,除非它后面跟著同一個語句的剩余部分,也就是 do 語 句中的 “while” 或者 if 語句中的 “else”,像這樣:

do?{
????????body?of?do-loop
}?while?(condition);

if?(x?==?y)?{
????????..
}?else?if?(x?>?y)?{
????????...
}?else?{
????????....
}

理由:K&R。

也請注意這種大括號的放置方式也能使空 (或者差不多空的) 行的數(shù)量最小化,同時不 失可讀性。因此,由于你的屏幕上的新行是不可再生資源 (想想 25 行的終端屏幕),你 將會有更多的空行來放置注釋。

當(dāng)只有一個單獨的語句的時候,不用加不必要的大括號。

if?(condition)
????????action();

if?(condition)
????????do_this();
else
????????do_that();

這并不適用于只有一個條件分支是單語句的情況;這時所有分支都要使用大括號:

if?(condition)?{
????????do_this();
????????do_that();
}?else?{
????????otherwise();
}

3.1 空格

Linux 內(nèi)核的空格使用方式 (主要) 取決于它是用于函數(shù)還是關(guān)鍵字。(大多數(shù)) 關(guān)鍵字 后要加一個空格。值得注意的例外是 sizeof, typeof, alignof 和 「attribute」,這 些關(guān)鍵字某些程度上看起來更像函數(shù) (它們在 Linux 里也常常伴隨小括號而使用,盡管 在 C 里這樣的小括號不是必需的,就像 struct fileinfo info; 聲明過后的 sizeof info)。

所以在這些關(guān)鍵字之后放一個空格:

if,?switch,?case,?for,?do,?while

但是不要在 sizeof, typeof, alignof 或者 「attribute」 這些關(guān)鍵字之后放空格。例如,

s?=?sizeof(struct?file);

不要在小括號里的表達(dá)式兩側(cè)加空格。這是一個 「反例」

s?=?sizeof(?struct?file?);

當(dāng)聲明指針類型或者返回指針類型的函數(shù)時, * 的首選使用方式是使之靠近變量名 或者函數(shù)名,而不是靠近類型名。例子:

char?*linux_banner;
unsigned?long?long?memparse(char?*ptr,?char?**retptr);
char?*match_strdup(substring_t?*s);

在大多數(shù)二元和三元操作符兩側(cè)使用一個空格,例如下面所有這些操作符:

=??+??-????*??/??%??|??&??^??<=??>=??==??!=?????:

但是一元操作符后不要加空格:

&??*??+??-??~??!??sizeof??typeof??alignof??__attribute__??defined

后綴自加和自減一元操作符前不加空格:

++??--

前綴自加和自減一元操作符后不加空格:

++??--

.-> 結(jié)構(gòu)體成員操作符前后不加空格。

不要在行尾留空白。有些可以自動縮進(jìn)的編輯器會在新行的行首加入適量的空白,然后 你就可以直接在那一行輸入代碼。不過假如你最后沒有在那一行輸入代碼,有些編輯器 就不會移除已經(jīng)加入的空白,就像你故意留下一個只有空白的行。包含行尾空白的行就 這樣產(chǎn)生了。

當(dāng) git 發(fā)現(xiàn)補(bǔ)丁包含了行尾空白的時候會警告你,并且可以應(yīng)你的要求去掉行尾空白;不過如果你是正在打一系列補(bǔ)丁,這樣做會導(dǎo)致后面的補(bǔ)丁失敗,因為你改變了補(bǔ)丁的 上下文。

4 命名

C 是一個簡樸的語言,你的命名也應(yīng)該這樣。和 Modula-2 和 Pascal 程序員不同, C 程序員不使用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字。C 程序員會 稱那個變量為 tmp ,這樣寫起來會更容易,而且至少不會令其難于理解。

不過,雖然混用大小寫的名字是不提倡使用的,但是全局變量還是需要一個具描述性的 名字。稱一個全局函數(shù)為 foo 是一個難以饒恕的錯誤。

全局變量 (只有當(dāng)你 「真正」 需要它們的時候再用它) 需要有一個具描述性的名字,就 像全局函數(shù)。如果你有一個可以計算活動用戶數(shù)量的函數(shù),你應(yīng)該叫它count_active_users() 或者類似的名字,你不應(yīng)該叫它 cntuser() 。

在函數(shù)名中包含函數(shù)類型 (所謂的匈牙利命名法) 是腦子出了問題——編譯器知道那些類 型而且能夠檢查那些類型,這樣做只能把程序員弄糊涂了。難怪微軟總是制造出有問題 的程序。

本地變量名應(yīng)該簡短,而且能夠表達(dá)相關(guān)的含義。如果你有一些隨機(jī)的整數(shù)型的循環(huán)計 數(shù)器,它應(yīng)該被稱為 i 。叫它 loop_counter 并無益處,如果它沒有被誤解的 可能的話。類似的, tmp 可以用來稱呼任意類型的臨時變量。

如果你怕混淆了你的本地變量名,你就遇到另一個問題了,叫做函數(shù)增長荷爾蒙失衡綜 合癥。請看第六章 (函數(shù))。

5 Typedef

不要使用類似 vps_t 之類的東西。

對結(jié)構(gòu)體和指針使用 typedef 是一個 「錯誤」 。當(dāng)你在代碼里看到:

vps_t?a;

這代表什么意思呢?

相反,如果是這樣

struct?virtual_container?*a;

你就知道 a 是什么了。

很多人認(rèn)為 typedef 能提高可讀性 。實際不是這樣的。它們只在下列情況下有用:

  1. 完全不透明的對象 (這種情況下要主動使用 typedef 來 「隱藏」 這個對象實際上 是什么)。

    例如:pte_t 等不透明對象,你只能用合適的訪問函數(shù)來訪問它們。

    不透明性和 “訪問函數(shù)” 本身是不好的。我們使用 pte_t 等類型的原因在于真 的是完全沒有任何共用的可訪問信息。

  2. 清楚的整數(shù)類型,如此,這層抽象就可以 「幫助」 消除到底是 int 還是 long 的混淆。

    u8/u16/u32 是完全沒有問題的 typedef,不過它們更符合類別 (d) 而不是這里。

    要這樣做,必須事出有因。如果某個變量是 unsigned long ,那么沒有必要typedef unsigned long myflags_t;

    不過如果有一個明確的原因,比如它在某種情況下可能會是一個 unsigned int 而在其他情況下可能為 unsigned long ,那么就不要猶豫,請務(wù)必使用 typedef。

  3. 當(dāng)你使用 sparse 按字面的創(chuàng)建一個 「新」 類型來做類型檢查的時候。

  4. 和標(biāo)準(zhǔn) C99 類型相同的類型,在某些例外的情況下。

    雖然讓眼睛和腦筋來適應(yīng)新的標(biāo)準(zhǔn)類型比如 uint32_t 不需要花很多時間,可 是有些人仍然拒絕使用它們。

    因此,Linux 特有的等同于標(biāo)準(zhǔn)類型的 u8/u16/u32/u64 類型和它們的有符號 類型是被允許的——盡管在你自己的新代碼中,它們不是強(qiáng)制要求要使用的。

    當(dāng)編輯已經(jīng)使用了某個類型集的已有代碼時,你應(yīng)該遵循那些代碼中已經(jīng)做出的選 擇。

  5. 可以在用戶空間安全使用的類型。

    在某些用戶空間可見的結(jié)構(gòu)體里,我們不能要求 C99 類型而且不能用上面提到的 u32 類型。因此,我們在與用戶空間共享的所有結(jié)構(gòu)體中使用 __u32 和類似 的類型。

可能還有其他的情況,不過基本的規(guī)則是 「永遠(yuǎn)不要」 使用 typedef,除非你可以明 確的應(yīng)用上述某個規(guī)則中的一個。

總的來說,如果一個指針或者一個結(jié)構(gòu)體里的元素可以合理的被直接訪問到,那么它們 就不應(yīng)該是一個 typedef。

6 函數(shù)

函數(shù)應(yīng)該簡短而漂亮,并且只完成一件事情。函數(shù)應(yīng)該可以一屏或者兩屏顯示完 (我們 都知道 ISO/ANSI 屏幕大小是 80x24),只做一件事情,而且把它做好。

一個函數(shù)的最大長度是和該函數(shù)的復(fù)雜度和縮進(jìn)級數(shù)成反比的。所以,如果你有一個理 論上很簡單的只有一個很長 (但是簡單) 的 case 語句的函數(shù),而且你需要在每個 case 里做很多很小的事情,這樣的函數(shù)盡管很長,但也是可以的。

不過,如果你有一個復(fù)雜的函數(shù),而且你懷疑一個天分不是很高的高中一年級學(xué)生可能 甚至搞不清楚這個函數(shù)的目的,你應(yīng)該嚴(yán)格遵守前面提到的長度限制。使用輔助函數(shù), 并為之取個具描述性的名字 (如果你覺得它們的性能很重要的話,可以讓編譯器內(nèi)聯(lián)它 們,這樣的效果往往會比你寫一個復(fù)雜函數(shù)的效果要好。)

函數(shù)的另外一個衡量標(biāo)準(zhǔn)是本地變量的數(shù)量。此數(shù)量不應(yīng)超過 5-10 個,否則你的函數(shù) 就有問題了。重新考慮一下你的函數(shù),把它分拆成更小的函數(shù)。人的大腦一般可以輕松 的同時跟蹤 7 個不同的事物,如果再增多的話,就會糊涂了。即便你聰穎過人,你也可 能會記不清你 2 個星期前做過的事情。

在源文件里,使用空行隔開不同的函數(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 里這是提倡的做法,因為這樣可以很簡單的給讀者提供更多的有價值的信息。

7 集中的函數(shù)退出途徑

雖然被某些人聲稱已經(jīng)過時,但是 goto 語句的等價物還是經(jīng)常被編譯器所使用,具體 形式是無條件跳轉(zhuǎn)指令。

當(dāng)一個函數(shù)從多個位置退出,并且需要做一些類似清理的常見操作時,goto 語句就很方 便了。如果并不需要清理操作,那么直接 return 即可。

選擇一個能夠說明 goto 行為或它為何存在的標(biāo)簽名。如果 goto 要釋放 buffer, 一個不錯的名字可以是 out_free_buffer: 。別去使用像 err1:err2: 這樣的GW_BASIC 名稱,因為一旦你添加或刪除了 (函數(shù)的) 退出路徑,你就必須對它們 重新編號,這樣會難以去檢驗正確性。

使用 goto 的理由是:

  • 無條件語句容易理解和跟蹤
  • 嵌套程度減小
  • 可以避免由于修改時忘記更新個別的退出點而導(dǎo)致錯誤
  • 讓編譯器省去刪除冗余代碼的工作 ;)
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;
}

一個需要注意的常見錯誤是 一個 err 錯誤 ,就像這樣:

err:
????????kfree(foo->bar);
????????kfree(foo);
????????return?ret;

這段代碼的錯誤是,在某些退出路徑上 foo 是 NULL。通常情況下,通過把它分離 成兩個錯誤標(biāo)簽 err_free_bar:err_free_foo: 來修復(fù)這個錯誤:

err_free_bar:
???????kfree(foo->bar);
err_free_foo:
???????kfree(foo);
???????return?ret;

理想情況下,你應(yīng)該模擬錯誤來測試所有退出路徑。

8 注釋

注釋是好的,不過有過度注釋的危險。永遠(yuǎn)不要在注釋里解釋你的代碼是如何運作的:更好的做法是讓別人一看你的代碼就可以明白,解釋寫的很差的代碼是浪費時間。

一般的,你想要你的注釋告訴別人你的代碼做了什么,而不是怎么做的。也請你不要把 注釋放在一個函數(shù)體內(nèi)部:如果函數(shù)復(fù)雜到你需要獨立的注釋其中的一部分,你很可能 需要回到第六章看一看。你可以做一些小注釋來注明或警告某些很聰明 (或者槽糕) 的 做法,但不要加太多。你應(yīng)該做的,是把注釋放在函數(shù)的頭部,告訴人們它做了什么, 也可以加上它做這些事情的原因。

當(dāng)注釋內(nèi)核 API 函數(shù)時,請使用 kernel-doc 格式。請看 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.
?*/

對于在 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ù)也是很重要的,不管是基本類型還是衍生類型。為了方便實現(xiàn)這一點,每一行 應(yīng)只聲明一個數(shù)據(jù) (不要使用逗號來一次聲明多個數(shù)據(jù))。這樣你就有空間來為每個數(shù)據(jù) 寫一段小注釋來解釋它們的用途了。

9 你已經(jīng)把事情弄糟了

這沒什么,我們都是這樣。可能你的使用了很長時間 Unix 的朋友已經(jīng)告訴你 GNU emacs 能自動幫你格式化 C 源代碼,而且你也注意到了,確實是這樣,不過它 所使用的默認(rèn)值和我們想要的相去甚遠(yuǎn) (實際上,甚至比隨機(jī)打的還要差——無數(shù)個猴子 在 GNU emacs 里打字永遠(yuǎn)不會創(chuàng)造出一個好程序) (譯注:Infinite Monkey Theorem)

所以你要么放棄 GNU emacs,要么改變它讓它使用更合理的設(shè)定。要采用后一個方案, 你可以把下面這段粘貼到你的 .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 在 ~/src/linux-trees 下的 C 源文件獲得更好的內(nèi)核代碼風(fēng)格。

不過就算你嘗試讓 emacs 正確的格式化代碼失敗了,也并不意味著你失去了一切:還可 以用 indent 。

不過,GNU indent 也有和 GNU emacs 一樣有問題的設(shè)定,所以你需要給它一些命令選 項。不過,這還不算太糟糕,因為就算是 GNU indent 的作者也認(rèn)同 K&R 的權(quán)威性 (GNU 的人并不是壞人,他們只是在這個問題上被嚴(yán)重的誤導(dǎo)了),所以你只要給 indent 指定選項 -kr -i8 (代表 K&R,8 字符縮進(jìn)),或使用 scripts/Lindent 這樣就可以以最時髦的方式縮進(jìn)源代碼。

indent 有很多選項,特別是重新格式化注釋的時候,你可能需要看一下它的手冊。不過記?。?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 配置文件

對于遍布源碼樹的所有 Kconfig* 配置文件來說,它們縮進(jìn)方式有所不同。緊挨著 config 定義的行,用一個制表符縮進(jìn),然而 help 信息的縮進(jìn)則額外增加 2 個空 格。舉個例子:

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.

而那些危險的功能 (比如某些文件系統(tǒng)的寫支持) 應(yīng)該在它們的提示字符串里顯著的聲 明這一點:

config?ADFS_FS_RW
??????bool?"ADFS?write?support?(DANGEROUS)"
??????depends?on?ADFS_FS
??????...

要查看配置文件的完整文檔,請看 Documentation/kbuild/kconfig-language.txt。

11 數(shù)據(jù)結(jié)構(gòu)

如果一個數(shù)據(jù)結(jié)構(gòu),在創(chuàng)建和銷毀它的單線執(zhí)行環(huán)境之外可見,那么它必須要有一個引 用計數(shù)器。內(nèi)核里沒有垃圾收集 (并且內(nèi)核之外的垃圾收集慢且效率低下),這意味著你 絕對需要記錄你對這種數(shù)據(jù)結(jié)構(gòu)的使用情況。

引用計數(shù)意味著你能夠避免上鎖,并且允許多個用戶并行訪問這個數(shù)據(jù)結(jié)構(gòu)——而不需要 擔(dān)心這個數(shù)據(jù)結(jié)構(gòu)僅僅因為暫時不被使用就消失了,那些用戶可能不過是沉睡了一陣或 者做了一些其他事情而已。

注意上鎖 「不能」 取代引用計數(shù)。上鎖是為了保持?jǐn)?shù)據(jù)結(jié)構(gòu)的一致性,而引用計數(shù)是一 個內(nèi)存管理技巧。通常二者都需要,不要把兩個搞混了。

很多數(shù)據(jù)結(jié)構(gòu)實際上有 2 級引用計數(shù),它們通常有不同 的用戶。子類計數(shù)器統(tǒng) 計子類用戶的數(shù)量,每當(dāng)子類計數(shù)器減至零時,全局計數(shù)器減一。

這種 多級引用計數(shù) 的例子可以在內(nèi)存管理 (struct mm_struct: mm_users 和 mm_count),和文件系統(tǒng) (struct super_block: s_count 和 s_active) 中找到。

記?。喝绻硪粋€執(zhí)行線索可以找到你的數(shù)據(jù)結(jié)構(gòu),但這個數(shù)據(jù)結(jié)構(gòu)沒有引用計數(shù)器, 這里幾乎肯定是一個 bug。

12 宏,枚舉和RTL

用于定義常量的宏的名字及枚舉里的標(biāo)簽需要大寫。

#define?CONSTANT?0x12345

在定義幾個相關(guān)的常量時,最好用枚舉。

宏的名字請用大寫字母,不過形如函數(shù)的宏的名字可以用小寫字母。

一般的,如果能寫成內(nèi)聯(lián)函數(shù)就不要寫成像函數(shù)的宏。

含有多個語句的宏應(yīng)該被包含在一個 do-while 代碼塊里:

#define?macrofun(a,?b,?c)???????????????????????\
????????do?{????????????????????????????????????\
????????????????if?(a?==?5)?????????????????????\
????????????????????????do_this(b,?c);??????????\
????????}?while?(0)

使用宏的時候應(yīng)避免的事情:

  1. 影響控制流程的宏:
#define?FOO(x)??????????????????????????????????\
????????do?{????????????????????????????????????\
????????????????if?(blah(x)?????????????????????????return?-EBUGGERED;??????\
????????}?while?(0)

「非?!?/strong> 不好。它看起來像一個函數(shù),不過卻能導(dǎo)致 調(diào)用 它的函數(shù)退出;不要打 亂讀者大腦里的語法分析器。

  1. 依賴于一個固定名字的本地變量的宏:
#define?FOO(val)?bar(index,?val)

可能看起來像是個不錯的東西,不過它非常容易把讀代碼的人搞糊涂,而且容易導(dǎo)致看起 來不相關(guān)的改動帶來錯誤。

  1. 作為左值的帶參數(shù)的宏:FOO(x) = y;如果有人把 FOO 變成一個內(nèi)聯(lián)函數(shù)的話,這 種用法就會出錯了。
  2. 忘記了優(yōu)先級:使用表達(dá)式定義常量的宏必須將表達(dá)式置于一對小括號之內(nèi)。帶參數(shù) 的宏也要注意此類問題。
#define?CONSTANT?0x4000
#define?CONSTEXP?(CONSTANT?|?3)
  1. 在宏里定義類似函數(shù)的本地變量時命名沖突:
#define?FOO(x)??????????????????????????\
({??????????????????????????????????????\
????????typeof(x)?ret;??????????????????\
????????ret?=?calc_ret(x);??????????????\
????????(ret);??????????????????????????\
})

ret 是本地變量的通用名字 - __foo_ret 更不容易與一個已存在的變量沖突。

cpp 手冊對宏的講解很詳細(xì)。gcc internals 手冊也詳細(xì)講解了 RTL,內(nèi)核里的匯編語 言經(jīng)常用到它。

13 打印內(nèi)核消息

內(nèi)核開發(fā)者應(yīng)該是受過良好教育的。請一定注意內(nèi)核信息的拼寫,以給人以好的印象。不要用不規(guī)范的單詞比如 dont,而要用 do not 或者 don't 。保證這些信 息簡單明了,無歧義。

內(nèi)核信息不必以英文句號結(jié)束。

在小括號里打印數(shù)字 (%d) 沒有任何價值,應(yīng)該避免這樣做。

里有一些驅(qū)動模型診斷宏,你應(yīng)該使用它們,以確保信息對應(yīng)于正確 的設(shè)備和驅(qū)動,并且被標(biāo)記了正確的消息級別。這些宏有:dev_err(), dev_warn(), dev_info() 等等。對于那些不和某個特定設(shè)備相關(guān)連的信息, 定義了 pr_notice(), pr_info(), pr_warn(), pr_err()和其他。

寫出好的調(diào)試信息可以是一個很大的挑戰(zhàn);一旦你寫出后,這些信息在遠(yuǎn)程除錯時能提 供極大的幫助。然而打印調(diào)試信息的處理方式同打印非調(diào)試信息不同。其他 pr_XXX() 函數(shù)能無條件地打印,pr_debug() 卻不;默認(rèn)情況下它不會被編譯,除非定義了 DEBUG 或設(shè)定了 CONFIG_DYNAMIC_DEBUG。實際這同樣是為了 dev_dbg(),一個相關(guān)約定是在一 個已經(jīng)開啟了 DEBUG 時,使用 VERBOSE_DEBUG 來添加 dev_vdbg()。

許多子系統(tǒng)擁有 Kconfig 調(diào)試選項來開啟 -DDEBUG 在對應(yīng)的 Makefile 里面;在其他 情況下,特殊文件使用 #define DEBUG。當(dāng)一條調(diào)試信息需要被無條件打印時,例如, 如果已經(jīng)包含一個調(diào)試相關(guān)的 #ifdef 條件,printk(KERN_DEBUG ...) 就可被使用。

14 分配內(nèi)存

內(nèi)核提供了下面的一般用途的內(nèi)存分配函數(shù):kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc()vzalloc()。請參考 API 文檔以獲取有關(guān)它們的詳細(xì)信息。

傳遞結(jié)構(gòu)體大小的首選形式是這樣的:

p?=?kmalloc(sizeof(*p),?...);

另外一種傳遞方式中,sizeof 的操作數(shù)是結(jié)構(gòu)體的名字,這樣會降低可讀性,并且可能 會引入 bug。有可能指針變量類型被改變時,而對應(yīng)的傳遞給內(nèi)存分配函數(shù)的 sizeof 的結(jié)果不變。

強(qiáng)制轉(zhuǎn)換一個 void 指針返回值是多余的。C 語言本身保證了從 void 指針到其他任何 指針類型的轉(zhuǎn)換是沒有問題的。

分配一個數(shù)組的首選形式是這樣的:

p?=?kmalloc_array(n,?sizeof(...),?...);

分配一個零長數(shù)組的首選形式是這樣的:

p?=?kcalloc(n,?sizeof(...),?...);

兩種形式檢查分配大小 n * sizeof(...) 的溢出,如果溢出返回 NULL。

15 內(nèi)聯(lián)弊病

有一個常見的誤解是 內(nèi)聯(lián) 是 gcc 提供的可以讓代碼運行更快的一個選項。雖然使用內(nèi)聯(lián)函數(shù)有時候是恰當(dāng)?shù)?(比如作為一種替代宏的方式,請看第十二章),不過很多情 況下不是這樣。「inline 的過度使用會使內(nèi)核變大」,從而使整個系統(tǒng)運行速度變慢。因為體積大內(nèi)核會占用更多的指令高速緩存,而且會導(dǎo)致 pagecache 的可用內(nèi)存減少。想象一下,一次 pagecache 未命中就會導(dǎo)致一次磁盤尋址,將耗時 5 毫秒。

「5 毫秒的 時間內(nèi) CPU 能執(zhí)行很多很多指令?!?/strong>

一個基本的原則是如果一個函數(shù)有 3 行以上,就不要把它變成內(nèi)聯(lián)函數(shù)。這個原則的一 個例外是,如果你知道某個參數(shù)是一個編譯時常量,而且因為這個常量你確定編譯器在 編譯時能優(yōu)化掉你的函數(shù)的大部分代碼,那仍然可以給它加上 inline 關(guān)鍵字。kmalloc() 內(nèi)聯(lián)函數(shù)就是一個很好的例子。

人們經(jīng)常主張給 static 的而且只用了一次的函數(shù)加上 inline,如此不會有任何損失, 因為沒有什么好權(quán)衡的。雖然從技術(shù)上說這是正確的,但是實際上這種情況下即使不加 inline gcc 也可以自動使其內(nèi)聯(lián)。而且其他用戶可能會要求移除 inline,由此而來的 爭論會抵消 inline 自身的潛在價值,得不償失。

16 函數(shù)返回值及命名

函數(shù)可以返回多種不同類型的值,最常見的一種是表明函數(shù)執(zhí)行成功或者失敗的值。這樣 的一個值可以表示為一個錯誤代碼整數(shù) (-Exxx=失敗,0=成功) 或者一個 成功 布爾值 (0=失敗,非0=成功)。

混合使用這兩種表達(dá)方式是難于發(fā)現(xiàn)的 bug 的來源。如果 C 語言本身嚴(yán)格區(qū)分整形和 布爾型變量,那么編譯器就能夠幫我們發(fā)現(xiàn)這些錯誤... 不過 C 語言不區(qū)分。為了避免 產(chǎn)生這種 bug,請遵循下面的慣例:

如果函數(shù)的名字是一個動作或者強(qiáng)制性的命令,那么這個函數(shù)應(yīng)該返回錯誤代
碼整數(shù)。如果是一個判斷,那么函數(shù)應(yīng)該返回一個?"成功"?布爾值。

比如, add work 是一個命令,所以 add_work() 在成功時返回 0,在失敗時返回 -EBUSY。類似的,因為 PCI device present 是一個判斷,所以 pci_dev_present() 在成功找到一個匹配的設(shè)備時應(yīng)該返回 1,如果找不到時應(yīng)該返回 0。

所有 EXPORTed 函數(shù)都必須遵守這個慣例,所有的公共函數(shù)也都應(yīng)該如此。私有 (static) 函數(shù)不需要如此,但是我們也推薦這樣做。

返回值是實際計算結(jié)果而不是計算是否成功的標(biāo)志的函數(shù)不受此慣例的限制。一般的, 他們通過返回一些正常值范圍之外的結(jié)果來表示出錯。典型的例子是返回指針的函數(shù), 他們使用 NULL 或者 ERR_PTR 機(jī)制來報告錯誤。

17 不要重新發(fā)明內(nèi)核宏

頭文件 include/linux/kernel.h 包含了一些宏,你應(yīng)該使用它們,而不要自己寫一些 它們的變種。比如,如果你需要計算一個數(shù)組的長度,使用這個宏

#define?ARRAY_SIZE(x)?(sizeof(x)?/?sizeof((x)[0]))

類似的,如果你要計算某結(jié)構(gòu)體成員的大小,使用

#define?FIELD_SIZEOF(t,?f)?(sizeof(((t*)0)->f))

還有可以做嚴(yán)格的類型檢查的 min() 和 max() 宏,如果你需要可以使用它們。你可以 自己看看那個頭文件里還定義了什么你可以拿來用的東西,如果有定義的話,你就不應(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)容。

每個人都有他自己的編輯器配置,你的源文件不 應(yīng)該覆蓋別人的配置。這包括有關(guān)縮進(jìn)和模式配置的標(biāo)記。人們可以使用他們自己定制 的模式,或者使用其他可以產(chǎn)生正確的縮進(jìn)的巧妙方法。

19 內(nèi)聯(lián)匯編

在特定架構(gòu)的代碼中,你可能需要內(nèi)聯(lián)匯編與 CPU 和平臺相關(guān)功能連接。需要這么做時 就不要猶豫。然而,當(dāng) C 可以完成工作時,不要平白無故地使用內(nèi)聯(lián)匯編。在可能的情 況下,你可以并且應(yīng)該用 C 和硬件溝通。

請考慮去寫捆綁通用位元 (wrap common bits) 的內(nèi)聯(liá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)任何副作用后就把它 移除了。你不必總是這樣做,盡管,這不必要的舉動會限制優(yōu)化。

在寫一個包含多條指令的單個內(nèi)聯(lián)匯編語句時,把每條指令用引號分割而且各占一行, 除了最后一條指令外,在每個指令結(jié)尾加上 nt,讓匯編輸出時可以正確地縮進(jìn)下一條 指令:

asm?("magic?%reg1,?#42\n\t"
?????"more_magic?%reg2,?%reg3"
?????:?/*?outputs?*/?:?/*?inputs?*/?:?/*?clobbers?*/);

20 條件編譯

只要可能,就不要在 .c 文件里面使用預(yù)處理條件 (#if, #ifdef);這樣做讓代碼更難 閱讀并且更難去跟蹤邏輯。替代方案是,在頭文件中用預(yù)處理條件提供給那些 .c 文件 使用,再給 #else 提供一個空樁 (no-op stub) 版本,然后在 .c 文件內(nèi)無條件地調(diào)用 那些 (定義在頭文件內(nèi)的) 函數(shù)。這樣做,編譯器會避免為樁函數(shù) (stub) 的調(diào)用生成 任何代碼,產(chǎn)生的結(jié)果是相同的,但邏輯將更加清晰。

最好傾向于編譯整個函數(shù),而不是函數(shù)的一部分或表達(dá)式的一部分。與其放一個 ifdef 在表達(dá)式內(nèi),不如分解出部分或全部表達(dá)式,放進(jìn)一個單獨的輔助函數(shù),并應(yīng)用預(yù)處理 條件到這個輔助函數(shù)內(nèi)。

如果你有一個在特定配置中,可能變成未使用的函數(shù)或變量,編譯器會警告它定義了但 未使用,把它標(biāo)記為 __maybe_unused 而不是將它包含在一個預(yù)處理條件中。(然而,如 果一個函數(shù)或變量總是未使用,就直接刪除它。)

在代碼中,盡可能地使用 IS_ENABLED 宏來轉(zhuǎn)化某個 Kconfig 標(biāo)記為 C 的布爾 表達(dá)式,并在一般的 C 條件中使用它:

if?(IS_ENABLED(CONFIG_SOMETHING))?{
????????...
}

編譯器會做常量折疊,然后就像使用 #ifdef 那樣去包含或排除代碼塊,所以這不會帶 來任何運行時開銷。然而,這種方法依舊允許 C 編譯器查看塊內(nèi)的代碼,并檢查它的正 確性 (語法,類型,符號引用,等等)。因此,如果條件不滿足,代碼塊內(nèi)的引用符號就 不存在時,你還是必須去用 #ifdef。

在任何有意義的 #if 或 #ifdef 塊的末尾 (超過幾行的),在 #endif 同一行的后面寫下 注解,注釋這個條件表達(dá)式。例如:

#ifdef?CONFIG_SOMETHING
...
#endif?/*?CONFIG_SOMETHING?*/


—— The End?—

推薦好文 ??點擊藍(lán)色字體即可跳轉(zhuǎn)
?感覺身體被掏空!只因為肝了這篇空間矢量控制算法
?當(dāng)心!別再被大小端的問題坑了
?PID微分器與濾波器的愛恨情仇
?簡易PID算法的快速掃盲
?增量式PID到底是什么?
?三面大疆慘敗,因為不懂PID的積分抗飽和

原創(chuàng)不易,歡迎轉(zhuǎn)發(fā)、留言、點贊、分享給你的朋友,感謝您的支持!


長按識別二維碼關(guān)注獲取更多內(nèi)容



免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉