GNC CC是一个功能非常强大的跨平台C编译器,它对C 语言提供了很多扩展,这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。本文把支持GNU 扩展的C 语言称为GNU C。

Linux 内核代码使用了大量的 GNU C 扩展,以至于能够编译 Linux 内核的唯一编译器是 GNU CC,以前甚至出现过编译 Linux 内核要使用特殊的 GNU CC 版本的情况。本文是对 Linux 内核使用的 GNU C 扩展的一个汇总,希望当你读内核源码遇到不理解的语法和语义时,能从本文找到一个初步的解答,更详细的信息可以查看gcc.info。文中的例子取自 Linux 2.4.18。

语句表达式

GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本只能在复合语句中使用。例如:

++++ include/linux/kernel.h

159: #define min_t(type,x,y) /

160: ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })

++++ net/ipv4/tcp_output.c

654: int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));

复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。这里定义了一个安全的求最小值的宏,在标准 C 中,通常定义为:

#define min(x,y) ((x) < (y) ? (x) : (y))

这个定义计算 x 和 y 分别两次,当参数有副作用时,将产生不正确的结果,使用语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。

Typeof

使用前一节定义的宏需要知道参数的类型,利用 typeof 可以定义更通用的宏,不必事先知道参数的类型,例如:

++++ include/linux/kernel.h

141: #define min(x,y) ({ /

142: const typeof(x) _x = (x); /

143: const typeof(y) _y = (y); /

144: (void) (&_x == &_y); /

145: _x < _y ? _x : _y; })

这里 typeof(x) 表示 x 的值类型,第 142 行定义了一个与 x 类型相同的局部变量 _x 并初使化为 x,注意第 144 行的作用是检查参数 x 和 y 的类型是否相同。typeof 可以用在任何类型可以使用的地方,通常用于宏定义。

零长度数组

GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如:

++++ include/linux/minix_fs.h

85: struct minix_dir_entry {

86: __u16 inode;

87: char name[0];

88: };

结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准 C 中则需要定义数组长度为 1,分配时计算对象大小比较复杂。

可变参数宏

在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如:

++++ include/linux/kernel.h

110: #define pr_debug(fmt,arg...) /

111: printk(KERN_DEBUG fmt,##arg)

这里 arg 表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构成 arg 的值,在宏扩展时替换 arg,例如:

pr_debug("%s:%d",filename,line)

扩展为

printk("<7>" "%s:%d", filename, line)

使用 ## 的原因是处理 arg 不匹配任何参数的情况,这时 arg 的值为空,GNUC 预处理器在这种特殊情况下,丢弃 ## 之前的逗号,这样

pr_debug("success!/n")

扩展为

printk("<7>" "success!/n")

注意最后没有逗号。

标号元素

标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式,例如:

+++++ arch/i386/kernel/irq.c

1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };

将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。要指定结构元素,在元素值前写 'FIELDNAME:',例如:

++++ fs/ext2/file.c

41: struct file_operations ext2_file_operations = {

42: llseek: generic_file_llseek,

43: read: generic_file_read,

44: write: generic_file_write,

45: ioctl: ext2_ioctl,

46: mmap: generic_file_mmap,

47: open: generic_file_open,

48: release: ext2_release_file,

49: fsync: ext2_sync_file,

50 };

将结构 ext2_file_operations 的元素 llseek 初始化为 generic_file_llseek,元素 read 初始化genenric_file_read,依次类推。我觉得这是 GNU C 扩展中最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。

Case 范围

GNU C 允许在一个 case 标号中指定一个连续范围的值,例如:

++++ arch/i386/kernel/irq.c

1062: case '0' ... '9': c -= '0'; break;

1063: case 'a' ... 'f': c -= 'a'-10; break;

1064: case 'A' ... 'F': c -= 'A'-10; break;

case '0' ... '9':

相当于

case '0': case '1': case '2': case '3': case '4':

case '5': case '6': case '7': case '8': case '9':

声明的特殊属性

GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代码检查。要指定一个声明的属性,在声明后写

__attribute__ (( ATTRIBUTE ))

其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持十几个属性,这里介绍最常用的:

* noreturn

属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:

++++ include/linux/kernel.h

47: # define ATTRIB_NORET __attribute__((noreturn)) ....

61: asmlinkage NORET_TYPE void do_exit(long error_code)

ATTRIB_NORET;

* format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)

属性 format 用于函数,表示该函数使用 printf, scanf 或 strftime 风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以让编译器根据格式串检查参数类型。例如:

++++ include/linux/kernel.h?

89: asmlinkage int printk(const char * fmt, ...)

90: __attribute__ ((format (printf, 1, 2)));

表示第一个参数是格式串,从第二个参数起根据格式串检查参数。

* unused

属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。

* section ("section-name")

属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的节中。例如:

++++ include/linux/init.h

78: #define __init __attribute__ ((__section__ (".text.init")))

79: #define __exit __attribute__ ((unused, __section__(".text.exit")))

80: #define __initdata __attribute__ ((__section__ (".data.init")))

81: #define __exitdata __attribute__ ((unused, __section__ (".data.exit")))

82: #define __initsetup __attribute__ ((unused,__section__ (".setup.init")))

83: #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))

84: #define __exit_call __attribute__ ((unused,__section__ (".exitcall.exit")))

连接器可以把相同节的代码或数据安排在一起,Linux 内核很喜欢使用这种技术,例如系统的初始化代码被安排在单独的一个节,在初始化结束后就可以释放这部分内存。

* aligned (ALIGNMENT)

属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对齐量,以字节为单位,例如:

++++ include/asm-i386/processor.h

294: struct i387_fxsave_struct {

295: unsigned short cwd;

296: unsigned short swd;

297: unsigned short twd;

298: unsigned short fop;

299: long fip;

300: long fcs;

301: long foo;

......

308: } __attribute__ ((aligned (16)));

表示该结构类型的变量以 16 字节对齐。通常编译器会选择合适的对齐量,显示指定对齐通常是由于体系限制、优化等原因。

* packed

属性 packed 用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。例如:

++++ include/asm-i386/desc.h

51: struct Xgt_desc_struct {

52: unsigned short size;

53: unsigned long address __attribute__((packed));

54: };

域 address 将紧接着 size 分配。属性 packed 的用途大多是定义硬件相关的结构,使元素之间没有因对齐而造成的空洞。

当前函数名

GNU CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码中的名字__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外信息,Linux 内核只使用了 __FUNCTION__。

++++ fs/ext2/super.c

98: void ext2_update_dynamic_rev(struct super_block *sb)

99: {

100: struct ext2_super_block *es = EXT2_SB(sb)->s_es;

101:

102: if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)

103: return;

104:

105: ext2_warning(sb, __FUNCTION__,

106: "updating to rev %d because of new feature flag, "

107: "running e2fsck is recommended",

108: EXT2_DYNAMIC_REV);

这里 __FUNCTION__ 将被替换为字符串 "ext2_update_dynamic_rev"。虽然__FUNCTION__ 看起来类似于标准 C 中的 __FILE__,但实际上 __FUNCTION__是被编译器替换的,不象 __FILE__ 被预处理器替换。

内建函数

GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如memcpy,它们与对应的 C 库函数功能相同,本文不讨论这类函数,其他内建函数的名字通常以 __builtin 开始。

* __builtin_return_address (LEVEL)

内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址,依此类推。例如:

.

++++ kernel/sched.c

437: printk(KERN_ERR "schedule_timeout: wrong timeout "

438: "value %lx from %p/n", timeout,

439: __builtin_return_address(0));

* __builtin_constant_p(EXP)

内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0。例如:

++++ include/asm-i386/bitops.h

249: #define test_bit(nr,addr) /

250: (__builtin_constant_p(nr) ? /

251: constant_test_bit((nr),(addr)) : /

252: variable_test_bit((nr),(addr)))

很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。

* __builtin_expect(EXP, C)

内建函数 __builtin_expect 用于为编译器提供分支预测信息,其返回值是整数表达式 EXP 的值,C 的值必须是编译时常数。例如:

++++ include/linux/compiler.h

13: #define likely(x) __builtin_expect((x),1)

14: #define unlikely(x) __builtin_expect((x),0)

++++ kernel/sched.c

564: if (unlikely(in_interrupt())) {

565: printk("Scheduling in interrupt/n");

566: BUG();

567: }

这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑.

阅读(133) | 评论(0) | 转发(0) |

linux内核能否扩展,Linux内核用到的GCC扩展相关推荐

  1. linux内核网络协议栈--linux bridge(十九)

    1 . 前言 本文是参考附录上的资料整理而成,以帮助读者更好的理解kernel中brdige 模块代码. 2. 网桥的原理 2.1 桥接的概念 简单来说,桥接就是把一台机器上的若干个网络接口" ...

  2. linux内核启动后门,Linux内核模块入门之简单内核后门

    内核模块简介 Linux内核支持运行时动态扩展,即运行时动态加载内核扩展模块(.ko文件),ko文件所包含的代码经加载后即成为内核代码的一部分,拥有内核特权,可以调用内核其它组件,访问内核空间数据以及 ...

  3. 初探内核之《Linux内核设计与实现》笔记上

    内核简介  本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核   原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单. 2. 高 ...

  4. 戴文的Linux内核专题:07内核配置(3)

    转自Linux中国 OK,我们还继续配置内核.还有更多功能等待着去配置. 下一个问题(Enable ELF core dumps (ELF_CORE))询问的是内核是否可以生成内核转储文件.这会使内核 ...

  5. Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7) 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4938388.html   研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了 ...

  6. Linux内核写入s3c2440,Linux空板的系统写入

    空板的Linux写入完整操作过程: 1. boot的写入 连接硬件,把JTAG插头插入板子上的JL3上,注意1角的标志 在命令行下,输入: SJF2440.exe /F:u-boot.bin (1)选 ...

  7. 红帽linux内核修复,红帽Linux 7和CentOS 7的新Linux内核更新修复两个错误

    红帽安全团队发布了一个新的Linux内核更新,该更新解决了所有受支持的Red Hat Enterprise Linux 7系统的Linux 3.10内核软件包中的两个重要错误. 新的Linux内核更新 ...

  8. [转]Linux 2.6.19.x 内核编译配置选项简介

    Linux 2.6.19.x 内核编译配置选项简介 作者:金步国,转载地址:http://lamp.linux.gov.cn/Linux/kernel_options.html 版权声明 本文作者是一 ...

  9. linux内核之alsa,Linux操作系统Alsa音频编程

    一.前序 这里了解一下各个参数的含义以及一些基本概念. 声音是连续模拟量,计算机将它离散化之后用数字表示,就有了以下几个名词术语. 样本长度(sample):样本是记录音频数据最基本的单位,计算机对每 ...

  10. Windows程序员初学Linux内核(附Linux内核各版本历史纪年表)

    我是荔园微风,作为一名在IT界整整25年的老兵,最近受邀给年轻人讲了一场Windows内核和Linux内核相关的讲座.大家听得非常认真.下面我把其中一些PPT放上来和大家分享. Windows内核(右 ...

最新文章

  1. AI 盯上了外包司机,看后视镜就被扣分,奖金拜拜!
  2. Unity 播放音频文件
  3. cad无法加载arx文件_多年经验总结CAD技巧8
  4. windows10下配置环境变量
  5. Fluent NHibernate关系映射
  6. deepin安装卡死在蓝色背景_求大神帮助~安装DEEPIN系统卡在蓝色背景图什么原因...
  7. deprecated_@Deprecated新外观可能是什么?
  8. Win10网络共享设置的方法步骤
  9. 【python教程入门学习】零基础想转行学python,过来人提醒大家几点
  10. android 7.0 黑屏,Android app启动是出现白屏或者黑屏如何解决?
  11. CSS基础——CSS 三大特性【学习笔记】
  12. 2019工作榜单 | Python程序员吸金榜,AI排第一,这个我服!
  13. 6线AB相马达与ARDUINO 和 L298N电机之间
  14. java语言判断101到200之间素数
  15. ppt文件太大怎么压缩?
  16. mentohust for linux,GitHub - xdlinux/mentohust_xidian: A ruijie client for xidian university
  17. C++MYSQL:获取表结构:MYSQL_FEILD
  18. javascript中实现点赞
  19. linux基本功系列之uname实战
  20. 关于IE系列浏览器对URL的兼容性处理

热门文章

  1. Python三元运算
  2. Python列表元素操作相关的2个函数和匿名函数
  3. 手写Python中列表和字符串的反转
  4. android 设置窗口透明效果,android - 如何将对话框窗口背景设置为透明,而不影响其边距...
  5. source环境变量命令千万不能乱用
  6. linux ubuntu16.04 Clion 安装及快捷方式创建
  7. python matplotlib.pyplot 如何实时绘制三维动态窗口?(可鼠标拖动角度)
  8. 为什么python中不需要变量名和变量类型声明?
  9. 线段检测网格检测(LCNN)的wireframe数据集标注、转换问题
  10. RocketMQ(三):基本原理和架构