邪恶八进制信息安全团队技术讨论组's Archiver

pub!1c 2006-3-14 12:54

[转载]Linux 内核使用的 GNU C 扩展

<P>信息来源: Linuxforum</P>
<P><FONT size=2>===========================<BR>Linux 内核使用的 GNU C 扩展<BR>===========================<BR><BR>GNC CC 是一个功能非常强大的跨平台 C 编译器,它对 C 语言提供了很多扩展,<BR>这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。本文把<BR>支持 GNU 扩展的 C 语言称为 GNU C。<BR><BR>Linux 内核代码使用了大量的 GNU C 扩展,以至于能够编译 Linux 内核的唯一编<BR>译器是 GNU CC,以前甚至出现过编译 Linux 内核要使用特殊的 GNU CC 版本的情<BR>况。本文是对 Linux 内核使用的 GNU C 扩展的一个汇总,希望当你读内核源码遇<BR>到不理解的语法和语义时,能从本文找到一个初步的解答,更详细的信息可以查看<BR>gcc.info。文中的例子取自 Linux 2.4.18。<BR><BR><BR>语句表达式<BR>==========<BR><BR>GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出<BR>现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本<BR>只能在复合语句中使用。例如:<BR><BR>++++ include/linux/kernel.h<BR>159: #define min_t(type,x,y) \<BR>160:   ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })<BR>++++ net/ipv4/tcp_output.c<BR>654:   int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));<BR><BR>复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。<BR>这里定义了一个安全的求最小值的宏,在标准 C 中,通常定义为:<BR><BR>#define min(x,y) ((x) < (y) ? (x) : (y))<BR><BR>这个定义计算 x 和 y 分别两次,当参数有副作用时,将产生不正确的结果,使用<BR>语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。<BR><BR><BR>Typeof<BR>======<BR><BR>使用前一节定义的宏需要知道参数的类型,利用 typeof 可以定义更通用的宏,不<BR>必事先知道参数的类型,例如:<BR><BR>++++ include/linux/kernel.h<BR>141: #define min(x,y) ({ \<BR>142:   const typeof(x) _x = (x);   \<BR>143:   const typeof(y) _y = (y);   \<BR>144:   (void) (&_x == &_y);    \<BR>145:   _x < _y ? _x : _y; })<BR><BR>这里 typeof(x) 表示 x 的值类型,第 142 行定义了一个与 x 类型相同的局部变<BR>量 _x 并初使化为 x,注意第 144 行的作用是检查参数 x 和 y 的类型是否相同。<BR>typeof 可以用在任何类型可以使用的地方,通常用于宏定义。<BR><BR><BR>零长度数组<BR>==========<BR><BR>GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例<BR>如:<BR><BR>++++ include/linux/minix_fs.h<BR>85: struct minix_dir_entry {<BR>86:   __u16 inode;<BR>87:   char name[0];<BR>88: };<BR><BR>结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准 C 中则需要<BR>定义数组长度为 1,分配时计算对象大小比较复杂。<BR><BR><BR>可变参数宏<BR>==========<BR><BR>在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如:<BR><BR>++++ include/linux/kernel.h<BR>110: #define pr_debug(fmt,arg...) \<BR>111:   printk(KERN_DEBUG fmt,##arg)<BR><BR>这里 arg 表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构<BR>成 arg 的值,在宏扩展时替换 arg,例如:<BR><BR>  pr_debug("%s:%d",filename,line)<BR><BR>扩展为<BR><BR>  printk("<7>" "%s:%d", filename, line)<BR><BR>使用 ## 的原因是处理 arg 不匹配任何参数的情况,这时 arg 的值为空,GNU<BR>C 预处理器在这种特殊情况下,丢弃 ## 之前的逗号,这样<BR><BR>  pr_debug("success!\n")<BR><BR>扩展为<BR><BR>  printk("<7>" "success!\n")<BR><BR>注意最后没有逗号。<BR><BR><BR>标号元素<BR>========<BR><BR>标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通<BR>过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在<BR>初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式,<BR>例如:<BR><BR>+++++ arch/i386/kernel/irq.c<BR>1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };<BR><BR>将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。<BR><BR>要指定结构元素,在元素值前写 'FIELDNAME:',例如:<BR><BR>++++ fs/ext2/file.c<BR>41: struct file_operations ext2_file_operations = {<BR>42:   llseek:   generic_file_llseek,<BR>43:   read:   generic_file_read,<BR>44:   write:    generic_file_write,<BR>45:   ioctl:    ext2_ioctl,<BR>46:   mmap:   generic_file_mmap,<BR>47:   open:   generic_file_open,<BR>48:   release:  ext2_release_file,<BR>49:   fsync:    ext2_sync_file,<BR>50 };<BR><BR>将结构 ext2_file_operations 的元素 llseek 初始化为 generic_file_llseek,<BR>元素 read 初始化为 genenric_file_read,依次类推。我觉得这是 GNU C 扩展中<BR>最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然<BR>保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。<BR><BR><BR>Case 范围<BR>=========<BR><BR>GNU C 允许在一个 case 标号中指定一个连续范围的值,例如:<BR><BR>++++ arch/i386/kernel/irq.c<BR>1062:         case '0' ... '9': c -= '0'; break;<BR>1063:         case 'a' ... 'f': c -= 'a'-10; break;<BR>1064:         case 'A' ... 'F': c -= 'A'-10; break;<BR><BR>  case '0' ... '9':<BR><BR>相当于<BR><BR>  case '0': case '1': case '2': case '3': case '4':<BR>  case '5': case '6': case '7': case '8': case '9':<BR><BR><BR>声明的特殊属性<BR>==============<BR><BR>GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代<BR>码检查。要指定一个声明的属性,在声明后写<BR><BR>  __attribute__ (( ATTRIBUTE ))<BR><BR>其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持十几个属性,这<BR>里介绍最常用的:<BR><BR>* noreturn<BR><BR>属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的<BR>代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:<BR><BR>++++ include/linux/kernel.h<BR>47: # define ATTRIB_NORET__attribute__((noreturn)) ....<BR>61: asmlinkage NORET_TYPE void do_exit(long error_code)<BR>  ATTRIB_NORET;<BR><BR>* format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)<BR><BR>属性 format 用于函数,表示该函数使用 printf, scanf 或 strftime 风格的参<BR>数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以<BR>让编译器根据格式串检查参数类型。例如:<BR><BR>++++ include/linux/kernel.h?<BR>89: asmlinkage int printk(const char * fmt, ...)<BR>90:   __attribute__ ((format (printf, 1, 2)));<BR><BR>表示第一个参数是格式串,从第二个参数起根据格式串检查参数。<BR><BR>* unused<BR><BR>属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免<BR>编译器产生警告信息。<BR><BR>* section ("section-name")<BR><BR>属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在<BR>.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的<BR>节中。例如:<BR><BR>++++ include/linux/init.h<BR>78: #define __init    __attribute__ ((__section__ (".text.init")))<BR>79: #define __exit    __attribute__ ((unused, __section__(".text.exit")))<BR>80: #define __initdata  __attribute__ ((__section__ (".data.init")))<BR>81: #define __exitdata  __attribute__ ((unused, __section__ (".data.exit")))<BR>82: #define __initsetup __attribute__ ((unused,__section__ (".setup.init")))<BR>83: #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))<BR>84: #define __exit_call __attribute__ ((unused,__section__ (".exitcall.exit")))<BR><BR>连接器可以把相同节的代码或数据安排在一起,Linux 内核很喜欢使用这种技术,<BR>例如系统的初始化代码被安排在单独的一个节,在初始化结束后就可以释放这部分<BR>内存。<BR><BR>* aligned (ALIGNMENT)<BR><BR>属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对<BR>齐量,以字节为单位,例如:<BR><BR>++++ include/asm-i386/processor.h<BR>294: struct i387_fxsave_struct {<BR>295:   unsigned shortcwd;<BR>296:   unsigned shortswd;<BR>297:   unsigned shorttwd;<BR>298:   unsigned shortfop;<BR>299:   long  fip;<BR>300:   long  fcs;<BR>301:   long  foo;<BR>......<BR>308: } __attribute__ ((aligned (16)));<BR><BR>表示该结构类型的变量以 16 字节对齐。通常编译器会选择合适的对齐量,显示指<BR>定对齐通常是由于体系限制、优化等原因。<BR><BR>* packed<BR><BR>属性 packed 用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用<BR>于枚举、结构或联合类型时表示该类型使用最小的内存。例如:<BR><BR>++++ include/asm-i386/desc.h<BR>51: struct Xgt_desc_struct {<BR>52:   unsigned short size;<BR>53:   unsigned long address __attribute__((packed));<BR>54: };<BR><BR>域 address 将紧接着 size 分配。属性 packed 的用途大多是定义硬件相关的结<BR>构,使元素之间没有因对齐而造成的空洞。<BR><BR><BR>当前函数名<BR>==========<BR><BR>GNU CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码<BR>中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个<BR>名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外<BR>信息,Linux 内核只使用了 __FUNCTION__。<BR><BR>++++ fs/ext2/super.c<BR>98: void ext2_update_dynamic_rev(struct super_block *sb)<BR>99: {<BR>100:   struct ext2_super_block *es = EXT2_SB(sb)->s_es;<BR>101: <BR>102:   if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)<BR>103:     return;<BR>104: <BR>105:   ext2_warning(sb, __FUNCTION__,<BR>106:        "updating to rev %d because of new feature flag, "<BR>107:        "running e2fsck is recommended",<BR>108:        EXT2_DYNAMIC_REV);<BR><BR>这里 __FUNCTION__ 将被替换为字符串 "ext2_update_dynamic_rev"。虽然<BR>__FUNCTION__ 看起来类似于标准 C 中的 __FILE__,但实际上 __FUNCTION__<BR>是被编译器替换的,不象 __FILE__ 被预处理器替换。<BR><BR><BR>内建函数<BR>========<BR><BR>GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如<BR>memcpy,它们与对应的 C 库函数功能相同,本文不讨论这类函数,其他内建函数<BR>的名字通常以 __builtin 开始。<BR><BR>* __builtin_return_address (LEVEL)<BR><BR>内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数<BR>LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数<BR>的调用者的返回地址,依此类推。例如:<BR><BR>++++ kernel/sched.c<BR>437:     printk(KERN_ERR "schedule_timeout: wrong timeout "<BR>438:        "value %lx from %p\n", timeout,<BR>439:        __builtin_return_address(0));<BR><BR>* __builtin_constant_p(EXP)<BR><BR>内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数<BR>EXP 的值是常数,函数返回 1,否则返回 0。例如:<BR><BR>++++ include/asm-i386/bitops.h<BR>249: #define test_bit(nr,addr) \<BR>250: (__builtin_constant_p(nr) ? \<BR>251:constant_test_bit((nr),(addr)) : \<BR>252:variable_test_bit((nr),(addr)))<BR><BR>很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以<BR>根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在<BR>参数是常数时编译出最优化的代码。<BR><BR>* __builtin_expect(EXP, C)<BR><BR>内建函数 __builtin_expect 用于为编译器提供分支预测信息,其返回值是整数表<BR>达式 EXP 的值,C 的值必须是编译时常数。例如:<BR><BR>++++ include/linux/compiler.h<BR>13: #define likely(x)   __builtin_expect((x),1)<BR>14: #define unlikely(x) __builtin_expect((x),0)<BR>++++ kernel/sched.c<BR>564:   if (unlikely(in_interrupt())) {<BR>565:     printk("Scheduling in interrupt\n");<BR>566:     BUG();<BR>567:   }<BR><BR>这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排<BR>语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中<BR>断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证<BR>经常执行的目标码更紧凑。</FONT><BR></P>

页: [1]
© 1999-2008 EvilOctal Security Team