关闭

Linux内核中的list链表

标签: Linux List
406人阅读 评论(0) 收藏 举报
分类:

昨天看了些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*的指针,指向我们需要用到的数据的办法,实现通用型链表,至于这两种方法的利弊,现在感受不是很深...





0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2392次
    • 积分:62
    • 等级:
    • 排名:千里之外
    • 原创:4篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档