Linux内核中的list链表

原创 2013年12月03日 16:48:42

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





相关文章推荐

详解linux内核链表之list.h

  • 2012年12月14日 23:14
  • 378KB
  • 下载

linux内核部件--通用双向链表list

在linux内核中,有一种通用的双向循环链表,构成了各种队列的基础。链表的结构定义和相关函数均在include/linux/list.h中,下面就来全面的介绍这一链表的各种API。 stru...

Linux内核中链表源码 list.h

  • 2011年12月06日 22:38
  • 29KB
  • 下载

Linux内核链表list_head扩展---klist

     看SPI驱动核心模块时,看到用到很多klist,之前没多深入理解,现在来一步步分析源码。klist是对list_head的扩展,实现代码很少。内核代码: include/linux/kli...

linux内核学习——list链表

链表使用基础: 链表是linux内核中最简单,最普遍的数据结构。刚开始接触内核的人可能会对linux的链表操作有点不习惯,因为它不是把数据结构放到链表里面,而是把链表节点放到数据结构里面。 链表代...

list.h linux内核链表分析

而Linux内核链表把链表指针域的相关操作单独抽离出来,封装成一套接口,每一种链表都可使用这个接口。这种颠覆传统双向循环链表的实现方法,使我们无需为每个双向链表编写专门的函数,从而大大提高了代码的重用...

深入浅出linux内核源代码之双向链表list_head(下):

原文链接:http://blog.csdn.net/fjb2080/article/details/5457630 我用一个程序来说明在struct person 中增加了struc...
  • k666668
  • k666668
  • 2016年05月26日 20:50
  • 138

linux内核list.h 想熟练运用链表的必须课

#ifndef __LIST_H #define __LIST_H #if defined(WIN32) #define INLINE __inline #else #define INLI...
  • xk_qq
  • xk_qq
  • 2012年07月17日 13:18
  • 470
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux内核中的list链表
举报原因:
原因补充:

(最多只允许输入30个字)