关于Linux的内核编译有不少系统管理员都不知道如何去处理。其实就像平时的Linux系统管理一样Linux内核编译也有技巧可以掌握。在本文中我们就向大家介绍下Linux内核编译九个技巧。
1.构建泛型宏 (。/linux/include/linux/kernel.h)
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 《 _min2 ? _min1 : _min2; })
大家看了就明白是什么意思了。但是我还有几点疑问:
(1)(void) (&_min1 == &_min2);这行代码是用来干什么的?
(2)为什么{}的外面要加(),不加的时候编译是不通过的,具体是什么原因?
2. 范围的扩展
(1) switch 语句
switch(a)
{
case 1 。.. 3:
printf(“fafadsf”);
break;
case 4 。.. 8:
printf(“dsafaf”);
break;
}
(2)数组的初始化 int widths[] = { [0 。.. 9] = 1, [10 。.. 99] = 2, [100] = 3 };以上部分内核中用的很多。
3 .零长度的数组
struct iso_block_store {
atomic_t refcount;
size_t data_size;
quadlet_t data[0];
};
这允许结构中的元素引用结构实例后面紧接着的内存。在需要数量可变的数组成员时,这个特性很有用应用实例:
struct iso_block_store * p =(void *)malloc(sizeof(struct iso_block_store) + data_size);
4. 获得函数的返回地址
如下面的代码所示,__builtin_return_address 接收一个称为 level 的参数。这个参数定义希望获取返回地址的调用堆栈级别。例如,如果指定 level 为 0,那么就是请求当前函数的返回地址。如果指定 level 为 1,那么就是请求进行调用的函数的返回地址,依此类推。
void * __builtin_turn_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));}
5 .常量检测
在编译时,可以使用 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) \
6. 函数属性
GCC 提供许多函数级属性,可以通过它们向编译器提供更多数据,帮助编译器执行优化。本节描述与功能相关联的一些属性。
属性通过其他符号定义指定了别名。
# 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))
定义是 GCC 中可用的一些函数属性。它们也是在 Linux 内核中最有用的函数属性。下面解释如何使用这些属性:
always_inline 让 GCC 以内联方式处理指定的函数,无论是否启用了优化。
deprecated 指出函数已经被废弃,不应该再使用。如果试图使用已经废弃的函数,就会收到警告。还可以对类型和变量应用这个属性,促使开发人员尽可能少使用它们。
__used__ 告诉编译器无论 GCC 是否发现这个函数的调用实例,都要使用这个函数。这对于从汇编代码中调用 C 函数有帮助。
__const__ 告诉编译器某个函数是无状态的(也就是说,它使用传递给它的参数生成要返回的结果)。
warn_unused_result 让编译器检查所有调用者是否都检查函数的结果。这确保调用者适当地检验函数结果,从而能够适当地处理错误。
下面是在 Linux 内核中使用这些属性的示例。deprecated 示例来自与体系结构无关的内核(。/linux/kernel/resource.c),const 示例来自 IA64 内核源代码(。/linux/arch/ia64/kernel/unwind.c)。
int __deprecated __check_region(struct resource
*parent, unsigned long start, unsigned long n)
static enum unw_register_index __attribute_const__
decode_abreg(unsigned char abreg, int memory)
7. 分支预测提示
在 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 部分,这不是最优的方式,但是访问它的可能性不大。按照这种方式,代码对于最可能出现的情况是最优的。
下面给出一个使用 likely 和 unlikely 宏的函数(见 。/linux/net/core/datagram.c)。这个函数预测 sum 变量将是零(数据包的 checksum 是有效的),而且 ip_summed 变量不等于 CHECKSUM_HW。
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;
}
8 .预抓取
另一种重要的性能改进方法是把必需的数据缓存在接近处理器的地方。缓存可以显著减少访问数据花费的时间。大多数现代处理器都有三类内存:
一级缓存通常支持单周期访问
二级缓存支持两周期访问
系统内存支持更长的访问时间
为了尽可能减少访问延时并由此提高性能,最好把数据放在最近的内存中。手工执行这个任务称为预抓取。GCC 通过内置函数 __builtin_prefetch 支持数据的手工预抓取。在需要数据之前,使用这个函数把数据放到缓存中。如下所示,__builtin_prefetch 函数接收三个参数:
数据的地址
rw 参数,使用它指明预抓取数据是为了执行读操作,还是执行写操作
locality 参数,使用它指定在使用数据之后数据应该留在缓存中,还是应该清除
void __builtin_prefetch( const void *addr, int rw, int locality );
Linux 内核经常使用预抓取。通常是通过宏和包装器函数使用预抓取。下面是一个辅助函数示例,它使用内置函数的包装器(见 。/linux/include/linux/prefetch.h)。这个函数为流操作实现预抓取机制。使用这个函数通常可以减少缓存缺失和停顿,从而提高性能。
#ifndef ARCH_HAS_PREFETCH#define prefetch(x) __builtin_prefetch(x)#endifstatic 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}
9.变量属性
除了本文前面讨论的函数属性之外,GCC 还为变量和类型定义提供了属性。最重要的属性之一是 aligned 属性,它用于在内存中实现对象对齐。除了对于性能很重要之外,某些设备或硬件配置也需要对象对齐。aligned 属性有一个参数,它指定所需的对齐类型。
下面的示例用于软件暂停(见 。/linux/arch/i386/mm/init.c)。在需要页面对齐时,定义 PAGE_SIZE 对象。
char __nosavedata swsusp_pg_dir[PAGE_SIZE]
__attribute__ ((aligned (PAGE_SIZE)));
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;