linux内核中链表操作

来源:互联网 发布:淘宝客招代理 编辑:程序博客网 时间:2024/05/22 15:21

List_head

下面的一些内容是我在学习linux内核中list.h文件中list_head时为了更好的理解而找的一些资料,我把它们整理一下发上来以供今后参考一下。其中也有部分是我自己在学习中的体会。下面的代码是我从list.h中复制过来的,是源码来的,没有改变,主要是为了大家能清楚的知道list.h的源码。
List_head这个结构体在list.h中的主要作用不是保存数据而是作为一个链表的一个节点来保存地址,为另一个结构体的数据能够获取作铺垫。具体的情况看完了下面的一些资料就清晰明白了。
list.h头文件集中定义了双链表(struct list_head结构体)的相关操作。比如这里的一个头文件中就有大量的struct list_head型的数据。
下面先介绍list_head的具体构成
 Struct list_head{
       Struct list_head *next;
       Struct list_head *prev;
};
List_head中的两个成员分别为两个指针,这个两个指针分别是双向链表的两个不通指向的指针。从这个结构体的形式可以看出它是不能存放数据的。所以也只能用来作别人的链接。
 

下面介绍一下list.h各个函数的具体实现。


1.链表的初始化

其实可以从后往前看,这样更容易理解。INIT_LIST_HEAD函数形成一个空链表。这个list变量一般作为头指针(非头结点)。

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

}

LIST_HEAD_INIT(name)将name的地址直接分别赋值给next和prev,那么它们事实上都指向自己,也形成一个空链表。现在再回头看宏LIST_HEAD(name),它其实就是一个定义并初始化作用。

#define LIST_HEAD_INIT(name) { &(name), &(name) }
 
下面的宏生成一个头指针name,如何生成?请看LIST_HEAD_INIT(name)。#define LIST_HEAD(name) \
       struct list_head name = LIST_HEAD_INIT(name)
 

3.添加元素

这两个函数分别给链表头结点后,头结点前添加元素。前者可实现栈的添加元素,后者可实现队列的添加元素。
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);


这两个函数如何实现的?它们均调用的下面函数:这种调用的情况在list.h文件中是很常见的。


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和list_add_tail两函数在调用__list_add函数时,对应的各个参数分别是什么?通过下面所列代码,我们可以发现这里的参数运用的很巧妙,类似JAVA中的封装。
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);                            
}
 

注意,这里的形参prev和next是两个连续的结点。这其实是数据结构中很普通的双链表元素添加问题,在此不再赘述。


3.删除元素

这里又是一个调用关系,__list_del函数具体的过程很简单,分别让entry节点的前后两个结点(prev和next)“越级”指向彼此。请注意这个函数的后两句话,它属于不安全的删除。


其中LIST_POISON1;LIST_POISON2;是两个宏来的他们的定义不在list.h中,


具体的定义:


#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
static inline void list_del(struct list_head *entry)
{
       __list_del(entry->prev, entry->next);
       entry->next = LIST_POISON1;
       entry->prev = LIST_POISON2;
}
想要安全的删除,那么可以调用下面函数。还记得INIT_LIST_HEAD(entry)吗,它可以使entry节点的两个指针指向自己。为什么为安全看了下面的代码就知道了。
static inline void list_del_init(struct list_head *entry)
{
       __list_del_entry(entry);
       INIT_LIST_HEAD(entry);
}
      

4.替换元素

用new结点替换old结点同样很简单,几乎是在old->prev和old->next两结点之间插入一个new结点。画图即可理解。


static inline void list_replace(struct list_head *old,
                            struct list_head *new)
{
       new->next = old->next;
       new->next->prev = new;
       new->prev = old->prev;
       new->prev->next = new;
}
下面是它的另一种版本,也可以说是安全版本吧,具体的使用看个人的喜好。
static inline void list_replace_init(struct list_head *old,//初始化并代替
                                   struct list_head *new)
{
       list_replace(old, new);
       INIT_LIST_HEAD(old);
}
 

5.移动元素

理解了删除和增加结点,那么将一个节点移动到链表中另一个位置,其实就很清晰了。list_move函数最终调用的是__list_add(list,head,head->next),实现将list移动到头结点之后;而list_move_tail函数最终调用__list_add_tail(list,head->prev,head),实现将list节点移动到链表末尾。
static inline void list_move(struct list_head *list, struct list_head *head)
{
       __list_del_entry(list);//删除
       list_add(list, head);//添加
}
static inline void list_move(struct list_head *list, struct list_head *head)
{
       __list_del_entry(list);//删除
       list_add(list, head);//添加
}

6.测试函数

接下来的几个测试函数,基本上是“代码如其名”。


list_is_last函数是测试list是否为链表head的最后一个节点。


static inline int list_is_last(const struct list_head *list,
                            const struct list_head *head)
{
       return list->next == head;
}
下面的函数是测试head链表是否为空链表。注意这个list_empty_careful函数,他比list_empty函数“仔细”在那里呢?前者只是认为只要一个结点的next指针指向头指针就算为空,但是后者还要去检查头节点的prev指针是否也指向头结点。另外,这种仔细也是有条件的,只有当其他cpu的链表操作只有list_del_init()时,否则仍然不能保证安全。
static inline int list_empty(const struct list_head *head)
{
       return head->next == head;
}
static inline int list_empty_careful(const struct list_head *head)
{
       struct list_head *next = head->next;
       return (next == head) && (next == head->prev);
}
下面的函数是测试head链表是否只有一个结点:这个链表既不能是空而且head前后的两个结点都得是同一个结点。
static inline int list_is_singular(const struct list_head *head)
{
       return !list_empty(head) && (head->next == head->prev);

}


7.将链表左转180度

正如注释说明的那样,此函数会将这个链表以head为转动点,左转180度。整个过程就是将head后的结点不断的移动到head结点的最左端。如果是单个结点那么返回真,否则假。


static inline void list_rotate_left(struct list_head *head)
{
       struct list_head *first;
 
       if (!list_empty(head)) {
              first = head->next;
              list_move_tail(first, head);
       }
}

上述函数每次都调用 list_move_tail(first, head);其实我们将其分解到“最小”,那么这个函数每次最终调用的都是:__list_del(first->prev,first->next);和__list_add(list,head->prev,head);这样看起来其实就一目了然了。


8.将链表一分为二

这个函数是将head后至entry之间(包括entry)的所有结点都“切开”,让他们成为一个以list为头结点的新链表。我们先从宏观上看,如果head本身是一个空链表则失败;如果head是一个单结点链表而且entry所指的那个结点又不再这个链表中,也失败;当entry恰好就是头结点,那么直接初始化list,为什么?因为按照刚才所说的切割规则,从head后到entry前事实上就是空结点。如果上述条件都不符合,那么就可以放心的“切割”了。


 
static inline void list_cut_position(struct list_head *list,
              struct list_head *head, struct list_head *entry)
{
       if (list_empty(head))
              return;
       if (list_is_singular(head) &&
              (head->next != entry && head != entry))
              return;
       if (entry == head)
              INIT_LIST_HEAD(list);
       else
              __list_cut_position(list, head, entry);
}
具体如何切割,这里的代码貌似很麻烦,可是我们画出图后,就“一切尽在不言中”了。
static inline void __list_cut_position(struct list_head *list,
              struct list_head *head, struct list_head *entry)
{
       struct list_head *new_first = entry->next;
       list->next = head->next;
       list->next->prev = list;
       list->prev = entry;
       entry->next = list;
       head->next = new_first;
       new_first->prev = head;
}

9.合并链表

既然我们可以切割链表,那么当然也可以合并了。先看最基本的合并函数,就是将list这个链表(不包括头结点)插入到prev和next两结点之间。这个代码阅读起来不困难,基本上是“见码知意”。


static inline void __list_splice(const struct list_head *list,
                             struct list_head *prev,
                             struct list_head *next)//合并的实现
{
       struct list_head *first = list->next;
       struct list_head *last = list->prev;
 
       first->prev = prev;
       prev->next = first;
 
       last->next = next;
       next->prev = last;
}
理解了最基本的合并函数,那么将它封装起来,就可以形成下面两个函数了,分别在head链表的首部和尾部合并。这里的调用过程类似增加,删除功能。
static inline void list_splice(const struct list_head *list,
                            struct list_head *head)//合并在前面
{
       if (!list_empty(list))
              __list_splice(list, head, head->next);
}
static inline void list_splice_tail(struct list_head *list,
                            struct list_head *head)//合并的位置不一样,这个是再后面
{
       if (!list_empty(list))
              __list_splice(list, head->prev, head);
}

合并两个链表后,list还指向原链表,因此应该初始化。在上述两函数末尾添加初始化语句INIT_LIST_HEAD(list);后,就安全了。


10.遍历

下面我们要分析链表的遍历。虽然涉及到遍历的宏比较多,但是根据我们前面分析的那样,掌握好最基本的宏,其他宏就是进行“封装”。便利中的基本宏是:


#define __list_for_each(pos, head) \
       for (pos = (head)->next; pos != (head); pos = pos->next)
 
head是整个链表的头指针,而pos则不停的往后移动。但是你有没有觉得,这里有些奇怪?因为我们在上篇文章中说过,struct list_head结构经常和其他数据组成新的结构体,那么现在我们只是不停的遍历新结构体中的指针,如何得到其他成员?因此我们需要搞懂list_entry这个宏:
#define list_entry(ptr, type, member) \
       container_of(ptr, type, member)

这个宏是很重要的一个宏,他的具体实现就是container_of




0 0
原创粉丝点击