在GNUC语言中,假如晓得a和b的类型,可以在宏上面定义一个变量,将a,b形参给变量,之后再比较。
Linux内核采用的是GCC编译器,GCC编译器不仅支持ANSIC,还支持GNUC。在Linux内核中,许多地方都使用了GNUC语言的扩充特点,如typeof、__attribute__、__aligned、__builtin_等,那些都是GNUC语言的特点。
typeof
下边是比较两个数大小返回最大值的精典宏写法:
#define1.
假如a传入i++,b传入j++,这么这个比较大小都会出错。诸如:
#define
int x = 1, y = 2;
printf("max=%dn", max(x++, y++));
printf("x = %d, y = %dn", x, y);1.2.3.4.5.
输出结果:max=3,x=2,y=4。这是错误的结果,正常我们希望的是max(1,2),返回max=2。怎么更改这个宏呢?
在GNUC语言中,假如晓得a和b的类型调用linux内核函数,可以在宏上面定义一个变量,将a,b形参给变量,之后再比较。诸如:
#define max(a,b) ({
int _a = (a);
int _b = (b);
_a > _b ? _a : _b; }) 1.2.3.4.
倘若不晓得具体的数据类型,可以使用typeof类转换宏,Linux内核中的事例:
#define max(a, b) ({
typeof(a) _a = (a);
typeof(b) _b = (b);
(void) (&_a == &_b);
_a > _b ? _a : _b; })1.2.3.4.5.
typeof(a)_a=(a):定义一个a类型的变量_a,将a形参给_a
typeof(b)_b=(b):定义一个b类型的变量_b,将b形参给_b
(void)(&_a==&_b):判定两个数的类型是否相同,倘若不相同,会抛出一个警告。由于a和b的类型不一样,其表针类型也会不一样,两个不一样的表针类型进行比较操作,会抛出一个编译警告。
typeof用法举例:
//typeof的参数可以是表达式或类型
//参数是类型
typeof(int *) a,b;//等价于:int *a,*b;
//参数是表达式
int foo();

typeof(foo()) var;//声明了int类型的var变量,因为表达式foo()是int类型的。由于表达式不会被执行,所以不会调用foo函数。1.2.3.4.5.6.7.8.
零长链表
零长链表,又叫柔性链表。而它的作用主要就是为了满足须要变厚度的结构体,因而有时也习惯性地称为变长链表。
用法:在一个结构体的最后,声明一个宽度为0的链表,就可以促使这个结构体是可变长的。
对于编译器来说,此时宽度为0的链表并不占用空间,由于链表名本身不占空间,它只是一个偏斜量,链表名这个符号本身代表了一个不可更改的地址常量
结构体中定义零长链表:
struct pcpu_chunk {
struct list_head list;
unsigned long populated[]; /* 变长数组 */
};1.2.3.4.5.
数据结构最后一个元素被定义为零厚度链表,不占结构体空间。这样,我们可以按照对象大小动态地分配结构的大小。
struct line {
int length;
char contents[0];
};
struct line *thisline = malloc(sizeof(struct line) + this_length);
thisline->length = this_length;1.2.3.4.5.6.7.
如上例所示,structline数据结构定义了一个intlength变量和一个变长链表contents[0],这个structline数据结构的大小只包含int类型的大小,不包含contents的大小,也就是**sizeof(structline)=sizeof(int)**。
创建结构体对象时,可依据实际的须要指定这个可变长链表的厚度,并分配相应的空间,如上述实例代码分配了this_length字节的显存,但是可以通过contents[index]来访问第index个地址的数据。
case范围
GNUC语言支持指定一个case的范围作为一个标签,如:
case low ...high:
case 'A' ...'Z':1.2.
这儿low到high表示一个区间范围,在ASCII字符代码中也特别有用。下边是Linux内核中的代码反例。
static int local_atoi(const char *name){
int val = 0;
for (;; name++) {
switch (*name) {
case '0' ...'9':
val = 10*val+(*name-'0');
break;
default:
return val;
}
}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.
另外,还可以用整形数来表示范围,并且这儿须要注意在“…”两边有空格,否则编译会出错。
static int at91sam9261_udc_init(struct at91_udc *udc){
for (i = 0; i ep[i];
switch (i) {
case 0:
ep->maxpacket = 8;
break;
case 1 ... 3:
ep->maxpacket = 64;
break;
case 4 ... 5:
ep->maxpacket = 256;
break;
}
}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.
标号元素
GNUC语言可以通过指定索引或结构体成员名来初始化,何必根据原先的固定次序进行初始化。
结构体成员的初始化在Linux内核中常常使用,如在设备驱动中初始化file_operations数据结构:
static const struct file_operations zero_fops = {
.llseek = zero_lseek,
.read = new_sync_read,
.write = write_zero,
.read_iter = read_iter_zero,
.aio_write = aio_write_zero,
.mmap = mmap_zero,
};1.2.3.4.5.6.7.8.9.
如上述代码中的zero_fops的成员llseek初始化为zero_lseek函数,read成员初始化为new_sync_read函数,依次类推。当file_operations数据结构的定义发生变化时,这些初始化方式仍然能保证已知元素的正确性,对于未初始化成员的值为0或则NULL。
可变参数宏
在GNUC语言中,宏可以接受可变数量的参数,主要用在输出函数里。诸如:
#define pr_debug(fmt, ...)
dynamic_pr_debug(fmt, ##__VA_ARGS__)1.2.3.
“…”代表一个可以变化的参数表,“__VA_ARGS__”是编译器保留数组,预处理时把参数传递给宏。当宏的调用展开时,实际参数就传递给dynamic_pr_debug函数了。
函数属性
GNUC语言容许申明函数属性(FunctionAttribute)、变量属性(VariableAttribute)和类型属性(TypeAttribute),便于编译器进行特定方面的优化和更仔细的代码检测。特殊属性句型格式为:
__attribute__ ((attribute-list))1.
attribute-list的定义有好多,如noreturn、format以及const等。据悉,还可以定义一些和处理器体系结构相关的函数属性,如ARM体系结构中可以定义interrupt、isr等属性。
下边是Linux内核中使用format属性的一个反例。
int libcfs_debug_msg(struct libcfs_debug_msg_data *msgdata,const char *format1, ...)__attribute__ ((format (printf, 2, 3)));1.2.
libcfs_debug_msg()函数里申明了一个format函数属性,它会告诉编译器根据printf的参数表的格式规则对该函数参数进行检测。数字2表示第二个参数为低格字符串,数字3表示参数“…”里的第一个参数在函数参数总量中排在第几个。
noreturn属性告诉编译器,该函数从不返回值,这可以去除一些何必要的警告信息。比如以下函数,函数不会返回:
void __attribute__((noreturn)) die(void);1.
const属性会让编译器只调用该函数一次,之后再调用时只须要返回第一次结果即可,进而提升效率。
static inline u32 __attribute_const__ read_cpuid_cachetype(void){
return read_cpuid(CTR_EL0);
}1.2.3.
Linux还有一些其他的函数属性,被定义在compiler-gcc.h文件中。
#define
#define
#define
#define
#define
#define
#define
#define1.2.3.4.5.6.7.8.
变量属性和类型属性
变量属性可以对变量或结构体成员进行属性设置。类型属性常见的属性有alignment、packed和sections等。
alignment属性规定变量或则结构体成员的最小对齐格式,以字节为单位。
struct qib_user_info {
__u32 spu_userversion;
__u64 spu_base_info;
} __aligned(8);1.2.3.4.
在这个事例中,编译器以8字节对齐的方法来分配qib_user_info这个数据结构。
packed属性可以使变量或则结构体成员使用最小的对齐方法,对变量是以字节对齐,对域是以位对齐。
struct test{
char a;
int x[2] __attribute__ ((packed));
};1.2.3.4.
x成员使用了packed属性,它会储存在变量a旁边,所以这个结构体一共占用9字节。
内建函数
内建函数以“_builtin_”作为函数名前缀。下边介绍Linux内核常用的一些内建函数。
__builtin_constant_p(x):判定x是否在编译时就可以被确定为常量。若果x为常量,该函数返回1,否则返回0。
__builtin_expect(exp,c):
#define __swab16(x)
(__builtin_constant_p((__u16)(x)) ?
___constant_swab16(x) :
__fswab16(x))__builtin_expect(exp, c)1.2.3.4.
__builtin_expect(exp,c):这儿的意思是exp==c的几率很大,拿来引导GCC编译器进行条件分支预测。开发人员晓得最可能执行那个分支,并将最有可能执行的分支告诉编译器,让编译器优化指令序列,使指令尽可能地次序执行,进而提升CPU预取指令的正确率。
Linux内核中常常看到likely()和unlikely()函数,本质也是__builtin_expect():
#define LIKELY(x) __builtin_expect(!!(x), 1) //x很可能为真
#define UNLIKELY(x) __builtin_expect(!!(x), 0) //x很可能为假1.2.
__builtin_prefetch(constvoid*addr,intrw,intlocality):主动进行数据预取,在使用地址addr的值之前就把其值加载到cache中,降低读取的延后,进而提升性能。
该函数可以接受3个参数:
#define
#define1.2.3.
下边是使用prefetch()函数进行优化的一个事例。

void __init __free_pages_bootmem(struct page *page, unsigned int order){
unsigned int nr_pages = 1 << order;
struct page *p = page;
unsigned int loop;
prefetchw(p);
for (loop = 0; loop < (nr_pages - 1); loop++, p++) {
prefetchw(p + 1);
__ClearPageReserved(p);
set_page_count(p, 0);
}
…
}1.2.3.4.5.6.7.8.9.10.11.12.13.
在处理structpage数据之前linux操作系统版本,通过prefetchw()预取到cache中,进而提高性能。
asmlinkage
在标准C语言中,函数的实参在实际传入参数时会涉及参数储存问题。
对于x86构架,函数参数和局部变量被一起分配到函数的局部堆栈里。x86中对asmlinkage的定义:
#define1.2.
attribute((regparm(0))):告诉编译器该函数不须要通过任何寄存器来传递参数,只通过堆栈来传递。
对于ARM来说,函数参数的传递有一套ATPCS标准adobe air linux,即通过寄存器来传递。ARM中的R0~R4寄存器储存传入参数,当参数超过5个时,多余的参数被储存在局部堆栈中。所以,ARM平台没有定义asmlinkage。
#define
#define1.2.3.
UL
在Linux内核代码中,我们常常会听到一些数字的定义使用了UL后缀修饰。
数字常量会被隐型定义为int类型调用linux内核函数,两个int类型相乘的结果可能会发生溢出。
因而使用UL强制把int类型数据转换为unsignedlong类型,这是为了保证运算过程不会由于int的位数不同而造成溢出。
1:表示有符号整型数字1
UL:表示无符号长整型数字1