Linux内核中C和汇编使用技巧集锦 —— 持续更新

本文主要是用来存放Linux-3.1.1内核中有关C语言和汇编的使用技巧。在此所记录的一些技巧将会帮助有语言基础的童鞋写出更富艺术性和创造力的代码:-) 。需要注意的是,Linux内核采用GCC编译器进行编译并使用了GCC中的很多扩展特性,所以这些代码并不保证能在其他平台如VC++/DEV-C++中使用。

C语言:

Const.h
#define __AC(X,Y)    (X##Y)
Const.h文件中的注释说明了使用这些宏,可以免去单方面声明某个常量的类型,从而使得代码更易理解
例如 ((base)  & _AC(0x00ff ffff,ULL)) << 16 实现的效果是将0x00ff ffff与类型声明符拼接在一起,从而声明该常量为无符号整型
另外##在C语言中除了实现对象的拼接,也可以将其作为代码生成器,详细内容参见C语言宏的高级应用
#define _AT(T,X)    ((T)(X))
_AT宏主要用于将变量X的类型强制转换为类型T

Page.h
#define __pte(x)  ((pte_t) {(x)} )
这个宏的作用是执行强制转换,只不过这里比较特殊,因为在(x)外加了一层大括号,属于gcc的扩展内容,下面我写了一个测试,以便加深理解
    unsigned x = 12;
    int y = (int)(x);
    int z = (int){(x)};
使用gcc test.c -o test.exe命令编译后执行objdump -d test.exe得到
  40139a:    c7 44 24 0c 0c 00 00     movl   $0xc,0xc(%esp)  ;/* x=12 */
  4013a1:    00 
  4013a2:    8b 44 24 0c              mov    0xc(%esp),%eax  ;/* (int)(x) */
  4013a6:    89 44 24 08              mov    %eax,0x8(%esp)
  4013aa:    8b 44 24 0c              mov    0xc(%esp),%eax  ;/* (int){(x)}*/
  4013ae:    89 44 24 04              mov    %eax,0x4(%esp)
由以上反汇编内容可以清楚的发现与普通的强制转换并无区别

Compiler.h
# define likely(x)    __builtin_expect(!!(x), 1)
# define unlikely(x)    __builtin_expect(!!(x), 0)
在讲述上述宏的作用之前,首先需要知道现代处理器采用的是深度流水线乱序处理技术,具体的说,处理器将单条指令的执行进一步划分为一系列的原子操作,每一个原子操作在一个时钟周期内由组合逻辑块执行并将最终的结果转储在中间寄存器中,并且每个时钟周期都将有一条新指令进入执行阶段(指交由ALU逻辑计算单元处理)。然而如果所取出的指令为条件转移(jmp),那么只有在该指令通过执行阶段后才知道下一条指令的地址,为此处理器的设计使用了某种策略,比如总是选择(always taken),从不选择(never taken,NT)或是反向选择、正向不选择(backward taken,forward not-taken,BTFNT)等等,使得即使条件转移指令未进入执行阶段,处理器仍将预先选择某一条分支的指令放在流水线上执行,好处是预测正确,那么提高了流水线的吞吐率,坏处则是预测错误将伴随一定的处罚。而上述宏的目的就是通过人为手段帮助处理器在指令级进行转移预测,这属于gcc的扩展特性,更详细的内容参见__builtin_expect详解
E820.h
struct e820entry {
    __u64 addr;	/* start of memory segment */
    __u64 size;	/* size of memory segment */
    __u32 type;	/* type of memory segment */
} __attribute__((packed));
__attribute__((packed))告诉编译器取消对结构体执行优化过程中的内存对齐操作,按照实际的字节数进行对齐。不过在这个结构体中添加该类型声明也没有多大用处,因为这些变量在内存中本来就是紧凑对齐的。假设有如下结构体:
struct test {
    char c;
    int i;
};
若未添加该类型声明,那么sizeof(test)=8,若添加该声明则sizeof(test)=5。(现在的gcc编译器已忽略该属性,实际上即使添加该属性之后sizeof(test)也为8)更多有关__attribute__的用法参见 Attribute Syntax

Setup.c
#define __initdata    __section(.init.data)  /*defined in the file "Init.h"*/
static struct e820entry map[E820MAX] __initdata;
宏__initdata可以放在变量之后,充当类型声明的作用。__section是gcc的一个关键字,该声明告诉链接器将变量放在.init.data节区里面,系统在初始化之后会会将该节区所占内存区域释放以减少内存使用。更多有关显示命令gcc编译器将函数或数据存放在指定节区的宏定义见“Init.h”头文件。

Stddef.h
#undef offsetof
#undef指令表示取消之前的宏定义,在上例中就意味着从当前文件的该位置开始无法再使用预定义的宏offsetof。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
对宏offsetof简单解析:首先通过((TYPE *)0)将0强制转换为TYPE类型的指针,之后通过“->”操作符访问结构体中的成员MEMBER,最后通过取地址操作符获得MEMBER的地址,其巧妙之处在于是从内存地址0处访问该数据成员,因此所得地址即为成员MEMBER在结构体中的偏移量,另外需要注意的一点是所得偏移量受到内存对齐的影响,举例如下:
typedef struct{
    int a;
    char b;
    float c;
}test;
使用上述宏offsetof得到结构体test中的数据成员a、b、c的偏移地址分别为0,4,8
在gcc编译器中内建了能够实现相同功能的宏__builtin_offsetof

Boot.h
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
1-2*!!(condition)的意思是若条件为真,那么运算的结果为1-2*1=-1,否则即为1,因为-1作为数组的维数是不合法的,因此该表达式的含义即为若条件为真,那么将引发一个编译时错误。
对condition的两次取反操作,将使得条件为真的任意求值结果均被强行置位1


待续


汇编:

head_32.S
ENTRY(startup_32)
ENTRY()表示将括号内的标识符声明为全局,对齐并定义为标号。即上述代码等价于如下形式:
.globl startup_32
.align 16, 0x90
startup_32:

Linkage.h
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))
该标识符与函数声明放在一起,告诉gcc编译器不需要通过任何寄存器来传递参数,而只通过堆栈来传递。详细内容参见FAQ/asmlinkage
待续

你可能感兴趣的:(内核)