Linux的内核源码都会包含文件linux\compile.h,所以先分析该文件内的内容,作为开篇。
1 汇编编译时不定义的内容
该文件的第一个内容是对宏__ASSEMBLY__的判断,这个宏的作用是避免在进行汇编编译的时候,不定义后续相关内容。这个宏通过在编译器中用-D选项中加入,参数AFLAGS也包含该宏定义。在汇编时编译器会定义__ASSEMBLY__为1。
#ifndef __ASSEMBLY__
1.1 Sparse工具检测使用的属性定义
接下来是__CHECKER__宏的判断,__CHECKER__宏在通过Sparse(Semantic Parser for C)工具对内核代码进行检查时会定义的。在使用make C=1或C=2时便会调用该工具,这个工具可以检查在代码中声明了sparse所能检查到的相关属性的内核函数和变量。
#ifdef __CHECKER__
下面分析一下sparse所能检查的相关属性。
# define __user __attribute__((noderef, address_space(1)))
# define __kernel /* default address space */
# define __safe __attribute__((safe))
# define __force __attribute__((force))
# define __nocast __attribute__((nocast))
# define __iomem __attribute__((noderef, address_space(2)))
# define __acquires(x) __attribute__((context(x,0,1)))
# define __releases(x) __attribute__((context(x,1,0)))
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)
extern void __chk_user_ptr(const volatile void __user *);
extern void __chk_io_ptr(const volatile void __iomem *);
__user特性用来修饰一个变量的地址,该变量必须是非解除参考(no dereference)即地址是有效的,并且变量所在的地址空间必须为1,这里为(address_space(1)),用户地址空间。sparse把地址空间分为3部分,0表示普通地址空间,对内核来说就是地址空间。1表示用户地址空间。2表示设备地址映射空间,即设备寄存器的地址空间。
__kernel特性修饰变量为内核地址,为内核代码里面默认的地址空间。
__safe特性声明该变量为安全变量,这是为了避免在内核函数未对传入的参数进行校验就使用的情况下,会导致编译器对其报错或输出告警信息。 通过该特性说明该变量不可能为空。
__force特性声明该变量是可以强制类型转换的。
__nocast声明该变量参数类型与实际参数类型要一致才可以。
__iomem声明地址空间是设备地址映射空间,其他的与__user一样。
__acquires为函数属性定义的修饰,表示函数内,该参数的引用计数值从1变为0。
__releases与__acquires相反,这一对修饰符用于Sparse在静态代码检测时,检查调用的次数和匹配请求,经常用于检测lock的获取和释放。
__acquire表示增加变量x的计数,增加量为1。
__release表示减少变量x的计数,减少量为1。这一对与上面的那一对是一样,只是这一对用在函数的执行过程中,都用于检查代码中出现不平衡的状况。
__cond_lock用于表示条件锁,当c这个值不为0时,计数值加1,并返回1。
__chk_user_ptr和__chk_io_ptr在这里只声明函数,没有函数体,目的就是在编译过程中Sparse能够捕捉到编译错误,检查参数的类型。