《基于Linux的C编程与内核导读》连载(6)
时间:2009-07-23 来源:叮汀
2.5 linux内核中的C语言
在这里我们并不讲解C语言的各种基础知识,而是针对Linux内核中一些比较特殊的C语言用法做一些简单的说明。
首先,在Linux内核中大量使用了inline函数,从功能上说,inline函数的使用与#define宏定义相似,但更有相对的独立性,也更安全。使用inline函数也有利于程序调试。如果编译时不加优化,则这些inline函数就是普通的、独立的函数。由于inline函数的大量使用,相当一部分的代码从.c文件移入.h文件中。
由于“inline”在C语言中不是保留字(在C++中是保留字),所以为了避免冲突,则在其前后都加上“__”,因而“__inline__”等价于保留字“inline”。同样道理,“__asm__”等价于“asm”。
虽然linux内核代码中使用了大量inline函数,不过这并未消除对宏操作的使用。人们常常会对内核代码中一些宏操作的定义感到迷惑不解,这里有必要做一些解释。先看一个实例,取自fs/proc/kcore.c
#define DUMP_WRITE(addr,nr) do{memcpy(bufp,addr,nr);bufp+=nr;}while(0) |
大家都知道,do-while循环是先执行后判断循环条件,所以,这个定义意味着每当引用这个宏操作时只会执行一次。可是,为什么要这样通过一个do-while循环来定义呢?能不能直接写成下面的形式呢?
#define DUMP_WRITE(addr,nr) memcpy(bufp,addr,nr);bufp+=nr; |
答案是不行的,下面我们举一个小例子,看看问题出在哪里?
if(addr) DUMP_WRITE(addr,nr); else do_something_else(); |
经过预处理以后,这段代码就会变成下面这样:
if(addr) memcpy(bufp,addr,nr);bufp+=nr; else do_something_else(); |
编译这段代码时gcc会失败,并报告语法错误。因为gcc认为if语句在memcpy()以后就结束了,然后却又碰到一个else。读者可能马上会想到在定义中加上大括号,成为这样:
#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr);bufp+=nr;} |
可是,上面那段程序还是通不过编译,因为经过预处理就变成这样:
if(addr) {memcpy(bufp,addr,nr);bufp+=nr;}; else do_something_else(); |
同样,gcc在碰到else前面的“;”时就认为if语句已经结束了,因而后面的else不在if语句中。相比之下,采用do-while的定义在任何情况下都没有问题。
了解了这一点之后,再来看对“空操作”的定义。由于linux内核的代码要考虑各种不同的CPU和不同的系统配置,所以常常需要在一定条件下把某些宏操作定义为空操作。例如在include/asm-i386/system.h中的prepare_to_switch():
#define prepare_to_switch() do{}while(0) |
内核在调度一个进程运行,进行切换之际,在有些CPU上需要调用prepare_to_switch()作些准备,而在另一些CPU上就不需要,所以要把它定义为空操作。
在linux内核中还有许多对C语言的巧妙使用,比如内核采用了一套通用的,可以应用到各种不同数据结构的队列操作。这些我们都将在以后几章的内核导读部分进行详细讲解。