Linux内核中的list链表

来源:互联网 发布:windows关机自动重启 编辑:程序博客网 时间:2024/06/06 17:53

昨天看了些Linux方面的书,书中有讲到Linux内核中List的实现,自己也想看看技术牛人们的list的代码时如何写的。所以今天一大早起来跑步完后,载了份Linux源码来看,里面实现的方式与自己以前的实现有很大的差别。

先看看Linux的List结构体:

struct list_head {struct list_head *next, *prev;};
这是一个双向链表,我们应该很熟系了,我们自己会这样写:

struct List_Node {             T value;             struct  List_Node *pre;              struct  List_Node *next;  };

Linux中这样实现:
struct List_Node { T value; struct list_head head; }; 

仔细想了想,因为Linux中会大量的使用链表这个数据结构,而每种里面所存储的数据是不一样,所以采取将数据与链表头分开的写法,对于每种不同的数据,只要操作链表头就能将它们串在一起,但是如果只操作list_head的话,怎样才能取得结点中的数据呢?Linux中定义了两个宏,这两个宏也是源码中令我们初学者想不到的方式:

#define list_entry(ptr, type, member) \container_of(ptr, type, member)//ptr为每个list_head的地址(指向member的指针) type为每个节点的结构体类型(如上面的List_Node) member就是上面的list_head类型
container_of定义在<kernel.h>中,说明这种方式在内核中时经常使用的

#define container_of(ptr, type, member) ({\const typeof( ((type *)0)->member ) *__mptr = (ptr);\(type *)( (char *)__mptr - offsetof(type,member) );})

在看一下offsetof宏的定义,它在<stddef.h>中

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

通过上面可以看到,将一个ptr即list_head的地址传给container_of时,将其传给_mptr指针(( ((type *)0)->member ) *__mptr = (ptr)),__mptr中保存的值就是type中member对象的地址,然后通过&((TYPE *)0)->MEMBER),现将0转化为TYPE*,然后再取出MEMBER的地址,其就为MEMBER相对与TYPE型节点的偏移地址,用__mptr减去这个偏移地址就是结点真正的地址,然后通过这个指针取出数据。


源码中还提供这样两个宏:

#define list_for_each(pos, head) \    for (pos = (head)->next; pos != (head); pos = pos->next)//向后遍历链表

#define list_for_each_prev(pos, head) \for (pos = (head)->prev; pos != (head); pos = pos->prev)//向前遍历链表

所以我们可以这样子用:

  1 struct group  2 {  3         int nNum;  4         struct list_head head;  5 };  6   7 struct group *groups,*p;        ..........  8 groups = &testhead;//假设链表中已有数据,且testhead是头  9 struct list_head *pos; 10  11 list_for_each(pos,&groups->head) 12 { 13         p = list_entry(pos,struct group,head); 14   15         printf("%d ",p->nNum); 16 } 17 

当然源码中也定义了许多其他操作:

#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)//初始化链表节点使其指向本身static inline void INIT_LIST_HEAD(struct list_head *list)//初始化时指向自己本身{    list->next = list;    list->prev = list;}
list_add(struct list_head *new, struct list_head *head);//插入操作{    __list_add(new, head, head->next);//在head后面插入,源码中还定义了一种在前面插入的办法}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;} 
static inline void __list_del(struct list_head * prev, struct list_head * next){next->prev = prev;prev->next = next;}static inline void __list_del_entry(struct list_head *entry){__list_del(entry->prev, entry->next);//del entry结点,不过在源码中没有找到回收entry的函数,有可能回收的工作由包含list_head节点自己实现}
static inline void list_replace(struct list_head *old,struct list_head *new);//用new替换old节点
static inline int list_empty(const struct list_head *head){return head->next == head;}//是否为空

static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next){struct list_head *first = list->next;//list的第一个节点struct list_head *last = list->prev;//list的最后一个节点first->prev = prev;//将第一个节点插到原来节点后面prev->next = first;last->next = next;//将原来节点后面节点的prev指向最后一个节点next->prev = last;}static inline void list_splice(const struct list_head *list,struct list_head *head)//将list链表插入到head处{if (!list_empty(list))__list_splice(list, head, head->next);}

上面这个拼接工作也是整个链表中,我感觉比较复杂的工作。自己第一次实现链表拼接的时候,在这个实现上调试了很长的时间.......

看完后觉得整个list.h源码与平时自己的实现想法最大的区别,就是对节点的定义上,平常中如果要实现通用链表的时候,我会使用将节点中数据定义为一个void*的指针,指向我们需要用到的数据的办法,实现通用型链表,至于这两种方法的利弊,现在感受不是很深...