linux是一个复杂的软件系统,其内核源代码往往会用到一些在应用程序设计中部常见语言成分和编程技巧,这些都是分析内核源代码的基本功,希望大家能重点掌握。
首先,gcc编译器从语言里吸收了“inline”和“const”。inline函数的大量使用,有利于提高运行效率,由此相当一部分代码从.c文件移入了.h文件。
gcc增加了一种新的基本数据类型“long long int”用于支持64位CPU结构。
许多C语言都支持一些“属性描述符”,如“aligned”,“packed”等。相当于一些新的保留字。但是,在原来的C语言这些词并非保留字,而是一 些普通的变量,这样就会产生一些冲突。例如:inline在老的代码中已经是作为变量在使用了,为了解决这个问题,gcc将作为保留字的“inline” 设计成__inline__,这样就不会冲突了。
gcc还支持一个保留字“attribute”,用来作属性描述。如:attribute__ <<packed>>,这样packed就成为属性描述符(一种特殊保留字)而非变量了。
gcc中有大量对宏操作的使用,大家肯定会对内核代码中的一些宏操作的定义方式感到不解,如:
#define DUMP_WRITE(addr, nr) do{ memcpy(bufp,addr,nr); bufp+=nr;}while(0) |
这是必须的,是为了防止在IF-ELSE语句中使用该宏定义时发生错误。
linux内核代码中大量使用链表,但其使用方法与我们在《数据结构》中学到的不大一样,大家可以回顾一下我们在课堂上学习链表的时候,通常除了对其数据 结构进行了定义,还定义了若干对该结构的操作。但对于大量使用链表的linux内核来说,如果定义了一个结构就要定义其相关的操作的话,显然代码量不小。 为了提高效率,内核采用了一套通用的,一般的,可以用到各种不同数据结构的队列操作。在include/linux/ list.h中,有如下申明:
struct list_head {
struct list_head *next, *prev; }; |
于是,其余数据结构如inode等将其引用而作为一个成员,就可以完成下面将要讲到的若干操作而不去单独定义了。引用该数据结构的结构我们称为宿主结构 。
1.定义和初始化
#define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) / struct list_head name = LIST_HEAD_INIT(name) |
需要注意的是,Linux 的每个双循环链表都有一个链表头,链表头也是一个节点, 只不过它不嵌入到宿主数据结构中,即不能利用链表头定位到对应的宿主结构,但可以由之获得虚拟的宿主结构指针。
LIST_HEAD()宏可以同时完成定义链表头,并初始化这个双循环链表为空。
静态定义一个list_head 类型变量,该变量一定为头节点。 name为struct list_head{}类型的一个变量, &(name)为该结构体变量的地址。用name结构体变量的始地址将该结构体变量进行初始化。
#define INIT_LIST_HEAD(ptr) do { / (ptr)->next = (ptr); (ptr)->prev = (ptr); / } while (0) |
动态初始化一个已经存在的list_head对象,ptr为一个结构体的指针, 这样可以初始化堆栈以及全局区定义的list_head对象。 ptr使用时候,当用括号,(ptr),避免ptr为表达式时宏扩展带来的异常问题。 此宏很少用于动态初始化内嵌的list对象,主要是链表合并或者删除后重新初始化头部。 若是在堆中申请了这个链表头,调用INIT_LIST_HEAD()宏初始化链表节点, 将next和prev指针都指向其自身,我们就构造了一个空的双循环链表。
2.6内核中内联函数版本如下:
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
此时的参数有明确的类型信息struct list_head,同时可以看出其为指针,list无须象宏中那样(), 即使参数为表达式,其也是求值后再作为参数传入的。内联函数有严格的参数类型检查, 同时不会出现宏函数扩展带来的异常问题,但是运行效率和空间效率与宏函数一致。
2.通过对列头head链入一个新队列new
所有链表(包括添加、删除、移动和拼接等)操作都是针对数据结构list_head进行的。 提供给用户的的添加链表的操作有两种:表头添加和表尾添加。注意到, Linux双循环链表中有一个链表头,表头添加是指添加到链表头之后,而表尾添加则是添加到链表头的prev所指链表节点之后。
static __inline__ void __list_add(struct list_head * new, struct list_head * prev, struct list_head * next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } |
普通的在两个非空结点中插入一个结点,注意new、prev、next都不能是空值。 Prev可以等于next,此时在只含头节点的链表中插入新节点。 参数new指向将要链入队列的宿主数据结构(如inode、page等)内部指定的list_head数据结构。
static __inline__ void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } |
在head和head->next两指针所指向的结点之间插入new所指向的结点。 即:在head指针后面插入new所指向的结点。Head并非一定为头结点。 当现有链表只含有一个头节点时,上述__list_add(new, head, head->next)仍然成立。
static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } |
在结点指针head所指向结点的前面插入new所指向的结点。当head指向头节点时,也相当于在尾结点后面增加一个new所指向的结点。 。
注意:head->prev不能为空,即若head为头结点,其head->prev当指向一个数值,一般为指向尾结点,构成循环链表。
上述三个函数实现了添加一个节点的任务,其中__list_add()为底层函数,“__”通常表示该函数是底层函 数,供其他模块调用,此处实现了较好的代码复用,list_add和list_add_tail虽然原型一样,但调用底层函数__list_add时传递 了不同的参数,从而实现了在head指向节点之前或之后添加新的对象
3.脱链的操作
如果要从链表中删除某个链表节点,则可以调用list_del或list_del_init。 需要注意的是,上述操作均仅仅是把节点从双循环链表中拿掉, 用户需要自己负责释放该节点对应的数据结构所占用的空间,而这个空间本来就是用户分配的。
static __inline__ void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } |
在prev和next指针所指向的结点之间,两者互相所指。在后面会看到: prev是待删除的结点的前面一个结点,next是待删除的结点的后面一个结点。
static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); |