Linux 内核使用的 GNU C 扩展
gcc核心扩展linuxforum(转)===========================
Linux 内核使用的 GNU C 扩展
=========================== 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.。 语句表达式
========== GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出
现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本
只能在复合语句中使用。例如: ++++ include/linux/kernel.h
: #define min_t(type,x,y)
: ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
++++ net/ipv4/tcp_output.c
: 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
: #define min(x,y) ({
: const typeof(x) _x = (x);
: const typeof(y) _y = (y);
: (void) (&_x == &_y);
: _x < _y ? _x : _y; }) 这里 typeof(x) 表示 x 的值类型,第 行定义了一个与 x 类型相同的局部变
量 _x 并初使化为 x,注意第 行的作用是检查参数 x 和 y 的类型是否相同。
typeof 可以用在任何类型可以使用的地方,通常用于宏定义。 零长度数组
========== GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例
如: ++++ include/linux/minix_fs.h
: struct minix_dir_entry {
: __u16 inode;
: char name[];
: }; 结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准 C 中则需要
定义数组长度为 ,分配时计算对象大小比较复杂。 可变参数宏
========== 在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如: ++++ include/linux/kernel.h
: #define pr_debug(fmt,arg...)
: printk(KERN_DEBUG fmt,##arg) 这里 arg 表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构
成 arg 的值,在宏扩展时替换 arg,例如: pr_debug("%s:%d",filename,line) 扩展为 printk("<7>" "%s:%d", filename, line) 使用 ## 的原因是处理 arg 不匹配任何参数的情况,这时 arg 的值为空,GNU
C 预处理器在这种特殊情况下,丢弃 ## 之前的逗号,这样 pr_debug("success! ") 扩展为 printk("<7>" "success! ") 注意最后没有逗号。 标号元素
======== 标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通
过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在
初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式,
例如: +++++ arch/i386/kernel/irq.c
: static unsigned long irq_affinity [NR_IRQS] = { [ ... NR_IRQS-] = ~0UL }; 将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。 要指定结构元素,在元素值前写 'FIELDNAME:',例如: ++++ fs/ext2/file.c
: struct file_operations ext2_file_operations = {
: llseek: generic_file_llseek,
: read: generic_file_read,
: write: generic_file_write,
: ioctl: ext2_ioctl,
: mmap: generic_file_mmap,
: open: generic_file_open,
: release: ext2_release_file,
: fsync: ext2_sync_file,
}; 将结构 ext2_file_operations 的元素 llseek 初始化为 generic_file_llseek,
元素 read 初始化为 genenric_file_read,依次类推。我觉得这是 GNU C 扩展中
最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然
保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 。 Case 范围
========= GNU C 允许在一个 case 标号中指定一个连续范围的值,例如: ++++ arch/i386/kernel/irq.c
: case '' ... '': c -= ''; break;
: case 'a' ... 'f': c -= 'a'-; break;
: case 'A' ... 'F': c -= 'A'-; break; case '' ... '': 相当于 case '': case '': case '': case '': case '':
case '': case '': case '': case '': case '': 声明的特殊属性
============== GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代
码检查。要指定一个声明的属性,在声明后写 __attribute__ (( ATTRIBUTE )) 其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持十几个属性,这
里介绍最常用的: * noreturn 属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的
代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如: ++++ include/linux/kernel.h
: # define ATTRIB_NORET __attribute__((noreturn)) ....
: 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?
: asmlinkage int printk(const char * fmt, ...)
: __attribute__ ((format (printf, , ))); 表示第一个参数是格式串,从第二个参数起根据格式串检查参数。 * unused 属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免
编译器产生警告信息。 * section ("section-name") 属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在
.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的
节中。例如: ++++ include/linux/init.h
: #define __init __attribute__ ((__section__ (".text.init")))
: #define __exit __attribute__ ((unused, __section__(".text.exit")))
: #define __initdata __attribute__ ((__section__ (".data.init")))
: #define __exitdata __attribute__ ((unused, __section__ (".data.exit")))
: #define __initsetup __attribute__ ((unused,__section__ (".setup.init")))
: #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
: #define __exit_call __attribute__ ((unused,__section__ (".exitcall.exit"))) 连接器可以把相同节的代码或数据安排在一起,Linux 内核很喜欢使用这种技术,
例如系统的初始化代码被安排在单独的一个节,在初始化结束后就可以释放这部分
内存。 * aligned (ALIGNMENT) 属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对
齐量,以字节为单位,例如: ++++ include/asm-i386/processor.h
: struct i387_fxsave_struct {
: unsigned short cwd;
: unsigned short swd;
: unsigned short twd;
: unsigned short fop;
: long fip;
: long fcs;
: long foo;
......
: } __attribute__ ((aligned ())); 表示该结构类型的变量以 字节对齐。通常编译器会选择合适的对齐量,显示指
定对齐通常是由于体系限制、优化等原因。 * packed 属性 packed 用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用
于枚举、结构或联合类型时表示该类型使用最小的内存。例如: ++++ include/asm-i386/desc.h
: struct Xgt_desc_struct {
: unsigned short size;
: unsigned long address __attribute__((packed));
: }; 域 address 将紧接着 size 分配。属性 packed 的用途大多是定义硬件相关的结
构,使元素之间没有因对齐而造成的空洞。 当前函数名
========== GNU CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码
中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个
名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外
信息,Linux 内核只使用了 __FUNCTION__。 ++++ fs/ext2/super.c
: void ext2_update_dynamic_rev(struct super_block *sb)
: {
: struct ext2_super_block *es = EXT2_SB(sb)->s_es;
:
: if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)
: return;
:
: ext2_warning(sb, __FUNCTION__,
: "updating to rev %d because of new feature flag, "
: "running e2fsck is recommended",
: 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 指定在栈上搜索框架的个数, 表示当前函数的返回地址, 表示当前函数
的调用者的返回地址,依此类推。例如: ++++ kernel/sched.c
: printk(KERN_ERR "schedule_timeout: wrong timeout "
: "value %lx from %p ", timeout,
: __builtin_return_address()); * __builtin_constant_p(EXP) 内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数
EXP 的值是常数,函数返回 ,否则返回 。例如: ++++ include/asm-i386/bitops.h
: #define test_bit(nr,addr)
: (__builtin_constant_p(nr) ?
: constant_test_bit((nr),(addr)) :
: variable_test_bit((nr),(addr))) 很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以
根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在
参数是常数时编译出最优化的代码。 * __builtin_expect(EXP, C) 内建函数 __builtin_expect 用于为编译器提供分支预测信息,其返回值是整数表
达式 EXP 的值,C 的值必须是编译时常数。例如: ++++ include/linux/compiler.h
: #define likely(x) __builtin_expect((x),1)
: #define unlikely(x) __builtin_expect((x),0)
++++ kernel/sched.c
: if (unlikely(in_interrupt())) {
: printk("Scheduling in interrupt ");
: BUG();
: } 这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排
语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中
断上下文是很少发生的,第 - 行的目标码可能会放在较远的位置,以保证
经常执行的目标码更紧凑。
Linux 内核使用的 GNU C 扩展的更多相关文章
- 嵌入式C语言自我修养 01:Linux 内核中的GNU C语言语法扩展
1.1 Linux 内核驱动中的奇怪语法 大家在看一些 GNU 开源软件,或者阅读 Linux 内核.驱动源码时会发现,在 Linux 内核源码中,有大量的 C 程序看起来“怪怪的”.说它是C语言吧, ...
- linux 内核邮件列表
第一节 - 一般性问题 1. 为什么有些时候使用“GNU/Linux"而另一些时候使用“Linux”? 答:在这个FAQ中,我们尝试使用“linux”或者“linux kernel”来表示内 ...
- Linux内核知识
版本 linus树 Linux内核最初创始人--Linus Torvalds管理一个Linus树,linus树也称为主线(mainline).一般指的upstream,"上游",也 ...
- Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)
Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...
- linux内核中GNU C和标准C的区别
linux内核中GNU C和标准C的区别 今天看了一下午的linux内核编程方面的内容,发现linux 内核中GNU C与标准C有一些差别,特记录如下: linux 系统上可用的C编译器是GNU C编 ...
- Linux从用户层到内核层系列 - GNU系列之glibc介绍
题记:本系列文章的目的是抛开书本从源代码和使用的角度分析Linux内核和相关源代码,byhankswang和你一起玩转linux开发 轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswa ...
- Linux内核版本 uname命令 GNU项目 Linux发行版
1.内核版本由linux内核社区统一编码和发布,格式如下图: major.minor.patch-build.desc 主版本号.次版本号.对次版本号的修订次数-编译次数.当前版本的特殊信息 次版本号 ...
- linux内核——PAE(物理地址扩展)
引入PAE机制后,分页模式是怎样的呢? 首先,要搞明白几件事,2.6.11以上版本的linux内核中,存在4中页表(页全局目录,页上级目录,页中级目录,页表),这些页表结构是已经存在于硬盘中的,当进程 ...
- Linux 内核概述 - Linux Kernel
Linux 内核学习笔记整理. Unix unix 已有40历史,但计算机科学家仍认为其是现存操作系统中最大和最优秀的系统,它已成为一种传奇的存在,历经时间的考验却依然声名不坠. 1973 年,在用 ...
随机推荐
- C语言写解一元二次方程程序心得
前言:在网上看到不少解一元二次方程的小程序,在使用时总得出一大堆小数,感觉很不爽,遂自己重新写了一遍. 首先,先回忆一下一元二次方程的求根公式: 分别读取二次项.一次项和常数项系数并且求出delta ...
- javascript之事件
客户端javascript程序采用了异步事件驱动编程模型. 相关事件的几个概念: 事件类型(event type):用来说明发生什么类型事件的字符串: 事件目标(event target):发生事件的 ...
- Accessing Scoped Variables
To permit the JSP page to access the data, the servlet needs to use setAttribute to store the data i ...
- 一个Bootstrap风格的分页控件
http://www.cnblogs.com/wangwei123/p/3682626.html 主题 jQueryBootstrap 一个Bootstrap风格的分页控件,对于喜欢Bootstr ...
- IEEE二进制浮点数算术标准(IEEE 754)
整理自IEEE 754 IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用.这个标准定义了表示浮点数的格式(包括负零-0) ...
- Windows Xp Home Edition 安装IIS组件
问题描述: 在虚拟机(操作系统是Windows Xp Home Edition)中安装Sql Server 2005的时候警告缺少IIS相关组件,控制面板"添加/删除组件"中也没有 ...
- 子查询解嵌套in改写为exists
SELECT * FROM (SELECT pubformdat0_.id id332_, pubformdat0_.domain_id domain2_332_, pubformdat0_.proc ...
- 【转】virtualbox 4.08安装虚机Ubuntu11.04增强功能失败解决方法
原文网址:http://fuliang.iteye.com/blog/1102998 在笔记本安装Ubuntu11.04增强功能失败 引用 fuliang@fuliang-VirtualBox:~$ ...
- 查看当前hadoop的版本号
查看当前hadoop的版本号: 2015-01-20 20:58:03
- 参考SQLHelper编写的OracleHelper
使用 Oracle.ManagedDataAccess.Client 类库参考SQLHelper编写的OracleHelper: // ================================ ...