Linux System Programming -- Appendix
这本书附录的名字是 “GCC对C语言的扩展” ,一下的内容是对扩展的总结
GCC 允许通过变量的引用识别类型。这种操作支持泛型编程。在 C++、Ada 和 Java™ 语言等许多现代编程语言中都可以找到相似的功能。Linux 使用 typeof
构建 min
和 max
等依赖于类型的操作。清单 1 演示如何使用 typeof
构建一个泛型宏(见 ./linux/include/linux/kernel.h)。
清单一:使用 typeof 构建一个泛型宏
#define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ (void) (&_min1 == &_min2); \ _min1 < _min2 ? _min1 : _min2; })
GCC 支持范围,在 C 语言的许多方面都可以使用范围。其中之一是 switch
/case
块中的 case
语句。在复杂的条件结构中,通常依靠嵌套的 if
语句实现与清单 2(见 ./linux/drivers/scsi/sd.c)相同的结果,但是清单 2 更简洁。使用 switch
/case
也可以通过使用跳转表实现进行编译器优化。
清单二:在 case 语句中使用范围
static int sd_major(int major_idx) { switch (major_idx) { case 0: return SCSI_DISK0_MAJOR; case 1 ... 7: return SCSI_DISK1_MAJOR + major_idx - 1; case 8 ... 15: return SCSI_DISK8_MAJOR + major_idx - 8; default: BUG(); return 0; /* shut up gcc */ } }
还可以使用范围进行初始化,如下所示(见 ./linux/arch/cris/arch-v32/kernel/smp.c)。在这个示例中,spinlock_t
创建一个大小为LOCK_COUNT
的数组。数组的每个元素初始化为 SPIN_LOCK_UNLOCKED
值。
/* Vector of locks used for various atomic operations */ spinlock_t cris_atomic_locks[] = { [0 ... LOCK_COUNT - 1] = SPIN_LOCK_UNLOCKED};
范围还支持更复杂的初始化。例如,以下代码指定数组中几个子范围的初始值。
int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
在 C 标准中,必须定义至少一个数组元素。这个需求往往会使代码设计复杂化。但是,GCC 支持零长度数组的概念,这对于结构定义尤其有用。这个概念与 ISO C99 中灵活的数组成员相似,但是使用不同的语法。
下面的示例在结构的末尾声明一个没有成员的数组(见 ./linux/drivers/ieee1394/raw1394-private.h)。这允许结构中的元素引用结构实例后面紧接着的内存。在需要数量可变的数组成员时,这个特性很有用。
struct iso_block_store { atomic_t refcount; size_t data_size; quadlet_t data[0]; };
在许多情况下,需要判断给定函数的调用者。GCC 提供用于此用途的内置函数 __builtin_return_address
。这个函数通常用于调试,但是它在内核中还有许多其他用途。
如下面的代码所示,__builtin_return_address
接收一个称为 level
的参数。这个参数定义希望获取返回地址的调用堆栈级别。例如,如果指定 level
为 0
,那么就是请求当前函数的返回地址。如果指定 level
为 1
,那么就是请求进行调用的函数的返回地址,依此类推。
void * __builtin_return_address( unsigned int level );
在下面的示例中(见 ./linux/kernel/softirq.c),local_bh_disable
函数在本地处理器上禁用软中断,从而禁止在当前处理器上运行 softirqs、tasklets 和 bottom halves。使用 __builtin_return_address
捕捉返回地址,以便在以后进行跟踪时使用这个地址。
void local_bh_disable(void) { __local_bh_disable((unsigned long)__builtin_return_address(0)); }
在编译时,可以使用 GCC 提供的一个内置函数判断一个值是否是常量。这种信息非常有价值,因为可以构造出能够通过常量叠算(constant folding)优化的表达式。__builtin_constant_p
函数用来检测常量。
__builtin_constant_p
的原型如下所示。注意,__builtin_constant_p
并不能检测出所有常量,因为 GCC 不容易证明某些值是否是常量。
int __builtin_constant_p( exp )
Linux 相当频繁地使用常量检测。在清单 3 所示的示例中(见 ./linux/include/linux/log2.h),使用常量检测优化 roundup_pow_of_two
宏。如果发现表达式是常量,那么就使用可以优化的常量表达式。如果表达式不是常量,就调用另一个宏函数把值向上取整到 2 的幂。
清单三.使用常量检测优化宏函数
#define roundup_pow_of_two(n) \ ( \ __builtin_constant_p(n) ? ( \ (n == 1) ? 1 : \ (1UL << (ilog2((n) - 1) + 1)) \ ) : \ __roundup_pow_of_two(n) \ )
GCC 提供许多函数级属性,可以通过它们向编译器提供更多数据,帮助编译器执行优化。本节描述与功能相关联的一些属性。下一节描述 影响优化的属性。
如清单 4 所示,属性通过其他符号定义指定了别名。可以以此帮助阅读源代码参考,了解属性的使用方法(见 ./linux/include/linux/compiler-gcc3.h)。
# define __inline__ __inline__ __attribute__((always_inline)) # define __deprecated __attribute__((deprecated)) # define __attribute_used__ __attribute__((__used__)) # define __attribute_const__ __attribute__((__const__)) # define __must_check __attribute__((warn_unused_result))
清单 4 所示的定义是 GCC 中可用的一些函数属性。它们也是在 Linux 内核中最有用的函数属性。下面解释如何使用这些属性:
always_inline
让 GCC 以内联方式处理指定的函数,无论是否启用了优化。deprecated
指出函数已经被废弃,不应该再使用。如果试图使用已经废弃的函数,就会收到警告。还可以对类型和变量应用这个属性,促使开发人员尽可能少使用它们。__used__
告诉编译器无论 GCC 是否发现这个函数的调用实例,都要使用这个函数。这对于从汇编代码中调用 C 函数有帮助。__const__
告诉编译器某个函数是无状态的(也就是说,它使用传递给它的参数生成要返回的结果)。warn_unused_result
让编译器检查所有调用者是否都检查函数的结果。这确保调用者适当地检验函数结果,从而能够适当地处理错误。
现在,讨论有助于生成更好的机器码的一些 GCC 特性。
在 Linux 内核中最常用的优化技术之一是 __builtin_expect
。在开发人员使用有条件代码时,常常知道最可能执行哪个分支,而哪个分支很少执行。如果编译器知道这种预测信息,就可以围绕最可能执行的分支生成最优的代码。
如下所示,__builtin_expect
的使用方法基于两个宏 likely
和 unlikely
(见 ./linux/include/linux/compiler.h)。
#define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0)
通过使用 __builtin_expect
,编译器可以做出符合提供的预测信息的指令选择决策。这使执行的代码尽可能接近实际情况。它还可以改进缓存和指令流水线。
例如,如果一个条件标上了 “likely”,那么编译器可以把代码的 True 部分直接放在分支指令后面(这样就不需要执行分支指令)。通过分支指令访问条件结构的 False 部分,这不是最优的方式,但是访问它的可能性不大。按照这种方式,代码对于最可能出现的情况是最优的。
清单 5 给出一个使用 likely
和 unlikely
宏的函数(见 ./linux/net/core/datagram.c)。这个函数预测 sum
变量将是零(数据包的checksum
是有效的),而且 ip_summed
变量不等于 CHECKSUM_HW
。
清单五.likely 和 unlikely 宏的使用范例
unsigned int __skb_checksum_complete(struct sk_buff *skb) { unsigned int sum; sum = (u16)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum)); if (likely(!sum)) { if (unlikely(skb->ip_summed == CHECKSUM_HW)) netdev_rx_csum_fault(skb->dev); skb->ip_summed = CHECKSUM_UNNECESSARY; } return sum; }
另一种重要的性能改进方法是把必需的数据缓存在接近处理器的地方。缓存可以显著减少访问数据花费的时间。大多数现代处理器都有三类内存:
- 一级缓存通常支持单周期访问
- 二级缓存支持两周期访问
- 系统内存支持更长的访问时间
为了尽可能减少访问延时并由此提高性能,最好把数据放在最近的内存中。手工执行这个任务称为预抓取。GCC 通过内置函数__builtin_prefetch
支持数据的手工预抓取。在需要数据之前,使用这个函数把数据放到缓存中。如下所示,__builtin_prefetch
函数接收三个参数:
- 数据的地址
rw
参数,使用它指明预抓取数据是为了执行读操作,还是执行写操作locality
参数,使用它指定在使用数据之后数据应该留在缓存中,还是应该清除
void __builtin_prefetch( const void *addr, int rw, int locality );
Linux 内核经常使用预抓取。通常是通过宏和包装器函数使用预抓取。清单 6 是一个辅助函数示例,它使用内置函数的包装器(见 ./linux/include/linux/prefetch.h)。这个函数为流操作实现预抓取机制。使用这个函数通常可以减少缓存缺失和停顿,从而提高性能。
清单六.范围预抓取的包装器函数
#ifndef ARCH_HAS_PREFETCH #define prefetch(x) __builtin_prefetch(x) #endif static inline void prefetch_range(void *addr, size_t len) { #ifdef ARCH_HAS_PREFETCH char *cp; char *end = addr + len; for (cp = addr; cp < end; cp += PREFETCH_STRIDE) prefetch(cp); #endif }
除了本文前面讨论的函数属性之外,GCC 还为变量和类型定义提供了属性。最重要的属性之一是 aligned
属性,它用于在内存中实现对象对齐。除了对于性能很重要之外,某些设备或硬件配置也需要对象对齐。aligned
属性有一个参数,它指定所需的对齐类型。
下面的示例用于软件暂停(见 ./linux/arch/i386/mm/init.c)。在需要页面对齐时,定义 PAGE_SIZE
对象。
char __nosavedata swsusp_pg_dir[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE)));
清单 7 中的示例说明关于优化的两点:
packed
属性打包一个结构的元素,从而尽可能减少它们占用的空间。这意味着,如果定义一个char
变量,它占用的空间不会超过一字节(8 位)。位字段压缩为一位,而不会占用更多存储空间。- 这段源代码使用一个
__attribute__
声明进行优化,它用逗号分隔的列表定义多个属性。
清单七.结构打包和设置多个属性
static struct swsusp_header { char reserved[PAGE_SIZE - 20 - sizeof(swp_entry_t)]; swp_entry_t image; char orig_sig[10]; char sig[10]; } __attribute__((packed, aligned(PAGE_SIZE))) swsusp_header;
Linux System Programming -- Appendix的更多相关文章
- Linux System Programming note 8 ——File and Directory Management
1. The Stat Family #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> ...
- Linux System Programming --Chapter Nine
这一章的标题是 "信号" ,所以本文将对信号的各个方面进行介绍,由于Linux中的信号机制远比想象的要复杂,所以,本文不会讲的很全面... 信号机制是进程之间相互传递消息的一种方法 ...
- Linux System Programming 学习笔记(十一) 时间
1. 内核提供三种不同的方式来记录时间 Wall time (or real time):actual time and date in the real world Process time:the ...
- Linux System Programming 学习笔记(七) 线程
1. Threading is the creation and management of multiple units of execution within a single process 二 ...
- Linux System Programming 学习笔记(六) 进程调度
1. 进程调度 the process scheduler is the component of a kernel that selects which process to run next. 进 ...
- Linux System Programming 学习笔记(四) 高级I/O
1. Scatter/Gather I/O a single system call to read or write data between single data stream and mu ...
- Linux System Programming 学习笔记(二) 文件I/O
1.每个Linux进程都有一个最大打开文件数,默认情况下,最大值是1024 文件描述符不仅可以引用普通文件,也可以引用套接字socket,目录,管道(everything is a file) 默认情 ...
- Linux System Programming 学习笔记(一) 介绍
1. Linux系统编程的三大基石:系统调用.C语言库.C编译器 系统调用:内核向用户级程序提供服务的唯一接口.在i386中,用户级程序执行软件中断指令 INT n 之后切换至内核空间 用户程序通过寄 ...
- Linux System Programming --Chapter Eight
内存管理 一.分配动态内存的几个函数 用户空间内存分配:malloc.calloc.realloc1.malloc原型如下:extern void *malloc(unsigned int num_b ...
随机推荐
- ThreadLocal深入剖析
JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序,Threa ...
- 20160212.CCPP体系详解(0022天)
程序片段(01):01.二维数组.c 内容概要:二维数组 #include <stdio.h> #include <stdlib.h> //01.关于栈内存开辟数组: // 诀 ...
- Android系统对话框
Android系统对话框 效果图 2个按钮的对话框 3个按钮的对话框 自定义View的对话框 单选对话框 多选对话框 列表框 Code XML <?xml version="1.0&q ...
- Cassandra 3.x官方文档(1)---关于Cassandra
写在前面 cassandra3.x官方文档的非官方翻译.翻译内容水平全依赖本人英文水平和对cassandra的理解.所以强烈建议阅读英文版cassandra 3.x 官方文档.此文档一半是翻译,一半是 ...
- Matplotlib Toolkits:python高级绘图库seaborn
http://blog.csdn.net/pipisorry/article/details/49515745 Seaborn介绍 seaborn (Not distributed with matp ...
- activiti源码编译
个小时,大家安装的时候一定要耐心. 最终编译之后的效果部分如下图所示: 因为我这里有些xml文件没有去除验证,所以有红色的警告,不过也不影响使用. 下面看一下下载之后文件的变化如下图所示: 我们导入主 ...
- Linux Debugging (九) 一次生产环境下的“内存泄露”
一个偶然的机会,发现一个进程使用了超过14G的内存.这个进程是一个RPC server,只是作为中转,绝对不应该使用这么多内存的.即使并发量太多,存在内存中的数据太多,那么在并发减少的情况下,这个内存 ...
- Android简易实战教程--第二十九话《创建图片副本》
承接第二十八话加载大图片,本篇介绍如何创建一个图片的副本. 安卓中加载的原图是无法对其修改的,因为默认权限是只读的.但是通过创建副本,就可以对其做一些修改,绘制等了. 首先创建一个简单的布局.一个放原 ...
- iOS7 CookBook精彩瞬间(三)UIActivityViewController的基本使用及自定义Activity
1.基本使用 UIActivityViewController主要用于分享内容,创建activityView的方法很简单,调用下面的方法创建: [[UIActivityViewController a ...
- Java Socket输入流如何检测到EOF
对于InputStream的 read(b, off, len) 方法 public int read(byte[] b, int off, int len) throws IOException,J ...