所有的内核代码,基本都包含了linux\compile.h这个文件,所以它是基础,打算先分析这个文件里的代码看看,有空再分析分析其它的代码。
首先印入眼帘的是对__ASSEMBLY__这个宏的判断,这个变量实际是在编译汇编代码的时候,由编译器使用-D这样的参数加进去 的,AFLAGS这个变量也定义了这个变量,gcc会把这个宏定义为1。用在这里,是因为汇编代码里,不会用到类似于__user这样的属性(关于 __user这样的属性是怎么回子事,本文后面会提到),因为这样的属性是在定义函数的时候加的,这样避免不必要的在编译汇编代码时候的引用。
接下来是一个对__CHECKER__这个宏的判断,这里需要讲的东西比较多。
当 编译内核代码的时候,使用make C=1或C=2的时候,会调用一个叫Sparse的工具,这个工具对内核代码进行检查,怎么检查呢,就是靠对那些声明过Sparse这个工具所能识别的特 性的内核函数或是变量进行检查。在调用Sparse这个工具的同时,在Sparse代码里,会加上#define __CHECKER__ 1的字样。换句话说,就是,如果使用Sparse对代码进行检查,那么内核代码就会定义__CHECKER__宏,否则就不定义。
所以这里就能看出来,类似于__attribute__((noderef, address_space(1)))这样的属性就是Sparse这个工具所能识别的了。
那么这些个属性是干什么用的呢,我一个个做介绍。
这样的属性说明,有一部分在gcc的文档里还没有加进去,至少我在gcc 4.3.2的特性里没有看到,网上有哥们问类似的问题,Greg对他进行了解答,然后他对Greg抱怨文档的事,Greg对他说,他有时间抱怨的话,还不 如自己来更新文档。他不能对一个免费工具的文档有如此之高的要求,除非他付费。
# define __user __attribute__((noderef, address_space(1))) |
__user这个特性,即__attribute__((noderef, address_space(1))),是用来修饰一个变量的,这个变量必须是非解除参考(no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是1,即用户程序空间的。
这里把程序空间分成了3个部分,0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,这个不用多讲,还有一个2,表示是设备地址映射空间,例如硬件设备的寄存器在内核里所映射的地址空间。
所以在内核函数里,有一个copy_to_user的函数,函数的参数定义就使用了这种方式。当然,这种特性检查,只有当机器上安装了Sparse这个工具,而且进行了编译的时候调用,才能起作用的。
# define __kernel /* default address space */ |
根据定义,就是默认的地址空间,即0,我想定义成__attribute__((noderef, address_space(0)))也是没有问题的。
# define __safe __attribute__((safe)) |
这个定义在sparse里也有,内核代码是在2.6.6-rc1版本变到2.6.6-rc2的时候被Linus加入的,经过我的艰苦的查找,终于查找到原因了,知道了为什么Linus要加入这个定义,原因是这样的:
有人发现在代码编译的时候,编译器对变量的检查有些苛刻,导致代码在编译的时候老是出问题(我这里没有去检查是编译不通过还是有警告信息,因为现在的编译器已经不是当年的编译器了,代码也不是当年的代码)。比如说这样一个例子,
int test( struct a * a, struct b * b, struct c * c ) { return a->a + b->b + c->c; } |
这个编译的时候会有问题,因为没有检查参数是否为空,就直接进行调用。但是呢,在内核里,有好多函数,当它们被调用的时候,这些个参数必定不为空,所以根本用不着去对这些个参数进行非空的检查,所以呢,就增加了一个__safe的属性,如果这样声明变量,
int test( struct a * __safe a, struct b * __safe b, struct c * __safe c ) { return a->a + b->b + c->c; } |
编译就没有问题了。