GCC扩展(转--对看kernel代码有帮助

Linux Kernel的代码,上次就发现一个结构体的定义形式看不懂,后来才知道它用的不是标准的ANSI C,而是GCC的一些扩展。刚好看到《Linux内核修炼之道》中对GCC扩展有所描述,转载一下吧,对看kernel代码有所帮助。
3.5内核代码的特点

Linux内核同时使用C语言和汇编语言实现,C语言编写的代码移植性较好、易于维护,而汇编语言编写的代码相当于针对特定的平台做了优化,速度较快,所以需要在它们之间寻找一定的平衡。

一般而言,在体系结构的底层或对执行时间要求严格的地方,会使用汇编语言,比如中断和异常处理的底层代码,初始化有关的部分代码等。内核其他部分则是用C语言编写。

3.5.1 GCC扩展(1)

Linux内核必须使用GNU的GCC编译器来编译,而GCC提供了很多的C语言扩展,这些扩展对优化、目标代码布局、更安全的检查等提供了很强的支持。因此,内核代码所使用的C语法并不完全符合ANSI C标准,实际上,只要有可能,内核开发者总是要用到GCC提供的C语言扩展部分。

这就为我们学习内核增加了一定的困难,因此,为了能够比较顺利地阅读内核代码,有必要对GCC扩展进行了解。

下面详细介绍Linux内核中常出现的、主要的GCC扩展。

1.语句表达式(statement-embedded expression)

GCC把包含在括号中的复合语句看作是一个表达式,称为语句表达式,它允许在一个表达式内使用循环、跳转、局部变量,并可以出现在任何允许表达式出现的地方。

位于括号中的复合语句的最后一句必需是一个以分号结尾的表达式,它的值将成为这个语句表达式的值。

计算最大值和最小值通常被定义为:

#define max(x, y) ((x) > (y) ? (x) : (y))  

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

但是其中的x和y可能会分别被计算两次。当参数x和y带有副作用时,将会产生错误的结果。而内核则使用语句表达式将其定义为:

++++ include/linux/kernel.h  

305 #define min_t(type,x,y) \  

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

307 #define max_t(type,x,y) \  

308         ({ type __x = (x); type __y = (y); __x > __y ? __x: __y; }) 

相比较来看,使用语句表达式只计算参数一次,避免了可能的错误。

如果对typeof加以利用,还可以定义更为通用的宏,针对最大值最小值的计算,内核的另外一种定义为:

++++ include/linux/kernel.h  

287 #define min(x,y) ({ \  

288         typeof(x) _x = (x); \  

289         typeof(y) _y = (y); \  

290         (void) (&_x == &_y);\  

291         _x < _y ? _x : _y; })  

292  

293 #define max(x,y) ({ \  

294         typeof(x) _x = (x); \  

295     typeof(y) _y = (y); \  

296     (void) (&_x == &_y);\  

297     _x > _y ? _x : _y; }) 

其中typeof(x)表示x的类型,第294行定义了一个与x类型相同的局部变量_x,并将其初始化为x,注意第290行的作用是检查参数x和y的类型是否相同。

2.零长度数组(flexible array)

在内核代码里经常出现类似下面的结构。

++++ include/linux/usb  

199 struct usb_interface_cache {  

200     unsigned num_altsetting;    /* number of alternate settings */  

201     struct kref ref;        /* reference counter */  

202  

203     /* variable-length array of alternate settings for this interface,  

204      * stored in no particular order */  

205     struct usb_host_interface altsetting[0];  

206 }; 

结构的最后一个元素usb_host_interface altsetting[0]就是GCC所谓的零长度数组,也可以称之为可变长数组,它并不占用结构的空间,但它意味着这个结构的长度充满了变数。创建该结构对象时,可以根据实际的需要指定这个可变长数组的长度,并分配相应的空间。

3.可变参数宏

1999年的ISO C标准里规定了可变参数宏,语法和函数类似,比如:

#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__) 

其中的"…"表示可变参数,实际调用时,它们会替代宏体里的__VA_ARGS__。GCC支持更复杂的形式,可以给可变参数取个名字,如下所示。

#define debug(format, args...) fprintf (stderr, format, args) 

有了名字之后,代码显得更具有可读性。内核中的例子为:

++++ include/linux/kernel.h  

244 #define pr_info(fmt,arg...) \  

245     printk(KERN_INFO fmt,##arg) 

其中的pr_info和上面的debug形式除了"##"外,几近相同。"##"主要针对参数为空的情况。既然称为可变参数,那传递空参数也是可以的。如果没有使用"##",传递空参数时,比如:

debug ("A message"); 

宏展开后,其中的字符串后面会多个多余的逗号,而"##"则会使预处理器去掉这个多余的逗号。

4.标号元素

在标准C里,数组或结构变量的初始化值必须以固定的顺序出现,而在GCC中,通过指定索引或结构域名,则允许初始化值以任意顺序出现。

指定数组索引的方法是在初始化值前写"[INDEX] =",还可以使用"[FIRST ... LAST] ="的形式指定一个范围,比如:

++++ arch/ia64/kernel/acpi.c  

132 int platform_intr_list[ACPI_MAX_PLATFORM_INTERRUPTS] = {  

133     [0 ... ACPI_MAX_PLATFORM_INTERRUPTS - 1] = -1  

134 }; 

将数组platform_intr_list的任何元素都初始化为-1。

对于结构初始化,比如:

++++ fs/ext2/file.c  

42 const struct file_operations ext2_file_operations = {  

43      .llseek     = generic_file_llseek,  

44      .read       = do_sync_read,  

45      .write      = do_sync_write,  

46      .aio_read   = generic_file_aio_read,  

47      .aio_write  = generic_file_aio_write,  

48      .ioctl      = ext2_ioctl,  

49 #ifdef CONFIG_COMPAT  

50      .compat_ioctl   = ext2_compat_ioctl,  

51 #endif  

52      .mmap       = generic_file_mmap,  

53      .open       = generic_file_open,  

54      .release    = ext2_release_file,  

55      .fsync      = ext2_sync_file,  

56      .splice_read    = generic_file_splice_read,  

57      .splice_write   = generic_file_splice_write,  

58 }; 

将结构ext2_file_operations的元素llseek初始化为generic_file_llseek,元素rea初始化为genenric_file_read,依次类推。使用这种形式,当结构体的定义变化导致元素的偏移位置改变时,仍然可以确保已知元素的正确性。对于未出现在初始化中的元素,其初值为0。

3.5.1 GCC扩展(2)

5.特殊属性(__attribute__)

GCC允许声明函数、变量和类型的特殊属性,以便指示编译器进行特定方面的优化和更仔细的代码检查。使用方式为在声明后加上:

attribute__ (( ATTRIBUTE )) 

其中ATTRIBUTE是属性的说明,多个说明之间以逗号分隔。GCC可以支持十几个属性,下面介绍一些比较常用的。

noreturn 

属性noreturn用于函数,表示该函数从不返回。它能够让编译器生成较为优化的代码,消除不必要的警告信息。

format(archetype, string-index, first-to-check) 

属性format用于函数,表示该函数使用printf、scanf、strftime或strfmon风格的参数,并可以让编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。

archetype指定是哪种风格,string-index指定传入函数的第几个参数是格式化字符串,first-to-check指定从函数的第几个参数开始按照上述规则进行检查。比如:

++++ include/linux/kernel.h  

164 static inline int printk(const char *s, ...)  

165     __attribute__ ((format (printf, 1, 2))); 

表示printk的第一个参数是格式化字符串,从第二个参数开始根据格式化字符串检查参数。

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

section ("section-name") 

属性section用于函数和变量,比如:

++++ include/linux/init.h  

43 #define __init       __attribute__
((__section__ (".init.text"))) __cold  

44 #define __initdata   __attribute__
((__section__ (".init.data")))  

45 #define __exitdata   __attribute__
((__section__(".exit.data")))  

46 #define __exit_call  __attribute_used__ __
attribute__ ((__section__ (".exitcall. exit"))) 

通常编译器将函数放在.text节,变量放在.data或.bss节,而使用section属性,可以让编译器将函数或变量放在指定的节中。因此上面对__init的定义便表示将__init修饰的代码放在.init.text节。

连接器可以把相同节的代码或数据安排在一起,Linux内核很喜欢使用这种技术,比如__init修饰的所有代码都会被放在.init.text节里,初始化结束后就可以释放这部分内存。

aligned (ALIGNMENT) 

属性aligned用于变量、结构或联合,设定一个指定大小的对齐格式,以字节为单位,比如:

++++ drivers/usb/host/ohci.h  

181 struct ohci_hcca {  

182 #define NUM_INTS 32  

183     __hc32  int_table [NUM_INTS];   /* periodic schedule */  

184  

185     /*  

186      * OHCI defines u16 frame_no, followed by u16 zero pad.  

187      * Since some processors can‘t do 16 bit bus accesses,  

188      * portable access must be a 32 bits wide.  

189      */  

190     __hc32  frame_no;       /* current frame number */  

191     __hc32  done_head;      /* info returned for an interrupt */  

192     u8  reserved_for_hc [116];  

193     u8  what [4];       /* spec only identifies 252 bytes :) */  

194 } __attribute__ ((aligned(256))); 

表示结构体ohci_hcca的成员以256字节对齐。

如果aligned后面不紧跟一个指定的数字值,那么编译器将依据目标机器情况使用最大、最有益的对齐方式。

需要注意的是,attribute属性的效果与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么此时定义32字节对齐也是无济于事的。

packed 

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

属性packed多用于定义硬件相关的结构时,使元素之间不会因对齐产生问题。比如:

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

295 struct usb_interface_descriptor {  

296     __u8  bLength;  

297     __u8  bDescriptorType;  

298  

299     __u8  bInterfaceNumber;  

300     __u8  bAlternateSetting;  

301     __u8  bNumEndpoints;  

302     __u8  bInterfaceClass;  

303     __u8  bInterfaceSubClass;  

304     __u8  bInterfaceProtocol;  

305     __u8  iInterface;  

306 } __attribute__ ((packed)); 

其中__attribute__ ((packed))告诉编译器,usb_interface_descriptor的元素为1字节对齐,不要再添加填充位。因为这个结构的定义和usb spec里是完全一致的,包括各个字段的长度,如果不给编译器这个暗示,编译器就会依据平台的类型在结构的每个元素之间添加一定的填充位,使用这个结构时就不能达到预期的结果。

6.内建函数

GCC提供了大量的内建函数,其中很多是标准C库函数的内建版本,比如memcpy,它们与对应的C库函数功能相同,这里就不再进行介绍。

还有很多内建函数的名字通常以__builtin开始,下面只对__builtin_expect进行分析并示例,其他__builtin_xxx函数的分析可参考这个分析过程。

__builtin_expect (long exp, long c) 

遇到这类很陌生的使用方式,GCC参考手册是我们最好的参考资料。下面是GCC参考手册里对__builtin_expect的介绍。

long __builtin_expect (long exp, long c)  

You may use __builtin_expect to provide the compiler
with branch prediction information. In general, you
should prefer to use actual profile feedback for
this (‘-fprofile-arcs‘), as programmers are
notoriously bad at predicting how their programs
actually perform. However, there are applications
in which this data is hard to collect.  

The return value is the value of exp, which should be
an integral expression. The value of c must be a
compile-time constant. The semantics of the
built-in are that it is expected that exp == c. For example:  

if (__builtin_expect (x, 0))  

    foo ();  

would indicate that we do not expect to call foo,
since we expect x to be zero. Since  

you are limited to integral expressions for exp,
you should use constructions such  

as  

if (__builtin_expect (ptr != NULL, 1))  

    error ();  

when testing pointer or floating-point values. 

大意是,由于大部分代码在分支预测方面做的比较糟糕,所以GCC提供了这个内建的函数来帮助处理分支预测、优化程序。它的第一个参数exp为一个整型的表达式,返回值也是这个exp。它的第二个参数c的值必须是一个编译期的常量。这个内建函数的意思就是exp的预期值为c,编译器可以根据这个信息适当地重排条件语句块的顺序,将符合这个条件的分支放在合适的地方。

具体到内核里,比如:

++++ include/linux/compiler.h  

60 #define likely(x)     __builtin_expect(!!(x),
时间: 2024-08-05 02:26:45

GCC扩展(转--对看kernel代码有帮助的相关文章

从JVM的角度看JAVA代码--代码优化

从JVM的角度看JAVA代码–代码优化 从JVM的角度看JAVA代码代码优化 片段一反复计算 片段二反复比較 在JVM载入优化为class文件,运行class文件时,会有JIT(Just-In-Time)的介入,它会做进一步优化,这样就须要考虑是否可能被JIT优化. 片段一:反复计算 // 反复计算,这里b()被计算两次 Object a = b() == null ? "" : b(); // 优化代码 Object c = b(); Object a = c == null ? &

看别人代码时的感受

知乎上看到一个图片, 说的是看别人代码时的感受, 觉得很有趣, 就翻译了一下. 如果您有更贴切的翻译, 希望多指教, 多交流. 好, 废话不多说, 上图: 1. 咋这么多坑? 2. 这样的结构逗我呢? 3. 这货想干吗? 4. 这个注释真是实话; 5. 这么低效的设计真是不敢直视; 6. 这个新装置又是什么鬼?

Java程序员看C++代码

从事了几年的android应用开发,这几年一直都是用Java开发,今年找了一家OEM公司,说白了就是Android系统源码定制,在这家公司学习了Python, C++.因为接触JNI会比较多一点,所以下面想和大家谈谈我看C++代码的一些理解. 我主要是从看代码和维护代码的角度来看,不涉及更深的技术层面.也欢迎大家来一起讨论下各自的理解. 首先我个人非常喜欢C++的头文件.C++的头文件类似于Java1.8的接口,可以在这里面定义好所有的接口方法/虚函数, 我觉得好的头文件应该是数据集+行为规范集

sklearn源码解析:ensemble模型 零碎记录;如何看sklearn代码,以tree的feature_importance为例

最近看sklearn的源码比较多,好记性不如烂笔头啊,还是记一下吧. 整体: )实现的代码非常好,模块化.多继承等写的很清楚. )predict功能通常在该模型的直接类中实现,fit通常在继承的类中实现,方便不同的子类共同引用. 随机森林 和 GBDT )RandomForest的bootstrap是又放回的:GBDT则是无放回的. )实现的代码非常好,比如GBDT提供了一些小白不常用的函数[staged_decision_function,staged_predict]之类,对于调试观察每个D

快看Sample代码,速学Swift语言(2)-基础介绍 快看Sample代码,速学Swift语言(1)-语法速览

快看Sample代码,速学Swift语言(2)-基础介绍 Swift语言是一个新的编程语言,用于iOS, macOS, watchOS, 和 tvOS的开发,不过Swift很多部分内容,我们可以从C或者Objective-C的开发经验获得一种熟悉感.Swift提供很多基础类型,如Int,String,Double,Bool等类型,它和Objective-C的相关类型对应,不过他是值类型,而Objective-C的基础类型是引用类型,另外Swift还提供了几个集合类型,如Array, Set, 和

将字符串转成整数的函数给我看一下吗,简单看一下代码基本功。要求:不要调用parseInt等转换函数

为了提高面试流程效率,方便用java写一段将字符串转成整数的函数给我看一下吗,简单看一下代码基本功. 要求:不要调用parseInt等转换函数.按位读取字符串里的字符进行处理将字符串转化为整数, 不考虑整数溢出问题,给定的输入一定是合法输入不包含非法字符,字符串头尾没有空格, 考虑字符串开头可能有正负号.public int StringToInt(String str) import javax.net.ssl.SSLContext; /** * 字符串转为int * @author IT *

软件必备模块-如何看懂代码

为什么写这一篇?因为上班了以后写代码是一个技能,不过大多数时候也时常是现用现差,这个说出来也是丢人.不过生活就是这样吧,不过积累的很重要的一个东西是什么呢,就是运行,调试,搜索相关历史.我们入职一家公司或者接手一个新的项目面临很重要的一个问题就是看懂代码. 找人教,找资料 新接手一个代码,怎么看最快?看书?自己调试?都不是,是找之前写这个代码的人耐心讲给你,不过工作了就发现大家不一定都这么好心.不过万一呢,万一你遇到好人了呢,先找人,再找文档.怎么找人?查看git历史,查看代码中的注释.看开源代

GCC 扩展内联汇编简介

基本内联汇编 基本内联汇编格式比较直观,可以直接这样写: asm("assembly code"); 例如: asm("movl %ecx, %eax"); /* 把 ecx 内容移动到 eax */ __asm__("movb %bh , (%eax)"); /* 把bh中一个字节的内容移动到eax指向的内存 */ 扩展内联汇编 前面讨论的基本内联汇编只涉及到嵌入汇编指令,而在扩展形式中,我们还可以指定操作数,并且可以选择输入输出寄存器,以及指

Objective-c 05 类别 类扩展 委托 非正式协议 协议 代码块 并发性 队列

类别 为已经存在的类添加行为时,通常采用创建子类的方法,不过有时子类并不方便, 比如:创建NSString的子类,但是NSString实际上只是一个类簇的表面形式.因而为这样的类创建子类会非常困难.在其他情况下, 也许可以创建它的子类,但是用到的工具集和库无法帮你处理新类的对象的.例如:当使用stringWithFormat:类方法生成新字符串时,你创建的 NSString类的新子类就无法返回.   利用Objective-C的动态运行时分配机制,可以为现有的类添加新的方法.这些新的方法在Obj