linux 内核审核代码格式 /scripts/checkpatch.pl

Linux程序的命名习惯和Windows程序的命名习惯及著名的匈牙利命名法有很大的不同
一、Documentation/CodingStyle文档
Linux有独特的编码风格,在Linux内核源代码下存在一个文件Documentation/CodingStyle,进行了比较详细的描述


二、scripts/checkpatch.pl脚本
内核下的scripts/checkpatch.pl提供了1个检查代码风格的脚本


在SCM软件服务端部署checkpatch.pl脚本
在工程阶段,一般可以在SCM软件的服务器端使能pre-commit hook,自动检查工程师提交的代码是否 符合Linux的编码风格,如果不符合,则自动拦截
git的pre-commit hook可以运行在本地代码仓库中,下面是一个演示版本
#!/bin/sh
#
# pre-commit hook to run check-patch on the output and stop any commits
# that do not pass. Note, only for git-commit, and not for any of the
# other scenarios
#
# Copyright 2010 Ben Dooks, <ben-linux@fluff.org>
 
if git rev-parse --verify HEAD 2>/dev/null >/dev/null
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
git diff --cached $against -- | ./scripts/checkpatch.pl --no-signoff  --- 删除掉就可以了
二、命名规范
名称间使用下划线,使用小写字母
三、代码缩进
Linux的代码缩进使用“TAB”
四、空格使用
另外,请注意代码中空格的应用,譬如“for *(i*=*0;*i*<*10;*i++)*{”   语 句中的“*”都是空格
五、Linux中代码括号“{”和“}”的使用原则
1.对于结构体、if/for/while/switch语句, “{”不另起一行,例如:


2.如果if、for循环后只有1行,不要加“{”和“}” ,例如:


3.if和else混用的情况下,else语句不另起一行,例如:


4.对于函数, “{”另起一行,譬如:


5.在switch/case语句方面,Linux建议switch和case对齐,例如:


六、GNU C与ANSI C
Linux上可用的C编译器是GNU C编译器,它建立在自由软件基金会的编程许可证的基础上,因此可以自由发布。GNU C对标准C进行一系列扩展,以增强标准C的功能
零长度和变量长度数组
GNU C允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。
例如:char data[0]仅仅意味着程序中通过var_data结构体实例的data[index]成员可以访问len之后的第index个地 址,它并没有为data[]数组分配内存,因此sizeof(struct var_data)=sizeof(int)
struct var_data {
    int len;
    char data[0];
};
假设struct var_data的数据域就保存在struct var_data紧接着的内存区域中,则通过如下代码可以遍历这 些数据:
struct var_data s;
...
for (i = 0; i < s.len; i++)
    printf("%02x",s.data[i]);
case范围
GNU C支持case x…y这样的语法,区间[x,y]中的数都会满足这个case的条件
请看下面的代码:
switch (ch) {
=case '0'... '9': c -= '0';
    break;
case 'a'... 'f': c -= 'a' - 10;
    break;
case 'A'... 'F': c -= 'A' - 10;
    break;
}
代码中的case'0'...'9'等价于标准C中的:
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
语句表达式
NU C把包含在括号中的复合语句看成是一个表达式,称为语句表达式,它可以出现在任何允许表 达式的地方。我们可以在语句表达式中使用原本只能在复合语句中使用的循环、局部变量等
例如:
#define min_t(type,x,y) \
({type _ _x =(x);type _ _y = (y); _ _x<_ _y _ _x: _ _y; })
int ia, ib, mini;
float fa, fb, minf;
mini = min_t(int, ia, ib);
minf = min_t(float, fa, fb);
因为重新定义了__xx和__y这两个局部变量,所以用上述方式定义的宏将不会有副作用。在标准C中,对应的如下宏则会产生副作用:
代码min(++ia,++ib)会展开为((++ia)<(++ib)(++ia):(++ib)),传入宏的“参数”增加 两次
#define min(x,y) ((x) < (y) (x) : (y))
typeof关键字
typeof(x)语句可以获得x的类型,因此,可以借助typeof重新定义min这个宏:
#define min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y _x : _y; })
我们不需要像min_t(type,x,y)那个宏那样把type传入,因为通过typeof(x)、typeof(y)可以获 得type。代码行(void)(&_x==&_y)的作用是检查_x和_y的类型是否一致
可变参数宏
标准C就支持可变参数函数,意味着函数的参数是不固定的
例如printf()函数的原型为:
int printf( const char *format [, argument]... );
而在GNU C中,宏也可以接受可变数目的参数,例如
#define pr_debug(fmt,arg...) \
            printk(fmt,##arg)
这里arg表示其余的参数,可以有零个或多个参数,这些参数以及参数之间的逗号构成arg的值,在宏扩展时替换arg,如下列代码:
pr_debug("%s:%d",filename,line)
 
会被扩展为:
printk("%s:%d", filename, line)
使用“##”是为了处理arg不代表任何参数的情况,这时候,前面的逗号就变得多余了。使用“##”之后, GNU C预处理器会丢弃前面的逗号,这样,下列代码:
pr_debug("success!\n")
 
会被正确地扩展为:
printk("success!\n")
而不是:
printk("success!\n",)
标号元素
标准C要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构体成 员名,允许初始化值以任意顺序出现
指定数组索引的方法是在初始化值前添加“[INDEX]=” ,当然也可以用“[FIRST...LAST]=”的形式指定 一个范围。例如,下面的代码定义了一个数组,并把其中的所有元素赋值为0:
unsigned char data[MAX] = { [0 ... MAX-1] = 0 };
下面的代码借助结构体成员名初始化结构体:
struct file_operations ext2_file_operations = {
    llseek: generic_file_llseek,
    read: generic_file_read,
    write: generic_file_write,
    ioctl: ext2_ioctl,
    mmap: generic_file_mmap,
    open: generic_file_open,
    release: ext2_release_file,
    fsync: ext2_sync_file,
};
但是,Linux 2.6推荐类似的代码应该尽量采用标准C的方式:
struct file_operations ext2_file_operations = {
    .llseek = generic_file_llseek,
    .read = generic_file_read,
    .write = generic_file_write,
    .aio_read = generic_file_aio_read,
    .aio_write = generic_file_aio_write,
    .ioct = ext2_ioctl,
    .mmap = generic_file_mmap,
    .open = generic_file_open,
    .release = ext2_release_file,
    .fsync = ext2_sync_file,
    .readv = generic_file_readv,
    .writev = generic_file_writev,
    .sendfile = generic_file_sendfile,
};
当前函数名
GNU C预定义了两个标识符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字, __PRETTY_FUNCTION__保存带语言特色的名字。在C函数中,这两个名字是相同的
void example()
{
    printf("This is function:%s", __FUNCTION__);
}
代码中的__FUNCTION__意味着字符串“example” 。C99已经支持__func__宏,因此建议在Linux编程中 不再使用__FUNCTION__,而转而使用__func__:
void example(void)
{
printf("This is function:%s", __func__);
}
特殊属性声明
GNU C允许声明函数、变量和类型的特殊属性,以便手动优化代码和定制代码检查的方法。要指定 一个声明的属性,只需要在声明后添加__attribute__((ATTRIBUTE))。其中ATTRIBUTE为属性说 明,如果存在多个属性,则以逗号分隔。
GNU C支持noreturn、format、section、aligned、packed等十多个 属性
noreturn属性作用于函数,表示该函数从不返回。这会让编译器优化代码,并消除不必要的警告信 息。例如
# define ATTRIB_NORET __attribute__((noreturn)) ....
asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;
format属性也用于函数,表示该函数使用printf、scanf或strftime风格的参数,指定format属性可以让编 译器根据格式串检查参数类型。例如:
代码中的第1个参数是格式串,从第2个参数开始都会根据printf()函数的格式串规则检查参 数
asmlinkage int printk(const char * fmt, ...) __attribute__ ((format (printf, 1, 2)));
unused属性作用于函数和变量,表示该函数或变量可能不会用到,这个属性可以避免编译器产生警告信息
aligned属性用于变量、结构体或联合体,指定变量、结构体或联合体的对齐方式,以字节为单位,例如:表示该结构类型的变量以4字节对齐
struct example_struct {
    char a;
    int b;
    long c;
} __attribute__((aligned(4)));
packed属性作用于变量和类型,用于变量或结构体成员时表示使用最小可能的对齐,用于枚举、结构 体或联合体类型时表示该类型使用最小的内存。例如:
struct example_struct {
    char a;
    int b;
    long c __attribute__((packed));
};


内建函数
GNU C提供了大量内建函数,其中大部分是标准C库函数的GNU C编译器内建版本,例如 memcpy()等,它们与对应的标准C库函数功能相同
不属于库函数的其他内建函数的命名通常以__builtin开始,如下所示:
内建函数__builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数LEVEL指定 调用栈的级数,如0表示当前函数的返回地址,1表示当前函数的调用者的返回地址
内建函数__builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数EXP的值是常 数,函数返回1,否则返回0
内建函数__builtin_expect(EXP,C)用于为编译器提供分支预测信息,其返回值是整数表达式EXP 的值,C的值必须是编译时常数
例如,下面的代码可检测第1个参数是否为编译时常数以确定采用参数版本还是非参数版本:
#define test_bit(nr,addr) \
(__builtin_constant_p(nr) \
constant_test_bit((nr),(addr)) : \
variable_test_bit((nr),(addr)))
Linux内核编程时常用的likely()和unlikely()底层调用的likely_notrace()、unlikely_notrace()就 是基于__builtin_expect(EXP,C)实现的
#define likely_notrace(x) __builtin_expect(!!(x), 1)
#define unlikely_notrace(x) __builtin_expect(!!(x), 0
若代码中出现分支,则即可能中断流水线,我们可以通过likely()和unlikely()暗示分支容易成立还是不容易成立,例如:
if (likely(!IN_DEV_ROUTE_LOCALNET(in_dev)))
    if (ipv4_is_loopback(saddr))
        goto e_inval;
在使用gcc编译C程序的时候,如果使用“-ansi–pedantic”编译选项,则会告诉编译器不使用GNU扩展语 法。例如对于如下C程序test.c:
struct var_data {
    int len;
    char data[0];
};
struct var_data a;
 
/********************/
直接编译可以通过:
gcc -c test.c
如果使用“-ansi–pedantic”编译选项,编译会报警:
gcc -ansi -pedantic -c test.c
test.c:3: warning: ISO C forbids zero-size array 'data'
七、do{}while(0)语句
在Linux内核中,经常会看到do{}while(0)这样的语句,许多人开始都会疑惑,认为do{}while(0) 毫无意义,因为它只会执行一次,加不加do{}while(0)效果是完全一样的
其实do{}while(0)的用法 主要用于宏定义中
演示案例
这里用一个简单的宏来演示
#define SAFE_FREE(p) do{ free(p); p = NULL;} while(0)
假设这里去掉do...while(0),即定义SAFE_DELETE为
#define SAFE_FREE(p) free(p); p = NULL;
那么以下代码:
if(NULL != p)
    SAFE_DELETE(p)
else
    .../* do something */
 
会被展开为:
if(NULL != p)
    free(p); p = NULL;
else
    .../* do something */
展开的代码中存在两个问题:
①因为if分支后有两个语句,导致else分支没有对应的if,编译失败
②假设没有else分支,则SAFE_FREE中的第二个语句无论if测试是否通过,都会执行
的确,将SAFE_FREE的定义加上{}就可以解决上述问题了,即:
#define SAFE_FREE(p) { free(p); p = NULL;}
这样,代码:
if(NULL != p)
    SAFE_DELETE(p)
else
    ... /* do something */
 
会被展开为:
if(NULL != p)
    { free(p); p = NULL; }
else
    ... /* do something */
但是,在C程序中,在每个语句后面加分号是一种约定俗成的习惯,那么,如下代码:
if(NULL != p)
    SAFE_DELETE(p);
else
    ... /* do something */
 
将被扩展为:
if(NULL != p)
    { free(p); p = NULL; };
else
    ... /* do something */
这样,else分支就又没有对应的if了,编译将无法通过。假设用了do{}while(0)语句,情况就不一样 了,同样的代码会被展开为:
if(NULL != p)
    do{ free(p); p = NULL;} while(0);
else
    ... /* do something */
而不会再出现编译问题。do{}while(0)的使用完全是为了保证宏定义的使用者能无编译错误地使用宏, 它不对其使用者做任何假设。
八、goto语句
用不用goto一直是一个著名的争议话题,Linux内核源代码中对goto的应用非常广泛,但是一般只限于 错误处理中
例如:
if(register_a()!=0)
    goto err;
if(register_b()!=0)
    goto err1;
if(register_c()!=0)
    goto err2;
if(register_d()!=0)
    goto err3;
...
 
err3:
    unregister_c();
err2:
    unregister_b();
err1:
    unregister_a();
err:
    return ret;

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值