请善用工具审核您的内核代码:)

在写内核代码时。代码风格(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 <[email protected]>");
MODULE_DESCRIPTION("scan netdevices and report them via printk");
MODULE_LICENSE("GPL");

写过内核模块的童鞋能轻易的分辩。这是个内核模块。再有经验的童鞋,可以看出来这个模块的主要工作都在 init 时做了(也即insmod 模块时)。

那么做了什么工作呢?事实上非常easy。就是读取网卡设备的状态然后显示出来,比方说发/收多少数据包,多少字节等。并且因为代码图简便,通过 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使用起来非常easy。比方上面的内核模块代码。我们怎样使用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压缩包后解压,然后直接
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 安装即完毕了。

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是让我们养成好的代码风格。既美观又符合内核中的代码风格,何乐而不为?事实上,不管是对于已工作的程序员还是对于要找工作的学生来说,养成好的代码习惯和风格总是好的。最大的优点是读代码方便。其次是好的代码风格能够让别人对你有了最主要的认识!

第二节中的两个工具都是由来已久,并且在内何编码界使用也非常广泛,假设你每次都使用这两个工具检查。相信对你的代码能力也会有非常大的提升。

最后送大家一句: 学习easy,坚持不易。且学且珍惜!

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

很多其它阅读:

[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

时间: 2024-10-22 19:01:35

请善用工具审核您的内核代码:)的相关文章

[转] LINUX内核代码编程规范

这是一个简短的文档,描述了linux内核的首选代码风格.代码风格是因人而异的,而且我 不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格, 并且我也希望绝大多数其他代码也能遵守这个风格.请在写代码时至少考虑一下本文所述的 风格. 首先,我建议你打印一份GNU代码规范,然后不要读它.烧了它,这是一个具有重大象征性 意义的动作. 不管怎样,现在我们开始: 第一章:缩进 制表符是8个字符,所以缩进也是8个字符.有些异端运动试图将缩进变为4(乃至2)个字符 深,这几乎相当于尝

[转] Linux内核代码风格 CodingStyle [CH]

from:http://blog.csdn.net/jiang_dlut/article/details/8163731 中文版维护者: 张乐 Zhang Le <[email protected]> 中文版翻译者: 张乐 Zhang Le <[email protected]> 中文版校译者: 王聪 Wang Cong <[email protected]>                wheelz <[email protected]>        

Linux内核代码风格

这是一个简短的文档,描述了linux内核的首选代码风格.代码风格是因人而异的,而且我 不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格, 并且我也希望绝大多数其他代码也能遵守这个风格.请在写代码时至少考虑一下本文所述的 风格. 首先,我建议你打印一份GNU代码规范,然后不要读它.烧了它,这是一个具有重大象征性 意义的动作. 不管怎样,现在我们开始: 第一章:缩进 制表符是8个字符,所以缩进也是8个字符.有些异端运动试图将缩进变为4(乃至2)个字符 深,这几乎相当于尝

完成一个简单的时间片轮转多道程序内核代码

王康 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 " 分别是1 存储程序计算机工作模型,cpu执行程序的基础流程: 2 函数调用堆栈:各种寄存器和存储主要是为了指令的传取值,通过eip,esp,eax,ebp和程序内存的分区,搭配push pop call return leave等一系列指令完成函数调用操作. 3 中断:多道批程序! 在复习一下上一讲的几个重要指令

对一个简单的时间片轮转多道程序内核代码的浅析

这周在网易云课堂上学习了<Linux内核分析>——操作系统是如何工作的.本周学习内容有利用 mykernel 实验模拟计算机平台和利用 mykernel 实验模拟计算机硬件平台两部分内容. 这是实验楼中 mykernel 平台运行的结果: 下面是一段一个简单的时间片轮转多道程序内核代码: 1 /* 2 * linux/mykernel/myinterrupt.c 3 * 4 * Kernel internal my_timer_handler 5 * 6 * Copyright (C) 201

开源项目成熟度分析工具-利用github api获取代码库的信息

1.github api github api是http形式的api,功能还是比较丰富的,博主因为项目的原因主要用到的是提取project信息这项功能,返回的数据是JSON格式. api页:https://developer.github.com/v3/ Options: (H) means HTTP/HTTPS only, (F) means FTP only --anyauth Pick "any" authentication method (H) -a, --append Ap

Linux0.11内核剖析--内核代码(kernel)--sched.c

1.概述 linux/kernel/目录下共包括 10 个 C 语言文件和 2 个汇编语言文件以及一个 kernel 下编译文件的管理配置文件 Makefile.其中三个子目录中代码注释的将放在后面的文章进行.本文主要对这 13 个代码文件进行注释. 首先我们对所有程序的基本功能进行概括性地总体介绍, 以便一开始就对这 12 个文件所实现的功能和它们之间的相互调用关系有个大致的了解,然后逐一对代码进行详细地注释.本文地址:http://www.cnblogs.com/archimedes/p/l

Linux内核分析—完成一个简单的时间片轮转多道程序内核代码

---恢复内容开始--- 20135125陈智威 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ” 实验要求: mykernel实验指导(操作系统是如何工作的) 运行并分析一个精简的操作系统内核,理解操作系统是如何工作的 使用实验楼的虚拟机打开shell cd LinuxKernel/linux-3.9.4 qemu -kernel arch/x86/boot/bzImage 然后cd

一个简单的循环多道程序内核代码 实现

张韩 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 首先.解读一下程序的核心代码 void __init my_start_kernel(void) { int i = 0; while(1) { i++; if(i%100000 == 0) printk(KERN_NOTICE "my_start_kernel here  %d \n",i); } } 这