在分析自旋锁代码时,最终跟踪下来,会在 include/linux/compiler.h 中看到下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
#ifdef __CHECKER__ ... # define __acquire(x) __context__(x,1) # define __release(x) __context__(x,-1) ... #else
# define __acquire(x) (void)0 # define __release(x) (void)0 ... #endif |
其中含有 __context__ 这种属性。刚开始时认为这也是 gcc 支持的属性一种,其实不然。在应用程序里,模拟上面宏定义并包含 __context__ ,然后用 gcc 编译一下,会有错误提示。
实际上,该属性是提供给一种叫 sparse 的工具进行代码的静态检查的。关于 sparse 的介绍可参考:
引用
在我们编译内核代码时,在 make 时使用 C=1 或者 C=2 选项时都会对 sparse 工具的调用。
sparse 进行代码静态检查并不会对代码进行编译,也不会进行相关宏的处理,它的工作只是检查而已。那么可能会有疑问,检查后,那最终还不是要用 gcc 来编译么?那这样一来不是还不能编译通过?这里需要注意到,像 # define __acquire(x) __context__(x,1) 这个宏,需要在 __CHECKER__ 这个宏定义下才能使用。那么搜遍整个内核代码,会发现没有 __CHECKER__ 的定义。但是回过头来查看 sparse 相关的源码时,会发现在对 sparse 初始化的函数里有这么一段(位于
lib.c 中):
1
2
3
4
5
6
|
struct symbol_list *sparse_initialize( int argc, char
struct
{ .... add_pre_buffer( "#define __CHECKER__ 1\n" ); ... } |
再看 add_pre_buffer() 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
void
const *fmt, ...) { va_list args; unsigned int size;
struct token *begin, *end; char buffer[4096]; va_start (args, fmt); size = vsnprintf(buffer, sizeof (buffer), fmt, args); va_end (args); begin = tokenize_buffer(buffer, size, &end); if (!pre_buffer_begin) pre_buffer_begin = begin; if (pre_buffer_end) pre_buffer_end->next = begin; pre_buffer_end = end; } |
上面,就是将 "#define __CHECKER__ 1" 这一个字符串通过 vsnprintf(buffer, sizeof(buffer), fmt, args); 拷贝到缓冲区中。
一般的,对代码的静态检查工具,基本工作原理是将要检查的源代码读入缓冲区中在进行比较。所以,__CHECKER__ 这个定义是先在 sparse 的工具的缓冲区中定义好,这样,我们自然就能够使用带有 __context__ 属性的宏了。需要注意一点,带有 __context__ 的宏,只能在缓冲区中比较,在内核中最后仍然是使用没有 __context__ 这种属性的宏的,也就是说最终用到的是:
1
2
|
# define __acquire(x) (void)0 # define __release(x) (void)0 |