linux list_head 理解

在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head。虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制(希望有机会写篇文章研究一下C语言的面向对象机制)。下面就是kernel中的list_head结构定义:

struct list_head {

  struct list_head *next, *prev;

};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

需要注意的一点是,头结点head是不使用的,这点需要注意。

使用list_head组织的链表的结构如下图所示:

 

list_head这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?

其实list_head不是拿来单独用的,它一般被嵌到其它结构中,如:

struct file_node{

  char c;

  struct list_head node;

};

此时list_head就作为它的父结构中的一个成员了,当我们知道list_head的地址(指针)时,我们可以通过list.c提供的宏 list_entry 来获得它的父结构的地址。下面我们来看看list_entry的实现:

#define list_entry(ptr,type,member)\

  container_of(ptr,type,member)

 

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

#define container_of(ptr,type,member) ( {\

  const typeof( ((type*)0)->member ) *__mptr=(ptr);\

  (type*)( (char*)__mptr - offsetof(type,member) );} )

 

这里涉及到三个宏,还是有点复杂的,我们一个一个来看:

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

我们知道 0 地址内容是不能访问的,但 0地址的地址我们还是可以访问的, 这里用到一个取址运算符

(TYPE *)0 它表示将 0地址强制转换为TYPE类型,((TYPE *)0)-> MEMBER 也就是从0址址找到TYPE 的成员MEMBER 。

我们结合上面的结构来看

struct file_node{

  char c;

  struct list_head node;

};

将实参代入 offset( struct file_node, node );最终将变成这样:

( (size_t) & ((struct file_node*)0)-> node );这样看的还是不很清楚,我们再变变:

struct file_node *p = NULL;

& p->node;

这样应该比较清楚了,即求 p 的成员 node的地址,只不过p 为0地址,从0地址开始算成员node的地址,也就是 成员 node 在结构体 struct file_node中的偏移量。offset宏就是算MEMBER在TYPE中的偏移量的。

我们再看第二个宏

#define container_of(ptr,type,member) ( {\

  const typeof( ((type*)0)->member ) *__mptr=(ptr);\

  (type*)( (char*)__mptr - offsetof(type,member) );} )

这个宏是由两个语句组成,最后container_of返回的结果就是第二个表达式的值。这里__mptr为中间变量,这就是list_head指针类型,它被初始化为ptr的值,而ptr就是当前所求的结构体中list_head节点的地址。为什么要用中间变量,这是考虑到安全性因素,如果传进来一个ptr++,所有ptr++放在一个表达式中会有副作用,像 (p++)+(p++)之类。

(char*)__mptr 之所以要强制类型转化为char是因为地址是以字节为单位的,而char的长度就是一个字节。

container_of的值是两个地址相减,

刚说了__mptr是结构体中list_head节点的地址,offset宏求的是list_head节点MEMBER在结构体TYPE中的偏移量,那么__mptr减去它所在结构体中的偏移量,就是结构体的地址。

所以list_entry(ptr,type,member)宏的功能就是,由结构体成员地址求结构体地址。其中ptr 是所求结构体中list_head成员指针,type是所求结构体类型,member是结构体list_head成员名。通过下图来总结一下:

 

 

继续列举一些双链表的常用操作:

双向链表的遍历——list_for_each

//注:这里prefetch 是gcc的一个优化选项,也可以不要

#define list_for_each(pos, head) \

         for (pos = (head)->next; prefetch(pos->next), pos != (head); \

                 pos = pos->next)

 

生成双向链表的头结点——LIST_HEAD()

LIST_HEAD() -- 生成一个名为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()

将new所代表的结构体插入head所管理的双向链表的头节点head之后: (即插入表头)

static inline void list_add(struct list_head *new, struct list_head *head)

{

  __list_add(new, head, head->next);

}

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;

}

从list中删除结点——list_del()

static inline void list_del(struct list_head *entry)

{

  __list_del(entry->prev, entry->next);

  entry->next = LIST_POISON1;

  entry->prev = LIST_POISON2;

}

static inline void __list_del(struct list_head * prev, struct list_head * next)

{

  next->prev = prev;

  prev->next = next;

}

 

判断链表是否为空(如果双向链表head为空则返回真,否则为假)——list_empty()

static inline int list_empty(const struct list_head *head)

{

  return head->next == head;

}



list.h

/*
注:这个list.h 是为了配合示例程序而建的,内容来自:linux/include/linux/list.h 和相关文件
*/
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
 
struct list_head {
         struct list_head *next, *prev;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

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

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


static inline void INIT_LIST_HEAD(struct list_head *list)
{
        list->next = list;
        list->prev = list;
}

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_add(struct list_head *new, struct list_head *head)
{
        __list_add(new, head, head->next);
}
 
 
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
        next->prev = prev;
        prev->next = next;
}
 
static inline void list_del(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);
        entry->next = NULL;
        entry->prev = NULL;
}


#define prefetch(x) __builtin_prefetch(x)


//注:这里prefetch 是gcc的一个优化,也可以不要
#define list_for_each(pos, head) \
         for (pos = (head)->next; prefetch(pos->next), pos != (head); \
                 pos = pos->next)

#define list_entry(ptr, type, member) \
         container_of(ptr, type, member)

#endif


一个简单的应用程序:


#include "list.h" 
#include <stdio.h> 
#include <string.h>

#define MAX_NAME_LEN 32
#define MAX_ID_LEN 10

typedef struct stud
{
    struct list_head list;
    char name[MAX_NAME_LEN];
    char stu_number[MAX_ID_LEN];
}num_n_stu;

int main(void)
{
    struct list_head head;
    num_n_stu stu_1;
    num_n_stu stu_2;
    num_n_stu *entry;

    struct list_head *p;
    INIT_LIST_HEAD(&head);
    strcpy(stu_1.name,"lisi");
    strcpy(stu_1.stu_number,"10000000");

    strcpy(stu_2.name,"zhangsan");
    strcpy(stu_2.stu_number,"10000001");
    list_add(&stu_1.list,&head);
    list_add(&stu_2.list,&head);
    list_del(&stu_2.list);
    list_for_each(p,&head)
    {
        entry=list_entry(p,struct stud,list);
        printf("name: %s\n",entry->name);
        printf("stu_number: %s\n",entry->stu_number);
    }
    list_del(&stu_1.list);
    return 0;
}

(一)结构体定义
struct list_head {

    struct list_head *next,*prev;

}
;

#define LIST_HEAD_INIT(name){&(name),&(name)}

#define LIST_HEAD(name)\

    struct list_head name = LIST_HEAD_INIT(name)
申请一个变量LIST_HEAD(temp)等价于这个:
struct list_head temp = {&(temp), &(temp)}

附带知识:
结构体赋值:

1、对成员赋值

例如结构体struct st1 {

int a;

int b;

int c;

}

1.1 用{}形式

struct st1 st1 = {1,2,3);

1.2 linux kernel风格.

struct st1 st1 = {

.a = 1;

.b = 2;

};

//注 此风格(即在成员变量之前加点“.”),可以不按成员变量的顺序进行赋值。如可以为

struct st1 st1 = {

.c = 3;

.a = 1;

.b = 2;

};

2 对整体赋值.

struct st1 a, b;

b = a;

3 结构体作为函数返回值对另一个结构体赋值.

struct st1 func1();

struct st1 a = func1();

(二)结构体初始化
结构体初始化函数:
static inline void INIT_LIST_HEAD(struct list_head*list)

{

    list->next= list;

    list->prev= list;

}
初始化结构体list指向自己本身。
对于(一)结构体定义和(二)结构体初始化来说,最终的效果是一样的,都是将一个
struct list_head变量指向自己本身。
可以写一个小的程序测试一下
  1. /*test.c*/
  2. #include <stdio.h>
  3. struct list_head {
  4.     struct list_head *next,*prev;
  5. };
  6. #define LIST_HEAD_INIT(name){&(name),&(name)}
  7. #define LIST_HEAD(name)\
  8.     struct list_head name = LIST_HEAD_INIT(name)
  9. static inline void INIT_LIST_HEAD(struct list_head*list)
  10. {
  11.     list->next= list;
  12.     list->prev= list;
  13. }
  14. int main()
  15. {
  16.     LIST_HEAD(temp);
  17.     printf("%p %p %p\n",(&temp)->prev,(&temp)->next,&temp);
  18.     INIT_LIST_HEAD(&temp);
  19.     printf("%p %p %p\n",(&temp)->prev,(&temp)->next,&temp);
  20.     return 0;
  21. }
  22. 运行结果:
  23. ^_^[sunny@sunny-laptop~/DS]11$./a.out
  24. 0xbf8191a8 0xbf8191a8 0xbf8191a8
  25. 0xbf8191a8 0xbf8191a8 0xbf8191a8
  26. ^_^[sunny@sunny-laptop~/DS]12$
可以看出他们完毕之后的地址都是一样的。

(三)增加结点
附带知识:
内联函数 inline

在c 中,为了解决一些频繁调用的小函数而大量消耗栈空间或者是叫栈内存的问题,特别的引入了inline修饰符,表示为内联函数。

内联函数使用inline关键字定义,

并且函数体和声明必须结合在一起,

否则编译器将他作为普通函数对待。

inline void function(int x); //仅仅是声明函数,没有任何效果

inline void function(int x) //正确

{

return x;

}
增加结点的话,有两种方式:头插法和尾插法。
我们调用的话就调用
static inline void list_add(struct list_head *new, struct list_head *head);
static inline void list_add_tail(struct list_head *new, struct list_head *head);
这两个接口。
头插法:
static inline void list_add(struct list_head *new, struct list_head *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);
}
真正的实现插入:
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;
}
__list_add(new, prev, next):表示在prev和next之间添加一个新的节点new
所以对于list_add()中的__list_add(new, head, head->next)表示的在head和
head->next之间加入一个新的节点,是头插法。
对于list_add_tail()中的__list_add(new, head->prev, head)表示在head->prev(双向循环链表的最后一个结点)和head之间添加一个新的结点。

(四)删除结点
指定一个结点,删除这个结点
我们调用的话,调用
static inline void list_del(struct list_head *entry)
这个函数接口就可以了。
  1. static inline void __list_del(struct list_head* prev, struct list_head*next)
  2. {
  3.     next->prev= prev;
  4.     prev->next=next;
  5. }
  6. static inline void list_del(struct list_head*entry)
  7. {
  8.     __list_del(entry->prev, entry->next);
  9.     entry->next= LIST_POISON1;
  10.     entry->prev= LIST_POISON2;
  11. }
__list_del(entry->prev, entry->next)表示将entry的前一个和后一个之间建立关联。
至于让,
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
LIST_POISON1和LIST_POISON2这两个变量在poison.h中定义的:
#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
prev、next指针分别被设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问(对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障)。
还有一个删除操作的接口函数
  1. static inline void list_del_init(struct list_head*entry)
  2. {
  3.     __list_del(entry->prev, entry->next);
  4.     INIT_LIST_HEAD(entry);
  5. }
这个函数首先将entry从双向链表中删除之后,并且将entry初始化为一个空链表。
list_del(entry)和list_del_init(entry)唯一不同的是对entry的处理,前者是将entry设置为不可用,后者是将其设置为一个空的链表的开始。

(五)替换结点
结点的替换操作是将old的结点替换成new,提供的接口是
static inline void list_replace_init(struct list_head *old,
struct list_head *new);
下面是替换的有关代码
  1. static inline void list_replace(struct list_head*old,
  2.                 struct list_head *new)
  3. {
  4.     new->next= old->next;
  5.     new->next->prev= new;
  6.     new->prev= old->prev;
  7.     new->prev->next= new;
  8. }
  9. static inline void list_replace_init(struct list_head*old,
  10.                     struct list_head *new)
  11. {
  12.     list_replace(old, new);
  13.     INIT_LIST_HEAD(old);
  14. }
List_replace_init首先调用list_replace改变new和old的指针关系,然后调用INIT_LIST_HEAD(old)将其设置为一个指向自己的结点(这个操作和前面的删除操作是一样的——初始化)。

(六)结点搬移
搬移就是将一个结点从一个链表但终删除之后,加入到其他的一新的链表当中。这里提供了两个接口:
static inline void list_move(struct list_head *list, struct list_head *head)
static inline void list_move_tail(struct list_head *list,struct list_head *head)
前者是加入的时候使用头插法,后者使用的是尾插法。
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
首先调用__list_del(list->prev, list->next),将list的前一个结点和后一个结点建立联系,之后调用list_add(list, head)将list添加到head的链表。下面的和这个类似,不同的是使用的是尾插法。
static inline void list_move_tail(struct list_head *list,
 struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}

(七)检测是否为最后节点、检测链表是否为空、检测链表是不是有一个成员结点
判断list这个结点是不是链表head的最后一个节点。
static inline int list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
下面两个接口都是判断head这个链表是不是为一个空链表(也就是只进行过初始化操作或者是刚申请的一个变量)。
  1. static inline int list_empty(const struct list_head*head)
  2. {
  3.     return head->next== head;
  4. }
  5. static inline int list_empty_careful(const struct list_head*head)
  6. {
  7.     struct list_head *next = head->next;
  8.     return (next== head)&&(next== head->prev);
  9. }
list_empty()函数和list_empty_careful()函数都是用来检测链表是否为空的。但是稍有区别的就是第一个链表使用的检测方法是判断表头的结点的下一个结点是否为其本身,如果是则返回为1,否则返回0。第二个函数使用的检测方法是判断表头的前一个结点和后一个结点是否为其本身,如果同时满足则返回0,否则返回值为1。
这主要是为了应付另一个cpu正在处理同一个链表而造成next、prev不一致的情况。但代码注释也承认,这一安全保障能力有限:除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
下面的这个函数是用来判断head这个链表是不是只有一个成员结点(不算带头结点的那个head)。
  1. static inline int list_is_singular(const struct list_head*head)
  2. {
  3.     return !list_empty(head)&&(head->next== head->prev);
  4. }

(八)旋转链表的第一个节点到最后
这个函数的操作的最终结果是将head的next与head自己本身进行了交换。
  1. static inline void list_rotate_left(struct list_head*head)
  2. {
  3.     struct list_head *first;
  4.     if (!list_empty(head)){
  5.         first = head->next;
  6.         list_move_tail(first, head);
  7.     }
  8. }

(九)分割链表
这里提供的函数接口是:
static inline void __list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
list:将剪切的结点要加进来的链表
head:被剪切的链表
entry:所指位于由head所指领头的链表内,它可以指向head,但是这样的话,head就不能被剪切了,在代码中调用了INIT_LIST_HEAD(list)。
是将head(不包括head)到entry之间的所有结点剪切下来加到list所指向的链表中。这个操作之后就有了两个链表head和list。具体的结果参看下面的截图:
  1. static inline void list_cut_position(struct list_head*list,
  2.         struct list_head *head, struct list_head *entry)
  3. {
  4.     if (list_empty(head))
  5.         return;
  6.     if (list_is_singular(head)&&
  7.         (head->next!= entry && head!= entry))
  8.         return;
  9.     if (entry == head)
  10.         INIT_LIST_HEAD(list);
  11.     else
  12.         __list_cut_position(list, head, entry);
  13. }
真正的分割操作是下面的函数:
  1. static inline void __list_cut_position(struct list_head*list,
  2.         struct list_head *head, struct list_head *entry)
  3. {
  4.     struct list_head *new_first = entry->next;
  5.     list->next= head->next;
  6.     list->next->prev= list;
  7.     list->prev= entry;
  8.     entry->next= list;
  9.     head->next= new_first;
  10.     new_first->prev= head;
  11. }
分割之前的情况:
 
分割之后的结果:
 
(十)链表的合并
提供的接口有四个:
static inline void list_splice(const struct list_head *list,
struct list_head *head)
static inline void list_splice_tail(struct list_head *list,
struct list_head *head)
static inline void list_splice_init(struct list_head *list,
   struct list_head *head)
static inline void list_splice_tail_init(struct list_head *list,
struct list_head *head)
真正的合并操作是这个函数实现的:
  1. static inline void __list_splice(const struct list_head*list,
  2.                  struct list_head *prev,
  3.                  struct list_head *next)
  4. {
  5.     struct list_head *first = list->next;
  6.     struct list_head *last = list->prev;

  7.     first->prev= prev;
  8.     prev->next= first;

  9.     last->next=next;
  10.     next->prev= last;
  11. }
这个函数实现的结果是将list领头的这个链表合并到prev和next之间,不包括list这个结点。
  1. static inline void list_splice(const struct list_head*list,
  2.                 struct list_head *head)
  3. {
  4.     if (!list_empty(list))
  5.         __list_splice(list, head, head->next);
  6. }
将list所指链的内容加到head和head->next之间(类似于头插法)。
  1. static inline void list_splice_tail(struct list_head*list,
  2.                 struct list_head *head)
  3. {
  4.     if (!list_empty(list))
  5.         __list_splice(list, head->prev, head);
  6. }
将list所指链的内容加到head->prev和head(类似于尾插法),插入之后,head->prev将会是原来的list->prev,这点需要注意。
  1. static inline void list_splice_init(struct list_head*list,
  2.                  struct list_head *head)
  3. {
  4.     if (!list_empty(list)){
  5.         __list_splice(list, head, head->next);
  6.         INIT_LIST_HEAD(list);
  7.     }
  8. }
将list所指链的内容加到head和head->next之间(类似于头插法),完了之后,将list初始化为一个空链表。
  1. static inline void list_splice_tail_init(struct list_head*list,
  2.                      struct list_head *head)
  3. {
  4.     if (!list_empty(list)){
  5.         __list_splice(list, head->prev, head);
  6.         INIT_LIST_HEAD(list);
  7.     }
  8. }
将list所指链的内容加到head->prev和head(类似于尾插法),插入之后,head->prev将会是原来的list->prev,这点需要注意,完了之后,将list初始化为一个空链表。

(十一)链表的宏遍历
在开始链表的遍历之前,先看一个问题:通过一个结构体的成员变量如何访问其他结构体成员的变量。
我们先看一个例子吧:
有这个结构体
struct symbol_list {
     int num ;
    char value [STR_TOKEN ] ; / /STR_TOKEN为一个宏定义的一个整数
    struct list_head list ;
} ;
我们的目标是通过for_each_symbol()函数得到这个结构体变量的首地址,这样,我们就能通过这个首地址访问这个结构体的其他成员变量了。
struct symbol_list *for_each_symbol(struct list_head*pos){
     const typeof ( ( (struct symbol_list * )0 ) - >list ) *ptr = pos ;
     int offset  =  ( int ) ( & ( (struct symbol_list  * )0 ) - >list ) ;
    struct symbol_list  *=  (struct symbol_list  * ) ( (char * )ptr - offset ) ;
    return p ;
}
先看第一句const typeof(((struct symbol_list *)0)->list) *ptr = pos,其中   typeof(type)是gcc的扩展,是得到type的数据类型,和我们比较熟悉的sizeof()比较类似。这一句的执行结果是申请一个struct list_head类型的指针变量,并将pos这个变量赋值给ptr,至于为什么要赋值给ptr,这里是为了防止修改pos的值。
接着,我们看第二句,int offset = (int)(&((struct symbol_list *)0)->list),将0强制转化为struct symbol_list类型的指针,并且取出list变量的地址给offset,这一句是为了得到list所指向的变量相对于整个结构体变量的相对地址。
最后一句,struct symbol_list *p = (struct symbol_list *)((char *)ptr - offset),是将ptr的值与offset相对地址相减,这样就可以得到了这个结构体变量的首地址,这样我们就可以通过这个p来获取这个结构体其他成员变量的值了。这里需要注意一点,就是将ptr强制转化的时候不是转化成了(int *),而是转化成了(char *)。指针相减:在数组中的定义是说明两个元素之间的相隔元素为单位的距离。这里就是一些指针相减的知识,char指针+1是1字节地址偏移,int指针+1是4字节地址偏移。不相信的话,你自己可以
printf("%p", pointer)
看看。

下面我们还需要看一个东西才能正式进入内核链表的遍历。
container_of宏定义在include/linux/kernel.h中:
#define container_of(ptr, type, member)({            \
         const typeof ( ( (type * )0 ) - >member ) *__mptr =  (ptr ) ;     \
         (type  * ) ( (char * )__mptr - offsetof (type ,member ) ) ; } )
offsetof宏定义在include/linux/stddef.h中:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
在container_of(ptr, type, member)中,ptr表示指向struct list_head成员变量的地址,type表示这个结构体的类型,member表示struct list_head在该结构体中的变量名称。
所以container_of(ptr, type, member)就等价于下面的东西:
const typeof(((type*)0)->member)*__mptr= (ptr);
(type  * ) ( (char * )__mptr - ( (size_t ) & ( (type * )0 ) - >member ) ) ;
再次注意一下,这里将__mptr强制转化成了(char *)类型进行了地址相减操作。
(1)这个宏好和container_of(ptr, type, member)没有什么区别:都是得到ptr所指地址的这个结构体的首地址
#define list_entry(ptr, type, member)\
    container_of (ptr , type , member )
(2)这里的ptr是一个链表的头节点,这个宏就是取得这个链表第一元素的所指结构体的首地址。
#define list_first_entry(ptr, type, member)\
    list_entry ( (ptr ) - > next , type , member )
(3)这个实际上就是一个for循环,从头到尾遍历链表。prefetch()用于预取以此提高效率。
#define list_for_each(pos, head)\
     for  (pos = (head ) - > next ; prefetch (pos - > next ) , pos ! = (head ) ; \
            pos  = pos - > next )
(4)这个实际上就是一个for循环,从头到尾遍历链表。和前一个不同的是,这个没有使用prefetch()函数来预取提高效率。
#define __list_for_each(pos, head)\
     for  (pos = (head ) - > next ; pos ! =  (head ) ; pos = pos - > next )
(5)这个实际上就是一个for循环,从尾到头遍历链表。prefetch()用于预取以此提高效率。
#define list_for_each_prev(pos, head)\
     for  (pos = (head ) - >prev ; prefetch (pos - >prev ) , pos ! =  (head ) ; \
            pos  = pos - >prev )
(6)这个实际上就是一个for循环,从头到尾遍历链表。这里使用了n来记录pos的下一个,这样处理完一个流程之后再赋给pos,避免了删除pos结点造成的问题,由它的英文注释我们可以看书,其实这个函数是专门为删除结点是准备的。
#define list_for_each_safe(pos, n, head)\
     for  (pos = (head ) - > next , n = pos - > next ; pos ! = (head ) ; \
        pos  = n , n = pos - > next )
注:list_for_each(pos, head)和list_for_each_safe(pos, n, head)都是从头至尾遍历链表的,但是对于前者来说当操作中没有删除结点的时候使用,但是如果操作中有删除结点 的操作的时候就使用后者,对于后面代safe的一般都是这个目的。
(7)这个实际上就是一个for循环,从尾到头遍历链表。这里使用了n来记录pos的前一个,这样处理完一个流程之后再赋给pos,避免了删除pos结点造成的问题,由它的英文注释我们可以看书,其实这个函数是专门为删除结点是准备的。
#define list_for_each_prev_safe(pos, n, head)\
     for  (pos = (head ) - >prev , n = pos - >prev ; \
     prefetch (pos - >prev ) , pos ! = (head ) ; \
     pos  = n , n = pos - >prev )
(8)第一个参数为传入的遍历指针,指向宿主数据结构,第二个参数为链表头,为list_head结构,第三个参数为list_head结构在宿主结构中的成员名。
list_entry((head)->next, typeof(*pos), member)用来得到head链表的第一个元素所在结构体的首地址。
这个函数是根据member成员遍历head链表,并且将每个结构体的首地址赋值给pos,这样的话,我们就可以在循环体里面通过pos来访问该结构体变量的其他成员了。而前面的list_for_each(pos, head)中的pos是list_head类型的。
#define list_for_each_entry(pos, head, member)                \
     for  (pos = list_entry ( (head ) - > next , typeof ( *pos ) , member ) ;     \
     prefetch (pos - >member . next ) , &pos - >member ! = (head ) ;      \
     pos  = list_entry (pos - >member . next , typeof ( *pos ) , member ) )
(9)和第(8)个类似,只是遍历的顺序不一样,是从尾到头来遍历。
#define list_for_each_entry_reverse(pos, head, member)            \
     for  (pos = list_entry ( (head ) - >prev , typeof ( *pos ) , member ) ;     \
     prefetch (pos - >member .prev ) , &pos - >member ! = (head ) ;      \
     pos  = list_entry (pos - >member .prev , typeof ( *pos ) , member ) )
(10)pos表示结构体变量;head表示这个链表的开始节点,是list_head类型;member是list_head在结构体当中的变量的名字。
这个函数的功能就是如果pos非空,那么pos的值就为其本身,如果pos为空,那么就从链表头强制扩展一个虚pos指针,这个宏定义是为了在list_for_each_entry_continue()中使用做准备的。
#define list_prepare_entry(pos, head, member)\
     ( (pos ) ? : list_entry (head , typeof ( *pos ) , member ) )
(11)pos表示结构体变量;head表示这个链表的开始节点,是list_head类型;member是list_head在结构体当中的变量的名字。
这个函数是根据member成员遍历head链表,并且将每个结构体的首地址赋值给pos,这样的话,我们就可以在循环体里面通过pos来访问该结构体变量的其他成员了。而前面的list_for_each(pos, head)中的pos是list_head类型的。
这个函数得遍历可以不从链表的头开始遍历,可以从一个指定的pos节点遍历。
#define list_for_each_entry_continue(pos, head, member)         \
     for  (pos = list_entry (pos - >member . next , typeof ( *pos ) , member ) ;     \
     prefetch (pos - >member . next ) , &pos - >member ! = (head ) ;     \
     pos  = list_entry (pos - >member . next , typeof ( *pos ) , member ) )
(12)和(11)类似,不同的是遍历的顺序相反。
#define list_for_each_entry_continue_reverse(pos, head, member)        \
     for  (pos = list_entry (pos - >member .prev , typeof ( *pos ) , member ) ;     \
     prefetch (pos - >member .prev ) , &pos - >member ! = (head ) ;     \
     pos  = list_entry (pos - >member .prev , typeof ( *pos ) , member ) )
(13)这个函数的遍历是从当前这个点开始遍历的。
#define list_for_each_entry_from(pos, head, member)             \
     for  ( ; prefetch (pos - >member . next ) , &pos - >member ! = (head ) ;     \
     pos  = list_entry (pos - >member . next , typeof ( *pos ) , member ) )
(14)和list_for_each_entry的遍历类似,这个带了safe是为了防止删除节点而造成断链的发生。
#define list_for_each_entry_safe(pos, n, head, member)            \
     for  (pos = list_entry ( (head ) - > next , typeof ( *pos ) , member ) ,     \
        n  = list_entry (pos - >member . next , typeof ( *pos ) , member ) ;     \
      &pos - >member ! = (head ) ;                      \
     pos  = n , n = list_entry (n - >member . next , typeof ( *n ) , member ) )
(15)和list_for_each_entry_continue()遍历类似,这个带了safe是为了防止删除节点而造成断链的发生。
#define list_for_each_entry_safe_continue(pos, n, head, member)         \
     for  (pos = list_entry (pos - >member . next , typeof ( *pos ) , member ) ,          \
        n  = list_entry (pos - >member . next , typeof ( *pos ) , member ) ;         \
      &pos - >member ! = (head ) ;                         \
     pos  = n , n = list_entry (n - >member . next , typeof ( *n ) , member ) )
(16)从当前的节点开始遍历。
#define list_for_each_entry_safe_from(pos, n, head, member)             \
     for  (n = list_entry (pos - >member . next , typeof ( *pos ) , member ) ;         \
      &pos - >member ! = (head ) ;                         \
     pos  = n , n = list_entry (n - >member . next , typeof ( *n ) , member ) )
(17)和list_for_each_entry_safe类似,不过遍历的顺序刚好相反。
#define list_for_each_entry_safe_reverse(pos, n, head, member)        \
     for  (pos = list_entry ( (head ) - >prev , typeof ( *pos ) , member ) ,     \
        n  = list_entry (pos - >member .prev , typeof ( *pos ) , member ) ;     \
      &pos - >member ! = (head ) ;                      \
     pos  = n , n = list_entry (n - >member .prev , typeof ( *n ) , member ) )
(18)list_safe_reset_next is not safe to use in general if the list may be modified concurrently (eg. the lock is dropped in the loop body). An exception to this is if the cursor element (pos) is pinned in the list, and list_safe_reset_next is called after re-taking the lock and before completing the current iteration of the loop body.

#define list_safe_reset_next(pos, n, member)                \
    n  = list_entry (pos - >member . next , typeof ( *pos ) , member )


  • 10
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值