Linux內核編碼規範總結

聲明:如果涉及侵權,請聯繫本人刪除侵權內容。
聲明:本文由本人以以往工作經驗爲依據,總結而得,如果錯誤,歡迎指正,便於後人參考,少走彎路。   

翻譯於:https://www.kernel.org/doc/html/latest/process/coding-style.html#indentation

  對於編碼風格,着實體驗了一把邯鄲學步的感覺。起初的代碼風格來源於教程,或者說沒有代碼風格。後來,就職於不同的公司,發現公司不同,代碼編寫規範各異。尤其是對於代碼風格沒有強制要求的公司,部門內部每個人都有自己的風格,見到優秀的學習一下,最後漸漸的發現自己的代碼編寫規範一直在變,到後來自己看自己之前寫的代碼都覺得彆扭。所以查看了linux內核的編碼規範,用於規範自己的代碼風格。

1. 縮進(Indentation)

  • 縮進使用"Tab"鍵,固定八個字符;

  • 代碼不要超過三層縮進;

  • switch語句中:

    • "switch"和"case"關鍵字左對齊;
    • 分支指令一層縮進;
    • "break"關鍵字與分支指令對齊(不是與"case"關鍵字左對齊);
          switch (suffix) {
          case 'G':
          case 'g':
                  mem <<= 30;
                  break;
          case 'M':
          case 'm':
                  mem <<= 20;
                  break;
          case 'K':
          case 'k':
                  mem <<= 10;
                  fallthrough;
          default:
                  break;
          }
  • 單獨一行裏不要寫多個語句,包括不要把多個賦值語句放在同一行;

  • 內核的代碼風格是十分簡潔的,請儘量避免使用複雜的表達式;

  • 除了在註釋、文檔和Kconfig中,不要使用空格作爲縮進;

  • 不要在行末留有空格

2. 換行(Breaking long lines and strings)

  • 規範代碼風格的目的是提高代碼的可讀性維護性

  • 強烈推薦單行的寬度限制爲八十列

  • 拆分超過八十列寬度的語句爲多行:

    • 如果超過八十列的部分可以提高可讀性且不會隱藏信息時可以不拆分
    • 適用範圍:寬度超過八十列的語句或有很長參數列表的函數頭;
    • 拆分出的子句長度應該比主句
    • 拆分出的子句儘量靠右
    • 不要拆分用戶可見的字符串(如"printk"的信息,否則會導致使用"grep"時找不到這些信息);
  • 疑問:
    • 在編輯器中怎麼限制(提醒)單行的寬度爲"八十列"?
    • "不會隱藏信息時"指什麼?
    • "用戶可見的字符串"指什麼?
    • "用戶可見的字符串"出現了"隱藏信息時"怎麼處理?

3. 花括號和空格(Placing Braces and Spaces)

3.1. 花括號(大括號)

  • 把左括號放在行末,右括號放在行首;

  • 適用範圍:非函數的語句塊(if,switch,for,while,do);

  • 函數的左括號應該放在行首;

  • 右括號一般單獨成一行,除非右括號之後有緊密結合的語句(如:do-while,if-else if-else等);

        //情況一:左括號放在行末,右括號放在行首
        if (x is true) {
                we do y
        }
    
        //情況二:左括號放在行末,右括號放在行首
        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
        }
    
        //情況四:右括號之後有緊密結合的語句
        do {
                body of do-loop
        } while (condition);
    
        //情況五:右括號之後有緊密結合的語句
        if (x == y) {
                ..
        } else if (x > y) {
                ...
        } else {
                ....
        }
  • 在不降低可讀性的前提下儘可能減少空行的數量;

    • 單行語句可以完成任務時不要使用括號;
    • 其他"case"有多行的情況下,所有"case"都要使用括號;
    • 循環中超過一條語句需要使用括號;
        //情況一:單行語句可以完成任務時不要使用括號
        if (condition)
            action();
    
        //情況二:單行語句可以完成任務時不要使用括號
        if (condition)
            do_this();
        else
            do_that();
    
        //情況三:其他"case"有多行的情況下,所有"case"都要使用括號
        if (condition) {
                do_this();
                do_that();
        } else {
                otherwise();
        }
    
        //情況四:循環中超過一條語句需要使用括號
        while (condition) {
                if (test)
                        do_something();
        }

3.2. 空格

  • Linux內核風格習慣在一些關鍵字之後添加一個空格

  • 使用方式類似函數的關鍵字使用不帶空格的一對小括號;

  • 使用方式類似函數的關鍵字:sizeof, typeof, alignof, __attribute__等等;

  • 需要添加空格的關鍵子:if, switch, case, for, do, while;

  • 不要在小括號內部周圍添加空格(左括號右邊,右括號左邊);

  • 指針聲明或函數返回爲指針時,星號緊靠變量名或函數名,不是類型名;

  • 一元操作符右邊不添加空格;

  • 自增自減一元操作符,後綴前不加空格,前綴後不加空格;

  • 結構體成員操作符周圍不加空格;

  • 二元操作符左右添加空格;

  • 三元操作符周圍添加空格;

  • 逗號操作符左邊不添加空格,右邊添加空格;

  • for語法中分號左邊不添加空格,右邊添加空格;

  • 不要在行末添加空格(某些編輯器會在新的行首添加一些空格來表示縮進,但是想保留一行空行時,這些由空格表示的縮進可能沒有刪除,導致看起來像是在上一行行末添加了多餘的空格);

        //情況一:需要添加空格的關鍵子
        //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
    
        //情況六:自增自減一元操作符,後綴前不加空格,前綴後不加空格;
        i++;
        i--;
        --i;
        ++i;
    
        //情況七:結構體成員操作符周圍不加空格
        student.age;
        pstudent->age;
    
        //情況八:二元操作符左右添加空格
        //=  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=
    
        //情況九:三元操作符周圍添加空格
        //?  :
    
        //情況十:逗號操作符左邊不添加空格,右邊添加空格
        //arg1, arg2, arg3
    
        //情況十一:for語法中分號左邊不添加空格,右邊添加空格
        for (i = 0; i < Cnt; i++) {

4. 命名(Naming)

  • C語言是一種簡潔的語言,建議用tmp代替VariableIsATemporaryCounter

  • 全局變量或全局函數可以使用混合大小寫的描述性名字,使用count_active_users()代替cntusr()

  • 編譯器會檢查函數類型,所以不建議寫一個包含函數類型的名字(匈牙利命名法);

  • 局部變量名應該簡潔;

    • 可以用i來定義一個計數器;
    • 可以用tmp來定義任何類型的臨時變量;
  • 備註:
    • 列舉常用的命名情況和特例!

5. 定義類型(Typedefs)

  • 代碼vps_t a;中的vps_t降低了代碼的可讀性,struct virtual_container *a;更易於理解代碼,是typedef的錯誤使用方式;

  • typedef僅有的用法:

    • 隱藏對象,封裝作用(內核不建議使用封裝和成員函數的思想);

    • 抽象整數類型,避免混淆它是int型還是long型;

      • 典型應用:u8/u16/u32;
    • 用"sparse"創建用於類型檢查的新類型;

    • 某些情況下使用與標準C99相同的新型

      • 標準C99類型:uint32_t(一些人反對使用);
      • Linux特有類型:u8/u16/u32/u64;
      • 兩種類型不強制,但是選定一種之後就應該一直使用,不建議混用;
    • 用戶空間的類型安全

      • 用戶空間的某些結構體可能不能使用標準C99類型和Linux特有類型,而使用"__u32"這類的類型;
  • 如果不滿足typedef的五種用法,就不建議使用typedef;

  • 指針或有可訪問元素的結構體不應該使用"typedef";

6. 函數(Functions)

  • 一個函數只做一件事;

  • 函數長度建議一到兩屏幕(ISO/ANSI的屏幕尺寸是80x24);

  • 函數的最大長度與該函數的複雜性和縮進程度成反比;

  • 如果由於"case"太多導致的函數過長是可以理解的;

  • 使用一些有描述性名稱的輔助函數;

  • 函數的性能至關重要時,可以使用內聯;

  • 函數的局部變量的數量不應該超過5-10個(人類的大腦通常可以很容易地跟蹤大約7件不同的事情);

  • 用一個空行分隔函數;

  • 如果函數被導出,導出宏應該緊跟在結束函數的大括號後面

        int system_is_up(void)
        {
                return system_state == SYSTEM_RUNNING;
        }
        EXPORT_SYMBOL(system_is_up);
  • 在函數原型中,包含參數名稱及其數據類型;

  • 不要在函數原型中使用extern關鍵字,因爲這會使行更長;

  • 疑問:
    • "不要在函數原型中使用extern關鍵字"怎麼理解?
    • 那什麼時候使用該關鍵字?
    • 頭文件中使用可以建議嗎?

7. 集中函數出口(Centralized exiting of functions)

  • 很多人反對使用goto,但是編譯器經常編譯出無條件跳轉指令,相當於使用goto;

  • 函數有多個退出點,且多於一個退出點要執行相同的復位、清理等操作時,建議使用goto;

  • goto使用的標籤名應具有描述性,如釋放緩存使用out_free_buffer:

  • 使用"GW-BASIC名稱"方式("err1:","err2:"等)會在需要刪除某個標籤時造成需要重新排序的問題;

  • 使用gotos的原由:

    • 無條件跳轉更好理解;
    • 減少嵌套;
    • 避免修改某個函數退出點而造成錯誤;
    • 優化編譯器的工作;
        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;
        }
  • 使用goto,常會出現稱爲:"one err bugs"的bug用法:

        err:
                kfree(foo->bar);
                kfree(foo);
                return ret;
  • bug在於可能要執行的kfree中的foo是NULL,解決方法是使用兩個標籤:

        err_free_bar:
                kfree(foo->bar);
        err_free_foo:
                kfree(foo);
                return ret;
  • 應該模擬錯誤來測試所有退出路徑;

  • 疑問:
    • goto語句多個標籤,跳到前面的標籤,且執行完標籤未退出,後面的標籤會不會被執行?
    • GW-BASIC 的命名方式?

8. 註釋(Commenting)

  • 註釋是好習慣;

  • 不要過度註釋;

  • 不要用註釋來解釋代碼如何工作,最好是讓代碼的工作顯而易見;

  • 註釋是解釋代碼做了什麼,而不是如何做;

  • 儘量避免在函數體內寫註釋,如果需要這樣做,說明函數應該拆分了;

  • 應該把註釋寫在函數開頭;

  • 可以做一些評論來提醒或警告代碼的優點或缺點,但是不能過分;

  • 註釋內核API函數時,請使用內核文檔格式,請參閱kernel-doc

  • 長(多行)註釋的首選格式是:

        /*
        * 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/中的文件,長(多行)註釋的首選樣式略有不同:

        /* 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.
        */
  • 建議對數據(基本類型或派生類型)進行註釋;

  • 建議每行只使用一個數據聲明(多個數據聲明不使用逗號);

  • 疑問:
    • "kernel API"指什麼?

9. 事情搞得一團糟(You’ve made a mess of it)

  • "GNU emacs"能自動對C代碼排版,但是默認的排版方式不是很好;

  • 可以修改.emacs文件(~/src/linux-trees)達到想要的效果:

        (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)))
    
        (dir-locals-set-class-variables
        'linux-kernel
        '((c-mode . (
                (c-basic-offset . 8)
                (c-label-minimum-indentation . 0)
                (c-offsets-alist . (
                        (arglist-close         . c-lineup-arglist-tabs-only)
                        (arglist-cont-nonempty .
                            (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only))
                        (arglist-intro         . +)
                        (brace-list-intro      . +)
                        (c                     . c-lineup-C-comments)
                        (case-label            . 0)
                        (comment-intro         . c-lineup-comment)
                        (cpp-define-intro      . +)
                        (cpp-macro             . -1000)
                        (cpp-macro-cont        . +)
                        (defun-block-intro     . +)
                        (else-clause           . 0)
                        (func-decl-cont        . +)
                        (inclass               . +)
                        (inher-cont            . c-lineup-multi-inher)
                        (knr-argdecl-intro     . 0)
                        (label                 . -1000)
                        (statement             . 0)
                        (statement-block-intro . +)
                        (statement-case-intro  . +)
                        (statement-cont        . +)
                        (substatement          . +)
                        ))
                (indent-tabs-mode . t)
                (show-trailing-whitespace . t)
                ))))
    
        (dir-locals-set-directory-class
        (expand-file-name "~/src/linux-trees")
        'linux-kernel)
  • 可以不使用"GNU emacs",使用"GNU indent";

  • 使用"GNU indent"需要一些命令行選項來進行配置;

    • "-kr -i8"表示K&R,8個字符寬的縮進;
    • 或者使用 scripts/Lindent;
  • indent 有很多命令行選項,可通過man手冊查看;

  • 可以使用clang-format工具

    • 自動快速地重新格式化代碼的某些部分;
    • 檢查完整的文件;
    • 發現編碼樣式錯誤、打字錯誤和可能的改進
    • 對#include進行排序;
    • 調整變量/宏、返回文本
    • 參考:clang-format
  • 疑問:
    • "GNU emacs"?
    • "GNU indent"?
    • "clang-format"?

10. Kconfig配置文件(Kconfig configuration files)

  • Linux中的Kconfig配置文件的縮進有所不同

    • config 定義下的縮進是一個 tab;
    • help 文本是兩個空格;
        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.
  • 嚴重危險的特性(比如對某些文件系統的寫支持)應該在提示字符串中突出顯示這一點,

  • 參見:Documentation/kbuild/kconfig-language.rst

        config ADFS_FS_RW
                bool "ADFS write support (DANGEROUS)"
                depends on ADFS_FS
                ...
  • 疑問:
    • "嚴重危險的特性"在說什麼?

11. 數據結構(Data structures)

  • 單線程環境下創建和銷燬的,在線程之外可見的數據結構必須有引用計數;

  • 內核裏沒有垃圾收集器,內核外垃圾收集器慢且低效;

  • 引用計數可以避免死鎖;

  • 引用計數可以使多用戶並行訪問數據;

  • 引用計數可以避免線程休眠喚醒(或其他情況)之後數據已銷燬現象;

  • 鎖不是引用計數的替代品;

  • 鎖用於保持數據的一致性;

  • 引用計數是一種內存管理技術;

  • 通常鎖和引用計數都需要;

  • 當用戶屬於不同的類時,數據結構可以有兩個級別的引用計數;

  • 子類計數爲零是纔對父類計數遞減;

  • 多級引用計數的使用:

    • 內存管理(struct mm_struct: mm_users and mm_count)
    • 文件系統(struct super_block: s_count and s_active)
  • 其他線程可以訪問你的數據結構,而你沒有對其進行引用計數,那麼你的數據結構使用方式存在bug;

  • 疑問:
    • "用戶屬於不同的類時"指什麼?
    • "子類引用計數和父類引用計數(二級引用和一級引用)"?

12. 宏,枚舉與RTL(Macros, Enums and RTL)

  • 宏名和枚舉中的標籤都要大寫;

  • 定義相關常量時優先使用枚舉;

  • 函數宏(使用宏代表定義函數)可以小寫;

  • 通常建議把內聯函數定義爲宏;

  • 包含多個語句的宏應該包含在"do - while"塊中;

    #define macrofun(a, b, c)                       \
            do {                                    \
                    if (a == 5)                     \
                            do_this(b, c);          \
            } while (0)
  • 應該避免的情況:

        //情況一:影響控制流的宏
        //導致調用者返回到上一層
        #define FOO(x)                                  \
                do {                                    \
                        if (blah(x) < 0)                \
                                return -EBUGGERED;      \
                } while (0)
    
        //情況二:依賴局部變量的宏
        //容易讓人困惑,小改動造成破壞後果
        #define FOO(val) bar(index, val)
    
        //情況三:帶參數的宏當作左值
        //FOO作爲內聯函數會導致錯誤
        FOO(x) = y;
    
        //情況四:忘記優先級
        //使用表達式定義常量的宏必須將表達式括在括號中
        //使用參數的宏也會出現類似的問題
        #define CONSTANT 0x4000
        #define CONSTEXP (CONSTANT | 3)
    
        //情況五:在函數宏中定義的局部變量存在命名空間衝突:
        // ret 很容易衝突
        // __foo_ret不容易衝突
        #define FOO(x)                          \
        ({                                      \
                typeof(x) ret;                  \
                ret = calc_ret(x);              \
                (ret);                          \
        })
  • C++ 手冊全面地闡述了宏定義的細節;

  • gcc 手冊闡述了彙編語言使用的 RTL 規則;

  • 備註:
    • RTL(Real Time Linux)
  • 疑問:
    • 爲什麼使用"依賴局部變量的宏"不好?上面的解釋沒看明白?
    • 爲什麼使用"帶參數的宏當作左值"不好?上面的解釋沒看明白?
    • "使用參數的宏也會出現類似的問題"的優先級問題怎麼理解?

13. 打印內核信息(Printing kernel messages)

  • 注意內核消息的拼寫,提示信息應簡潔、清晰、明確,比如用do not或don`t代替dont;

  • 內核信息末尾不加句號;

  • 不要在圓括號中打印數字:(%d);

  • 在linux/device.h中有許多驅動模型診斷宏

  • 應該使用診斷宏來確保消息匹配到正確的設備和驅動程序

  • 使用正確的級別標記

    • dev_err()
    • dev_warn()
    • dev_info()
    • and so forth
  • 對於沒有關聯特定設備的消息,使用linux/printk.h中的定義:

    • pr_notice()
    • pr_info()
    • pr_warn()
    • pr_err()
    • etc
  • 編寫好的調式信息不是一件簡單的事情;

  • 調式信息對遠程故障排除有很大幫助;

  • pr_XXX()函數無條件輸出;

  • pr_debug()和dev_dbg()默認不輸出,與調試有關的函數默認不會被編譯;

  • 可以定義DEBUG宏或CONFIG_DYNAMIC_DEBUG宏來編譯調式信息;

  • 可以使用VERBOSE_DEBUG爲已經開啓DEBUG的用戶添加dev_vdbg()消息;

  • 很多子系統的makefile裏有Kconfig調試選項(-DDEBUG);

  • 可以在文件裏定義宏#define DEBUG;

  • 當調試信息可以打印,printk(KERN_DEBUG ...) 可以用來打印調試信息;

  • 疑問:
    • 爲什麼內核信息末尾不加句號?
    • 驅動模型診斷宏?
    • 沒有關聯特定設備的消息?
    • pr_debug、dev_dbg、dev_vdbg?
    • printk(KERN_DEBUG ...)?

14. 內存分配(Allocating memory)

  • 內核中通用的內存分配器: kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc(), and vzalloc();

  • 爲結構體分配內存建議方式:p = kmalloc(sizeof(*p), ...);

  • sizeof(struct name)這種分配內存的方式會破壞代碼可讀性,且容易產生bug(結構體變量的結構體類型改變時可能忘記修改這裏的結構體名);

  • 不要在malloc之前添加強制類型轉換(把空類型的指針轉換爲特定類型的指針),編譯器會做這個工作;

  • 爲數組分配內存建議方式:p = kmalloc_array(n, sizeof(...), ...);

  • 爲零數組分配內存建議方式:p = kcalloc(n, sizeof(...), ...);

  • 以上兩種形式都會檢查溢出,並且溢出發生時返回一個空指針 NULL;

  • 當沒有剩餘的__GFP_NOWARN使用時,這些通用分配函數都會在失敗時發出堆棧轉儲,所以當返回NULL時,沒有必要發出額外的失敗消息;

  • 疑問:
    • 零數組?
    • __GFP_NOWARN?
    • 發生了"堆棧轉儲"爲什麼就不需要額外的失敗消息了?"堆棧轉儲"會打印信息?

15. 內聯之疾(The inline disease)

  • 內聯讓程序跑得更快;

  • 過多的inline關鍵字會使內核變大;

  • 過多的內聯使整個系統運行速度變慢;

    • 大內核會佔用更多的CPU高速緩存;
    • 大內核會導致可用內存頁緩存減少;
    • 大內核對導致更多的cache miss;
    • cache miss 時需要訪問磁盤,至少5ms,佔用了很多指令時間;
  • 函數有三行以上的代碼不建議用內聯;

  • 編譯時常量參數導致在編譯器編譯之後能優化大部分代碼,建議使用內聯(例如kmalloc());

  • 只使用一次的靜態函數不需要手動添加inline關鍵字,gcc編譯器會自動添加;

  • 不給只使用一次的靜態函數添加inline關鍵字,可以避免該函數如果被使用兩次時需要手動刪除inline關鍵字;

16. 函數返回值和名稱(Function return values and names)

  • 函數可以返回多種類型的值;

  • 函數常見返回值是:表明函數是否成功執行

    • 可以是錯誤代碼(0:成功,Exxx:失敗)
    • 可以是布爾值(0:失敗,非0:成功)
  • 不建議函數返回值混合使用上述兩種方式,應滿足以下規則:

    • 如果函數名是動作或命令,應該返回錯誤代碼;
    • 如果函數名是謂詞(判斷),應該返回布爾值;
  • 所有(export)導出函數必須遵守這一約定;

  • 所有公共函數必須遵守這一約定;

  • 私有(靜態)函數不需要遵守,但建議遵守;

  • 如果返回值表示計算的實際結果,不是表示計算是否成功,則不遵守此規則;

  • 返回值表示計算結果的時候,通過返回超出範圍的結果來指示失敗;

  • 典型例子是返回指針的函數使用NULL或ERR_PTR機制來報告失敗;

  • 疑問:
    • 導出函數和公共函數的區別?
    • ERR_PTR機制

17. 使用布爾值(Using bool)

  • Linux內核bool類型是C99 _Bool類型的別名;

  • bool變量可以使用的值只有:0和1;

  • 隱式或顯式的到bool類型的轉換會自動轉爲:true或false;

  • 不要自己構建bool類型;

  • 當使用bool值的時候,應該是true和flase,不應使用0或1;

  • 推薦使用bool類型作爲返回值而不是int類型;

  • bool的大小的對齊方式會根據編譯的體系結構發生變換;

  • 如果cache line和變量的尺寸比較重要時不建議使用bool;

  • 對於需要對大小和對齊優化的結構體不建議使用bool;

  • 如果一個結構體中有多個真假值,建議使用位字段或固定寬度類型(u8);

  • 如果一個函數參數中有多個真價值,建議使用位字段的flags;

  • 否則在結構和參數中使用bool可以提高可讀性;

18. 不要重新創建內核宏(Don’t re-invent the kernel macros)

  • 頭文件include/linux/kernel.h包含一些應該使用的宏;
    • 計算數組長的宏:#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
    • 計算結構體中某個成員的大小:#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
    • 類型檢查:min() 和 max()
  • 多查看該文件;

19. 編輯器的自定義配置(Editor modelines and other cruft)

  • 一些編輯器可以識別嵌入在源文件中的配置信息;

  • 配置信息需要使用特殊的標記表示;

  • 不要在源文件中包含這些配置信息,你的配置信息會影響到別人;

  • emacs 可以識別的標記

    -*- mode: c -*-
        /*
        Local Variables:
        compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
        End:
        */
  • vim可以解釋的標記:

    /* vim:set sw=8 noet */

     

20. 內聯彙編(Inline assembly)

  • 能用C語言乾的事不用匯編指令;

  • 操作某些特定體系結構可能需要內聯彙編;

  • 建議編寫輔助函數來封裝內聯彙編,避免重複在代碼中直接使用內聯彙編;

  • 內聯彙編也可以使用C函數的參數;

  • 大型的、重要的彙編函數應該單獨創建.S文件;

  • 大型的、重要的彙編函數應該有對應的C頭文件,且定義相應的C原型;

  • 相應的C原型應添加 asmlinkage 關鍵字

  • 有些彙編代碼需要添加關鍵字:volatile,防止編譯器優化刪除;

  • 對所有的彙編代碼添加volatile會影響優化;

  • 一條內聯彙編語句裏包含多條指令,每條指令使用帶引號的方式分行寫,除最後一行外其他行的行末添加\n\t進行縮進和對齊:

        asm ("magic %reg1, #42\n\t"
                "more_magic %reg2, %reg3"
                : /* outputs */ : /* inputs */ : /* clobbers */);
  • 疑問:
    • 內聯彙編如何使用C函數的參數?
    • asmlinkage 關鍵字?
    • 頭文件中叫聲明還是定義?
    • 哪些彙編代碼需要添加volatile關鍵字?添加該關鍵字的語法?
    • 上面的代碼怎麼理解?

21. 條件編譯(Conditional Compilation)

  • 不要在.c文件中使用條件編譯命令;

  • 將條件編譯命令放到.h文件中;

  • 在.h文件中,同樣的條件編譯條件只使用一次(該條件編譯下的函數全部一次性放於該添加下),不建議先放一部分再放一部分;

  • 某個函數或變量可能在特定配置中未使用,可以標記爲"__maybe_unused",而不是移到預編譯條件內;

  • 如果一個函數或變量總是不使用,刪除它;

  • 在代碼中,如果可能的話,使用IS_ENABLED宏將Kconfig符號轉換爲C布爾表達式,並在普通的C條件下使用:

        if (IS_ENABLED(CONFIG_SOMETHING)) {
                ...
        }
  • 編譯器會摺疊預編譯條件;

  • 編譯器會包含或排除代碼塊;

  • 預編譯不會增加代碼運行時開銷;

  • 編譯器會檢查所有預編譯分支下的代碼正確性(語法、類型、符號引用等);

  • 如果條件滿足的分支引用了條件不滿足下的符號,需要在該符號周圍繼續使用預編譯指令;

  • 不管嵌套與否,每個#endif默認應添加註釋:

        #ifdef CONFIG_SOMETHING
        ...
        #endif /* CONFIG_SOMETHING */
  • 疑問:
    • 如何在C代碼中使用Kconfig中的符號?

N. 附錄I 引用(Appendix I :References)

The C Programming Language, Second Edition by Brian W. Kernighan and Dennis M. Ritchie. Prentice Hall, Inc., 1988. ISBN 0-13-110362-8 (paperback), 0-13-110370-9 (hardback).

The Practice of Programming by Brian W. Kernighan and Rob Pike. Addison-Wesley, Inc., 1999. ISBN 0-201-61586-X.

GNU manuals - where in compliance with K&R and this text - for cpp, gcc, gcc internals and indent, all available from http://www.gnu.org/manual/

WG14 is the international standardization working group for the programming language C, URL: http://www.open-std.org/JTC1/SC22/WG14/

Kernel process/coding-style.rst, by [email protected] at OLS 2002: http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章