請善用工具審覈您的內核代碼:)

在寫內核代碼時,代碼風格(coding style)是一個很重要的部分,否則內核代碼將變的混亂不堪。那麼什麼樣的代碼算漂亮的代碼?什麼樣的代碼符合c99這樣的標準?此外,程序寫完之後,有什麼工具能夠幫我們檢查代碼有沒有指針錯誤?客官且隨我看看這三個工具:

1. 代碼風格篇

想開發一個內核程序?你的電腦有內核源碼麼?不管是曾經用來編譯內核或者你自己查閱資料,如果您的電腦上有內核源碼,好的,本節將介紹一個很多人都不知道的強大的工具 -- checkpatch。

So, where is it ? ok ,打開內核代碼,cd 到 “ scripts ”目錄下,查看有木有checkpatch.pl 文件?

How to use ? Yup, very easy ! Please use " patch-to-kernel/scripts/checkpatch.pl  --file yourcode.c " !

還不明白?來看看我怎麼用:

~/kernel/linux-3.12.1/scripts/checkpatch.pl --file ../net_drive/netdump.c

那麼這個工具有什麼好?請看下面這個代碼:

/*
 * file : netdump.c
 * (C) 2014 Yunlong Zhou <[email protected]>
 * Under licence  GPL
 *
 * Introduction :
 *      This modules will scan netdevices and report them via printk 
 *
 * Useage:  1. make                 -- you should make the module firstly(with Makefile )
 *          2. su                   -- use root 
 *          3. insmod netdump.ko    -- install the module
 *          4. dmesg | tail         -- check the status that module print!
 *          5. rmmod netdump        -- after use ,please don't forget rmmove the module
**/


#include <linux/module.h>   /* MODULE* */
#include <linux/kernel.h>   /* printk */
#include <linux/netdevice.h>    /* dev_get_by_index */


static int __init hello_init(void)
{
    printk("netscan module enter\n");
    struct net_device *dev;
    struct rtnl_link_stats64 temp;
    int idx=1;  /* first netdevice if it exists */


    do{
            dev = dev_get_by_index(&init_net,idx);
            if (dev==NULL) {
                    printk("Last netdevice index %d\n",idx-1);
            }
            else {
            const struct rtnl_link_stats64 *stats = dev_get_stats(dev,&temp);
            printk("%s: ifindex %d\n",dev->name,dev->ifindex);
            // more in this struct than reported here ! 
            printk("This is the current status jus get !\n");
            printk("packets:%llu/%llu bytes: %llu/%llu errors:%llu dropped:%llu\n\n",
                stats->tx_packets,
                stats->rx_packets,
                stats->tx_bytes,
                stats->rx_bytes,
                stats->rx_errors,
                stats->rx_dropped);
        }
        idx++;
    }while(dev!=NULL);


    return 0;
}

static void __exit hello_exit(void)
{
    printk("netscan module exit\n");
}


module_init(hello_init);
module_exit(hello_exit);


MODULE_AUTHOR("Zhou Yunlong <reaper888@yeah>");
MODULE_DESCRIPTION("scan netdevices and report them via printk");
MODULE_LICENSE("GPL");


寫過內核模塊的童鞋能輕易的分辯,這是個內核模塊。再有經驗的童鞋,能夠看出來這個模塊的主要工作都在 init 時做了(也即insmod 模塊時)。那麼做了什麼工作呢?其實很簡單,就是讀取網卡設備的狀態然後顯示出來,比如說發/收多少數據包,多少字節等。而且由於代碼圖簡便,通過 printk 輸出,所以信息只能通過 dmesg查看!

對於有經驗的童鞋,會在編譯模塊的Makefile 文件中添加 -Wall 標誌(W 即warning,all即所有,所以添加 -Wall 標誌位會打印出所有編譯時的警告)。對於這段代碼:

$ make
make -Wall -C /lib/modules/`uname -r`/build M=`pwd` modules
make[1]: Entering directory `/home/long/kernel/linux-3.12.1-rtpatched'
  CC [M]  /tmp/netdump.o
/tmp/netdump.c: In function ‘hello_init’:
/tmp/netdump.c:24:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /tmp/netdump.mod.o
  LD [M]  /tmp/netdump.ko
make[1]: Leaving directory `/home/long/kernel/linux-3.12.1-rtpatched'
$ sudo insmod netdump.ko
$ dmesg
....
[ 8300.686085] netscan module enter
[ 8300.686095] lo: ifindex 1
[ 8300.686097] This is the current status jus get !
[ 8300.686101] packets:888/888 bytes: 282809/282809 errors:0 dropped:0
[ 8300.686101] 
[ 8300.686105] eth1: ifindex 2
[ 8300.686107] This is the current status jus get !
[ 8300.686110] packets:945987/2384507 bytes: 77162255/3264031681 errors:0 dropped:35
[ 8300.686110] 
[ 8300.686115] eth3: ifindex 3
[ 8300.686117] This is the current status jus get !
[ 8300.686119] packets:0/0 bytes: 0/0 errors:0 dropped:0
[ 8300.686119] 
[ 8300.686123] sit0: ifindex 4
[ 8300.686125] This is the current status jus get !
[ 8300.686128] packets:0/0 bytes: 0/0 errors:0 dropped:0
[ 8300.686128] 
[ 8300.686131] Last netdevice index 4


我們可以看到,程序編譯時也只提示了一個“ ISO C90 forbids mixed declarations and code ”錯誤,對於有經驗的童鞋,可以很輕鬆的排除這個錯誤,就是把提示警告的函數中所有的聲明部分放在函數最前面,而其他代碼放在聲明後面。

那麼這樣的代碼在您平時編程中是不是堪稱完美?編譯器不報錯(上述簡單的警告,我們可以輕鬆排除),程序運行正常。那麼這樣一段程序對於 checkpatch 來說是什麼樣的?我們可以看看:

$ ~/kernel/linux-3.12/linux-3.12.1/scripts/checkpatch.pl --file netdump.c > log

打開log 文件:

ERROR: trailing whitespace
#7: FILE: netdump.c:7:
+ *      This modules will scan netdevices and report them via printk $

WARNING: line over 80 characters
#9: FILE: netdump.c:9:
+ * Useage:  1. make                 -- you should make the module firstly(with Makefile )
...
total: 22 errors, 16 warnings, 63 lines checked

NOTE: whitespace errors detected, you may wish to use scripts/cleanpatch or
      scripts/cleanfile

netdump.c has style problems, please review.


最後一行,checkpatch 工具很輕柔的告訴我們,netdump.c 文件有代碼風格問題,請改正吧! “ total: 22 errors, 16 warnings, 63 lines checked ”!63行的代碼,有22個錯誤,16個警告!我們可以先看看ERROR部分(因爲ERROR部分是必須要改的,重要的錯誤):

$ grep "ERROR" log | sort | uniq
ERROR: code indent should use tabs where possible       --- 代碼行前面的空白處應該使用TAB 而不是空格
ERROR: do not use C99 // comments                       --- 不能使用C99中的"//"型註釋,需要使用
"/**/"型
ERROR: space required before the open brace '{'         --- 對於 for,if,while等有涉及到代碼段時,使用 { 和 } 時,需要在{ 之前和}之後(如果後面有東西的話,否則就成了代碼行末尾空白)加空格,比如 if (cond) { ... } else { ... }
ERROR: space required after that close brace '}'
ERROR: space required after that ',' (ctx:VxO)          --- 帶參時,比如foo(a,b),在a,後b之前需要空格,所以正確用法是: foo(a, b)
ERROR: space required after that ',' (ctx:VxV)
ERROR: space required before that '&' (ctx:OxV)         --- 此條和上面的帶參重複
ERROR: space required before the open parenthesis '('   --- 類似{} ,()前後也需要空格
ERROR: spaces required around that '==' (ctx:VxV)       --- 比較"=="/"!="和賦值"="前後也需要空格
ERROR: spaces required around that '=' (ctx:VxV)
ERROR: spaces required around that '!=' (ctx:VxV)
ERROR: trailing whitespace                              --- 代碼行的末尾有多餘的空白(空格/tab>)


分析完ERROR,我們在來看看WARNING:

$ grep "WARNING" log | sort | uniq >b           -- 驚訝的發現,16個警告去重複之後只有4類
WARNING: line over 80 characters                -- 代碼行多餘80個字符!爲什麼是80個字符,有興趣
可以去查查(小透露一下,歷史原因!)
WARNING: please, no space before tabs           -- tab前有空格,所有空格一律使用tab!
WARNING: please, no spaces at the start of a line   -- 行開始沒有空白
WARNING: printk() should include KERN_ facility level   -- printk沒有"KERN_"這樣的輸出級別!爲>什麼這只是warning?大家都知道,如果printk沒有帶輸出級別,它將採用默認!


現在還敢說你的代碼習慣很好麼?你可以試驗一下你最自豪的代碼!祝您玩的愉快偷笑


二、 代碼檢測篇

2.1 Coccinelle 

Coccinelle是一個程序的匹配和轉換引擎,它提供了語言SMPL(語義補丁語言)用於指定C代碼所需的匹配和轉換。Coccinelle 最初是用來幫助Linux的演變,支持更改庫應用程序編程接口,比如重命名一個函數,增加一個依賴於上下文的函數參數或者重新組織一個數據結構。除此之外,Coccinelle頁被人用來查找或者修復系統代碼的bug。

2.1.1 安裝

(1) sudo apt-get build-dep coccinelle 

如果您的apt-get 提示找不到coccinelle,建議您把你的" /etc/apt/sources.list "配成我這樣的吧:

$ cat  /etc/apt/sources.list
deb http://mirrors.163.com/debian wheezy main non-free contrib
deb-src http://mirrors.163.com/debian wheezy main non-free contrib


(2) ./configure --enable-release
(3) sudo make all
(4) sudo make install

2.1.2 使用

其實Coccinelle使用起來很簡單,比如上面的內核模塊代碼。我們如何使用coccinelle檢查這段代碼?只需要在編譯時添加coccicheck 選項即可!

比如,我們的Makefile可以這麼寫:

$ cat  Makefile
obj-m:=netdump.o
default:
    make -Wall -C /lib/modules/`uname -r`/build M=`pwd` modules

cocci:
    make -C /lib/modules/`uname -r`/build coccicheck MODE=report M=`pwd` 

clean:
    make -C /lib/modules/`uname -r`/build M=`pwd` clean

  這樣,我們可以使用 make 來簡單編譯模塊,還可以使用 make cocci 來使用coccinelle對代碼進行檢查:

$ make cocci 
make -C /lib/modules/`uname -r`/build coccicheck MODE=report M=`pwd` 
make[1]: Entering directory `/home/long/Mar_class/linux-3.12.9'

Please check for false positives in the output before submitting a patch.
When using "patch" mode, carefully review the patch before submitting it.

make[1]: Leaving directory `/home/long/kernel/linux-3.12.9'



2.2 sparse

Sparse 是用於 C 語言的語法分析器,用以對 C 代碼進行靜態檢查,它不但可以檢查 ANSI C 而且還能檢查具有 gcc 擴展的 C 。在 Linux 中,不但可以檢查用戶端代碼,還可以檢查內核代碼。起初它由 Linus 編寫,後來交給其他人維護。Sparse通過 gcc 的擴展屬性 __attribute__ 以及自己定義的 __context__ 來對代碼進行靜態檢查。

下面我們來看看這個神奇的工具:

2.2.1 安裝

對於sparse的安裝,可以使用多種方法:

最簡單的一種就是使用apt-get安裝:sudo apt-get install sparse

其次是從網站下載,下載sparse-0.4.4.tar.gz壓縮包後解壓,然後直接 makemake install 即可!

最後就是使用 git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git 克隆sparse倉庫,然後進入倉庫先使用 git tag 查看最新的版本,然後使用 $ git checkout -b stable v0.4.4 切到最新的版本,最後連續使用root 權限make make install 安裝即完成了!

2.2.2 使用

其實sparse的使用比上面介紹的coccinelle還簡單,只需要在make 後添加 “ C=2 ”,所以上面的Makefile 還可以擴展成:

$ cat  Makefile
obj-m:=netdump.o
default:
    make -Wall -C /lib/modules/`uname -r`/build M=`pwd` modules

cocci:
    make -C /lib/modules/`uname -r`/build coccicheck MODE=report M=`pwd` 

sparse:
    make C=2 -C /lib/modules/`uname -r`/build  M=`pwd`

clean:
    make -C /lib/modules/`uname -r`/build M=`pwd` clean

此時我們只要使用 make sparse 即可使用sparse工具對代碼進行檢查:

$ make sparse 
make C=2 -C /lib/modules/`uname -r`/build  M=`pwd`
make[1]: Entering directory `/home/long/kernel/linux-3.12.9'
  LD      /tmp/test/built-in.o
  CHECK   /tmp/test/netdump.c
/tmp/test/netdump.c:23:9: warning: mixing declarations and code
/tmp/test/netdump.c:29:48: warning: incorrect type in argument 1 (different base types)
/tmp/test/netdump.c:29:48:    expected struct net *net
/tmp/test/netdump.c:29:48:    got struct net extern [addressable] [toplevel] init_net
  CC [M]  /tmp/test/netdump.o
/tmp/test/netdump.c: In function ‘hello_init’:
/tmp/test/netdump.c:23:2: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
/tmp/test/netdump.c:29:4: error: incompatible type for argument 1 of ‘dev_get_by_index’
In file included from /tmp/test/netdump.c:18:0:
include/linux/netdevice.h:1795:27: note: expected ‘struct net *’ but argument is of type ‘struct net’
make[2]: *** [/tmp/test/netdump.o] Error 1
make[1]: *** [_module_/tmp/test] Error 2
make[1]: Leaving directory `/home/long/kernel/linux-3.12.9'
make: *** [sparse] Error 2

因爲sparse是對屬性進行檢查,所以在上面使用 make sparse 之前,我把代碼第29行“ dev = dev_get_by_index(&init_net,idx); ”中的& 去掉了,所以sparse會檢測出參數格式錯誤!所以你可以想象,你的代碼中如果指針使用錯誤,sparse都會一一指出哦!!!是不是很幸福?偷笑

注:現在貌似也有針對其他語言的sparse工具,前幾天剛看到有python的sparse,不過還沒嘗試過。


三、總結篇

使用第一節中的checkpatch是讓我們養成好的代碼風格,既美觀又符合內核中的代碼風格,何樂而不爲?其實,無論是對於已工作的程序猿還是對於要找工作的學生來說,養成好的代碼習慣和風格總是好的。最大的好處是讀代碼方便,其次是好的代碼風格可以讓別人對你有了最基本的認識!

第二節中的兩個工具都是由來已久,而且在內何編碼界使用也很廣泛,如果你每次都使用這兩個工具檢查,相信對你的代碼能力也會有很大的提升。

最後送大家一句: 學習容易,堅持不易,且學且珍惜!


==================

更多閱讀: 

[1] http://kernelnewbies.org/KernelHacking

[2] http://coccinelle.lip6.fr/

[3] https://home.regit.org/technical-articles/coccinelle-for-the-newbie/

[4] http://kernelnewbies.org/Sparse

[5] http://www.cnblogs.com/wang_yb/p/3575039.html


發佈了90 篇原創文章 · 獲贊 434 · 訪問量 106萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章