在写内核代码时,代码风格(coding style)是一个很重要的部分,否则内核代码将变的混乱不堪。那么什么样的代码算漂亮的代码?什么样的代码符合c99这样的标准?此外,程序写完之后,有什么工具能够帮我们检查代码有没有指针错误?客官且随我看看这三个工具:
想开发一个内核程序?你的电脑有内核源码么?不管是曾经用来编译内核或者你自己查阅资料,如果您的电脑上有内核源码,好的,本节将介绍一个很多人都不知道的强大的工具 -- 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没有带输出级别,它将采用默认!
现在还敢说你的代码习惯很好么?你可以试验一下你最自豪的代码!祝您玩的愉快
Coccinelle是一个程序的匹配和转换引擎,它提供了语言SMPL(语义补丁语言)用于指定C代码所需的匹配和转换。Coccinelle 最初是用来帮助Linux的演变,支持更改库应用程序编程接口,比如重命名一个函数,增加一个依赖于上下文的函数参数或者重新组织一个数据结构。除此之外,Coccinelle页被人用来查找或者修复系统代码的bug。
(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
其实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 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'
Sparse 是用于 C 语言的语法分析器,用以对 C 代码进行静态检查,它不但可以检查 ANSI C 而且还能检查具有 gcc 扩展的 C 。在 Linux 中,不但可以检查用户端代码,还可以检查内核代码。起初它由 Linus 编写,后来交给其他人维护。Sparse通过 gcc 的扩展属性 __attribute__ 以及自己定义的 __context__ 来对代码进行静态检查。
下面我们来看看这个神奇的工具:
对于sparse的安装,可以使用多种方法:
最简单的一种就是使用apt-get安装:sudo apt-get install sparse
其次是从网站下载,下载sparse-0.4.4.tar.gz压缩包后解压,然后直接 make 再 make 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 安装即完成了!
其实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 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工具,前几天刚看到有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