一 向量定义
在某些情况下,数据结构定义的末端会包含一个可选的区块,以下是一个实例:
struct abc {
int age;
char *name[20];
.....
char placeholder[0];
}
可选区块从placeholder开始,注意,placeholder定义为大小为0的向量,也就是说,当abc被分配为带有可选区域时,placeholder就指向此区块的起始处。不需要可选区块时,placeholder就只是一个指向此结构尾端的指针而已,不耗用人和空间,因此,如果abc被好几段代码所用到,每段代码都可以使用相同的基本定义,但是,又可以根据需求以不同的方式abc的定义予以个别化。
二 条件指示指令 (#ifdef 及其系列指令)
有时候必须把一些条件指令传给编译器。大量使用条件指示指令,会降低代码的可读性,但是我们可以说,linux并没有滥用这些指令。条件指示指令因各种不同的原因而出现,但是,我们感兴趣的是那些用于检查内核是否支持特定功能的条件指示指令。例如,make xconfig这类配置工具可以确认该功能是否被编译。
例如,#ifdef 或者 #if defined用于指示C预处理程序完成检查功能
从数据结构定义中引入或者排除字段。
struct sk_buff
{
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
};
这个例子中,Netfilter调试功能需要sk_buff结构中的nf_debug字段。当内核不支持Netfilter调试时就不需要引入这个字段,引入改字段对每个网络数据包而言只会消耗更多的内存。
从函数中引入或者排除一些代码片段,
int ip_route_input()
{
#ifndef CONFIG_IP_ROUTE_FWMARK
#endif
}
在第三十三章会说明,路由缓存查询函数ip_route_input 只有在内核编译支持"IP:use netfilter MARK value as routing key"功能时,才会检查由防火墙所设的标记值。
#ifdef CONFIG_IP_MULTIPLE_TABLES
struct fib_table *fib_hash_init(int id)
#else
struct fib_table * __init fib_hash_init(int id)
{}
在这个例子中,当内核不支持策略路由时,这些指示用于把__init标志加至原型。
为函数选择适当的定义
#ifndef CONFIG_IP_MULTIPLE_TABLES
static inline struct fib_table *fib_get_table(int id)
{
if (id != RT_TABLE_LOCAL)
return ip_fib_main_table;
return ip_fib_local_table;
}
#else
static inline struct fib_table *fib_get_table(int id)
{
if (id == 0)
id = RT_TABLE_MAIN;
return fib_tables(id);
}
#endif
注意这种与前一种情况不同之处,前一种情况中,函数体位于#ifdef #endif块之外,而现在这种情况,每个块都包含一个完整的函数定义。
变量和宏的定义或者初始化可以使用条件编译
知道某些函数或者宏存在多个定义很重要的,与前面的例子类似,这些函数宏的定义,由预处理程序在编译期间决定的, 。
三 条件检查的编译期间最优化
多数情况下,当内核某些外部值比较一个变量以了解是否满足给定条件时,其结果极有可能是可预测的,这是很常见的,那些强制检查检查的代码,内核分别使用likely和unlikely宏,进行返回值真或者假的包裹比较。这些宏辉利用gcc编译器的一项功能,这个功能就是可依据该项信息使代码的编译最优化。
以下是一个实例,假设你需要调用do_something函数,调用失败时,你必须用handle_error 函数进行处理
err = do_something(x,y,z);
假设do_something很少失败,可以按以下方式重写代码
err = do_something(x,y,z);
if (unlikely(err))
handle_error(err);
likely和unlikely 宏可能做的优化实例之一就是处理IP报头里面的选项,IP选项的使用只限于一些特定的情况,因此,内核可以安全的假设多数IP封装数据包不会携带IP选项,当内核转发IP封包时,转发IP封包的最后一个阶段由ip_forward_finish 负责,此函数使用unlikely 宏把检查是否要处理ip 选项的条件包裹起来。
一个简单的例子:
include <stdio.h>
int func()
{
return 1;
}
int main()
{
if (func() == 0) {
printf("I am OK\n");
} else {
printf("I am no OK\n");
}
return 0;
}
编译之后,
like.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_Z4funcv>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: b8 01 00 00 00 mov $0x1,%eax
d: 5d pop %rbp
e: c3 retq
000000000000000f <main>:
f: f3 0f 1e fa endbr64
13: 55 push %rbp
14: 48 89 e5 mov %rsp,%rbp
17: e8 00 00 00 00 callq 1c <main+0xd>
1c: 85 c0 test %eax,%eax
1e: 0f 94 c0 sete %al //al = zf 直接判断zf值
21: 84 c0 test %al,%al
23: 74 0e je 33 <main+0x24> //jump if equal to 0 这里就是if (func() == 0)的判断
25: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 2c <main+0x1d>
2c: e8 00 00 00 00 callq 31 <main+0x22>
31: eb 0c jmp 3f <main+0x30>
33: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 3a <main+0x2b>
3a: e8 00 00 00 00 callq 3f <main+0x30>
3f: b8 00 00 00 00 mov $0x0,%eax
44: 5d pop %rbp
45: c3 retq
改一下C代码:
define likely(x) __builtin_expect(!!(x), 1)
define unlikely(x) __builtin_expect(!!(x), 0)
int func()
{
return 1;
}
int main()
{
if (unlikely(func() == 0)) {
printf("I am OK\n");
} else {
printf("I am no OK\n");
}
return 0;
}
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
int func()
{
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
return 1;
8: b8 01 00 00 00 mov $0x1,%eax
}
d: 5d pop %rbp
e: c3 retq
000000000000000f <main>:
int main()
{
f: f3 0f 1e fa endbr64
13: 55 push %rbp
14: 48 89 e5 mov %rsp,%rbp
if (unlikely(func() == 0)) {
17: e8 00 00 00 00 callq 1c <main+0xd>
1c: 85 c0 test %eax,%eax
1e: 0f 94 c0 sete %al
21: 0f b6 c0 movzbl %al,%eax
24: 48 85 c0 test %rax,%rax
27: 0f 95 c0 setne %al // zf取反再送入al 值
2a: 84 c0 test %al,%al
2c: 74 0e je 3c <main+0x2d>
printf("I am OK\n");
2e: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 35 <main+0x26>
35: e8 00 00 00 00 callq 3a <main+0x2b>
3a: eb 0c jmp 48 <main+0x39>
} else {
printf("I am no OK\n");
3c: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 43 <main+0x34>
43: e8 00 00 00 00 callq 48 <main+0x39>
}
return 0;
48: b8 00 00 00 00 mov $0x0,%eax
}
4d: 5d pop %rbp
4e: c3 retq