這是一份簡短的,描述linux內核首選編碼風格的文檔。編碼風格是很個人化的東西,而且我也不愿意把我的觀點強加給任何人,不過這里所講述的是我必須要維護的代碼所遵守的風格,并且我也希望絕大多數其他代碼也能遵守這個風格。所以請至少考慮一下本文所述的觀點。(轉載) 首先,我建議你打印一份GNU的編碼規范,然后不要讀它。燒掉它,這是一個很高調的具有象征意義的姿態。 Anyway, here goes: 第一章:縮進 制表符是8個字符,所以縮進也是8個字符。有些異端運動試圖將縮進變為4(乃至2)個字符深,這跟嘗試著將圓周率PI的值定義為3沒什么兩樣。 理由:縮進的全部意義就在于清楚的定義一個控制塊起止于何處。尤其是當你盯著你的屏幕連續看了20小時之后,你將會發現大一點的縮進將會使你更容易分辨縮進。 現在,有些人會抱怨8個字符的縮進會使代碼向右邊移動的太遠,在80個字符的終端屏幕上就很難讀這樣的代碼。這個問題的答案是,如果你需要3級以上的縮進,不管縮進深度如何你的代碼已經有問題了,應該修正你的程序。 簡而言之,8個字符的縮進可以讓代碼更容易閱讀,還有一個好處是當你的函數嵌套太深的時候可以向你提出告警。請留意這個警告。 在switch語句中消除多級縮進的首選的方式是讓“switch”和從屬于它的“case”標簽對齊于同一列,而不要“兩次縮進”“case”標簽。比如: 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; 也不要在一行里放多個賦值語句。內核編碼風格超級簡單。就是請避免使用怪異的表達式。除了注釋、文檔和Kconfig之外,不要使用空格來縮進,前面的例子是例外,是有意為之。 選用一個好的編輯器,不要在行尾留空格。 第二章:把長的行和字符串打散 編碼風格的意義就在于使用平常使用的工具來維持代碼的可讀性和可維護性。 每一行的長度的限制是80列,我們強烈建議您遵守這個慣例。 長于80列的語句要打散成有意義的片段。每個片段要明顯短于原來的語句,而且放置的位置也明顯的靠右。同樣的規則也適用于有很長參數列表的函數頭。長字符串也要打散成較短的字符串。唯一的例外是超過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語言風格中另外一個常見問題是大括號的放置。和縮進大小不同,選擇或棄用某種放置策略并沒有多少技術上的原因,不過首選的方式,就像Kernighan和Ritchie展示給我們的,是把起始大括號放在行尾,而把結束大括號放在行首,所以: if (x is true) { we do y } 這適用于所有的非函數語句塊(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; } 不過,有一種特殊情況,命名函數:它們的起始大括號放置于下一行的開頭,這樣: int function(int x) { body of function } 全世界的異端可能會抱怨這個不一致性,呃…確實是不一致的,不過所有思維健全的人都知道(a)K&R是 正確的, 并且(b)K&R是正確的。另外,不管怎樣函數都是特殊的(在C語言中,函數是不能嵌套的)。 注意結束大括號獨自占據一行,除非它后面跟著同一個語句的剩余部分,比如說do語句中的“while”或者if語句中的“else”,像這樣: do { body of do-loop } while (condition); 和 if (x == y) { .. } else if (x > y) { ... } else { .... } 理由:K&R。也請注意這種大括號的放置方式還能使空(或者差不多空的)行的數量最小化,同時不失可讀性。因此,由于你的屏幕上的新行的供應不是可回收的資源(想想25行的終端屏幕),你將會有更多的空行來放置注釋。 僅有一個單獨的語句時,不用加不必要的大括號。 if (condition) action(); 這點不適用于本身為某個條件語句的一個分支的單獨語句。這時應該兩個分支里都使用大括號。 if (condition) { do_this(); do_that(); } else { otherwise(); } 3.1:空格 Linux內核的空格使用方格(主要)取決于它是用于函數還是關鍵字。(大多數)關鍵字后要加一個空格。值得注意的例外是sizeof、typeof、alignof和__attribute__,這些關鍵字在一定程度上看起來更像函數(它們在Linux里也常常伴隨小括號使用,盡管在C語言里這樣的小括號不是必需的,就像“struct fileinfo info”聲明過后的“sizeof info”) 所以在這些關鍵字之后放一個空格: if, switch, case, for, do, while 但是不在sizeof、typeof、alignof或者__attribute__這些關鍵字之后放空格。例如, s = sizeof(struct file); 不要在小括號里的表達式兩側加空格。這是一個反例: s = sizeof( struct file ); 當聲明指針類型或者返回指針類型的函數時,“*”的首選使用方式是使之靠近變量名或者函數名,而不是靠近類型名。例子: char *linux_banner; unsigned long long memparse(char *ptr, char **retptr); char *match_strdup(substring_t *s); 在大多數二元和三元操作符兩側使用一個空格,例如下面所有這些操作符: = + - < > * / % | & ^ <= >= == != ? : 但是一元操作符后不要加空格: & * + - ~ ! sizeof typeof alignof __attribute__ defined 后綴自增和自減一元操作符前不加空格: ++ -- 前綴自增和自減一元操作符后不加空格: ++ -- “.”和“->”結構體成員操作符前后不加空格。 不要在行尾留空白。有些可以自動縮進的編輯器會在新行的行首加入適量的空白,然后你就可以直接在那一行輸入代碼。不過假如你最后沒有在那一行輸入代碼,有些編輯器就不會移除已經加入的空白,就像你故意留下一個只有空白的行。包含行尾空白的行就這樣產生了。 當Git發現補丁包含了行尾空白的時候會警告你,并且可以應你的要求去掉行尾空白;不過如果你是正在打一系列補丁,這樣做會導致后面的補丁失敗,因為你改變了補丁的上下文。 第四章:命名 C 是一個簡樸的語言,你的命名也應該這樣。和Modula-2和Pascal程序員不同,C程序員不使用類似ThisVariableIsATemporaryCounter這樣華麗的名字。C程序員會稱那個變量為“tmp”,這樣寫起來會更容易,而且至少不會令其難于理解。 不過,雖然混用大小寫的名字是不提倡使用的,但是全局變量還是需要一個具描述性的名字。稱一個全局函數為“foo”是一個難以饒恕的錯誤。 全局變量(只有當你真正需要它們的時候再用它)需要有一個具描述性的名字,就像全局函數。如果你有一個可以計算活動用戶數量的函數,你應該叫它“count_active_users()”或者類似的名字,你不應該叫它“cntuser()”。 在函數名中包含函數類型(所謂的匈牙利命名法)是腦子出了問題——編譯器知道那些類型而且能夠檢查那些類型,這樣做只能把程序員弄糊涂了。難怪微軟總是制造出有問題的程序。 本地變量名應該簡短,而且能夠表達相關的含義。如果你有一些隨機的整數型的循環計數器,它應該被稱為“i”。叫它“loop_counter”并無益處,如果它沒有可能被誤解的話。類似的“tmp”可以用來稱呼任意類型的臨時變量。 如果你怕混淆了你的本地變量名,你就遇到另一個問題了,叫做函數增長荷爾蒙失衡綜合癥。請看第六章(函數)。 第五章:Typedef 不要使用類似“vps_t”之類的東西。 對結構體和指針使用typedef是一個錯誤。當你在代碼里看到: vps_t a; 這代表什么意思呢? 相反,如果是這樣 struct virtual_container *a; 你就知道“a”是什么了。 很多人認為typedef“能提高可讀性”。實際不是這樣的。它們只在下列情況下有用: (a) 完全不透明的對象(這種情況下要主動使用typedef來隱藏這個對象實際上是什么)。 例如:“pte_t”等不透明對象,你只能用合適的訪問函數來訪問它們。 注意!不透明性和“訪問函數本身”是不好的。我們使用pte_t等類型的原因在于真的是完全沒有任何共用的可訪問信息。 (b) 清楚的整數類型,這樣抽象層就可以幫助我們消除到底是"int"還是"long"的混淆。 u8/u16/u32是完全沒有問題的typedef,不過它們更符合(d)中所言,而不是這里。再次注意!要這樣做,必須事出有因。如果某個變量是“unsigned long“,那么沒有必要 typedef unsigned long myflags_t; 不過如果有一個明確的原因,比如它在某種情況下可能會是一個“unsigned int”而在其他情況下可能為“unsigned long”,那么就不要猶豫,請務必使用typedef。 (c) 當你使用sparse按字面的創建一個新類型來做類型檢查的時候。 (d) 和標準C99類型相同的類型,在某些例外的情況下。 雖然讓眼睛和腦筋來適應新的標準類型比如“uint32_t”不需要花很多時間,可以有些人仍然拒絕使用它們。 因此,Linux特有的等同于標準類型的“u8/u16/u32/u64”類型和它們的有符號類型是被允許的——盡管在你自己的新代碼中,它們不是強制要求要使用的。 當編輯已經使用了某個類型集的已有代碼時,你應該遵循那些代碼中已經做出的選擇。 (e) 可以在用戶空間安全使用的類型。 在某些用戶空間可見的結構體里,我們不能要求C99類型而且不能用上面提到的“u32”類型。因此,我們在與用戶空間共享的所有結構體中使用__u32和類似的類型。 可能還有其他的情況,不過基本的規則是永遠不要使用typedef,除非你可以明確的應用上述某個規則中的一個。 總的來說,如果一個指針或者一個結構體里的元素可以合理的被直接訪問到,那么它們就不應該是一個typedef。 第六章:函數 函數應該簡短而漂亮,并且只完成一件事情。函數應該可以一屏或者兩屏顯示完(我們都知道ISO/ANSI屏幕大小是80x24),只做一件事情,而且把它做好。 一個函數的最大長度是和該函數的復雜度和縮進級數成反比的。所以,如果你有一個理論上很簡單的只有一個很長(但是簡單)的case語句的函數,而且你需要在每個case里做很多很小的事情,這樣的函數盡管很長,但也是可以的。 不過,如果你有一個復雜的函數,而且你懷疑一個天分不是很高的高中一年級學生可能甚至搞不清楚這個函數的目的,你應該更嚴格的遵守最大限制。使用輔助函數,并為之取個具描述性的名字(如果你覺得其對性能要求嚴格的話,你可以要求編譯器將它們內聯展開,它往往會比你更好的完成任務。) 函數的另外一個衡量標準是本地變量的數量。此數量不應超過5-10個,否則你的函數就有問題了。重新考慮一下你的函數,把它分拆成更小的函數。人的大腦一般可以輕松的同時跟蹤7個不同的事物,如果再增多的話,就會糊涂了。即便你聰穎過人,你也可能會記不清你2個星期前做過的事情。 在源文件里,使用空行隔開不同的函數。如果該函數需要被導出,它的EXPORT*宏應該緊貼在它的結束大括號之下。比如: int system_is_up(void) { return system_state == SYSTEM_RUNNING; } EXPORT_SYMBOL(system_is_up); 在函數原型中,包含函數名和它們的數據類型。雖然C語言里沒有這樣的要求,在Linux里這是提倡的做法,因為這樣可以很簡單的給讀者提供更多的有價值的信息。 第七章:集中的函數退出途徑 雖然被某些人聲稱已經過時,但是goto語句的等價物還是經常被編譯器所使用,具體形式是無條件跳轉指令。當一個函數從多個位置退出并且需要做一些通用的清潔工作的時候,goto的好處就顯現出來了。 理由是: - 無條件語句容易理解和跟蹤 - 嵌套程度減小 - 可以避免由于修改時忘記更新某個單獨的退出點而導致的錯誤 - 減輕了編譯器的工作,無需刪除冗余代碼;) 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; } 第八章:注釋 注釋是好的,不過有過度注釋的危險。永遠不要在注釋里解釋你的代碼是如何運作的:更好的做法是讓別人一看你的代碼就可以明白,解釋寫的很差的代碼是浪費時間。 一般的,你想要你的注釋告訴別人你的代碼做了什么,而不是怎么做的。也請你不要把注釋放在一個函數體內部:如果函數復雜到你需要獨立的注釋其中的一部分,你很可能需要回到第六章看一看。你可以做一些小注釋來注明或警告某些很聰明(或者槽糕)的做法,但不要加太多。你應該做的,是把注釋放在函數的頭部,告訴人們它做了什么,也可以加上它做這些事情的原因。 當注釋內核API函數時,請使用kernel-doc格式。請看 Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc以獲得詳細信息。 Linux的注釋風格是C89“/* ... */”風格。不要使用C99風格“// ...”注釋。 長(多行)的首選注釋風格是: /* * 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. */ 注釋數據也是很重要的,不管是基本類型還是衍生類型。為了方便實現這一點,每一行應只聲明一個數據(不要使用逗號來一次聲明多個數據)。這樣你就有空間來為每個數據寫一段小注釋來解釋它們的用途了。 第九章:你已經把事情弄糟了 這沒什么,我們都是這樣。可能你的使用了很長時間Unix的朋友已經告訴你“GNU emacs”能自動幫你格式化C源代碼,而且你也注意到了,確實是這樣,不過它所使用的默認值和我們想要的相去甚遠(實際上,甚至比隨機打的還要差——無數個猴子在GNU emacs里打字永遠不會創造出一個好程序)(譯注:請參考Infinite Monkey Theorem) 所以你要么放棄GNU emacs,要么改變它讓它使用更合理的設定。要采用后一個方案,你可以把下面這段粘貼到你的.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命令。當你hack一個模塊的時候,如果你把字符串 -*- linux-c -*-放在頭兩行的某個位置,這個模式將會被自動調用。如果你希望在你修改 /usr/src/linux里的文件時魔術般自動打開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一樣有問題的設定,所以你需要給它一些命令選項。不過,這還不算太糟糕,因為就算是GNU indent的作者也認同K&R的權威性(GNU的人并不是壞人,他們只是在這個問題上被嚴重的誤導了),所以你只要給indent指定選項“- kr -i8”(代表“K&R,8個字符縮進”),或者使用“scripts/Lindent”,這樣就可以以最時髦的方式縮進源代碼。“indent”有很多選項,特別是重新格式化注釋的時候,你可能需要看一下它的手冊頁。不過記住:“indent”不能修正壞的編程習慣。如想參加系統學習可聯系郭老師QQ754634522 第十章:Kconfig配置文件 對于遍布源碼樹的所有Kconfig*配置文件來說,它們縮進方式與C代碼相比有所不同。緊挨在“config”定義下面的行縮進一個制表符,幫助信息則再多縮進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. 仍然被認為不夠穩定的功能應該被定義為依賴于“EXPERIMENTAL”: config SLUB depends on EXPERIMENTAL && !ARCH_USES_SLAB_PAGE_STRUCT bool "SLUB (Unqueued Allocator)" ... 而那些危險的功能(比如某些文件系統的寫支持)應該在它們的提示字符串里顯著的聲明這一點: config ADFS_FS_RW bool "ADFS write support (DANGEROUS)" depends on ADFS_FS ... 要查看配置文件的完整文檔,請看Documentation/kbuild/kconfig-language.txt。 更多嵌入式學習可以群聊:305711544 |