Linux内核中LIST_HEAD分析

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的地址直接分别赋值给nextprev,那么它们事实上都指向自己,也形成一个空链表。现在再回头看宏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_addlist_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);                            

}

 

注意,这里的形参prevnext是两个连续的结点。这其实是数据结构中很普通的双链表元素添加问题,在此不再赘述。下面的图可供参考,图中14分别对应__list_add函数的四条语句。

<!--[if !vml]--> <!--[endif]-->

3.删除元素

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

其中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->prevold->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;

}

图示:

<!--[if !vml]--> <!--[endif]-->

9.合并链表

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

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

下面是container of的具体实现

这个宏的作用是通过ptr指针获取type结构的地址,也就是指向type的指针。其中ptr是指向member成员的指针。这个list_entry宏貌似很简单的样子,就是再调用container_of宏,可是当你看了container_of宏的定义后……

 

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

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

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

是不是让人有点抓狂?别急,我们一点点来分析。

首先这个宏包含两条语句。第一条:const typeof( ((type *)0)->member ) *__mptr = (ptr);首先将0转化成type类型的指针变量(这个指针变量的地址为0×0),然后再引用member成员(对应就是((type *)0)->member ))。注意这里的typeofx),是返回x的数据类型,那么 typeof( ((type *)0)->member )其实就是返回member成员的数据类型。那么这条语句整体就是将__mptr强制转换成member成员的数据类型,再将ptr的赋给它(ptr本身就是指向member的指针)

第二句中,我们先了解offsetof是什么?它也是一个宏被定义在:linux/include/stddef.h中。原型为:

#define offsetof(type,member) ((size_t) &((type*)0)->member)

这个貌似也很抓狂,不过耐心耐心:((TYPE *)0)->MEMBER)这个其实就是提取type类型中的member成员,那么&((TYPE *)0)->MEMBER)得到member成员的地址,再强制转换成size_t类型(unsigned int)。但是这个地址很特别,因为TYPE类型是从0×0开始定义的,那么我们现在得到的这个地址就是member成员在TYPE数据类型中的偏移量。

我们再来看第二条语句, (type *)( (char *)__mptr – offsetof(type,member) )求的就是type的地址,即指向type的指针。不过这里要注意__mptr被强制转换成了(char *),为何要这么做?因为如果member是非char型的变量,比如为int型,并且假设返回值为offset,那么这样直接减去偏移量,实际上__mptr会减去sizeof(int)*offset!这一点和指针加一减一的原理相同。

有了这个指针,那么就可以随意引用其内的成员了。关于此宏的更具体了解,不妨亲自动手测试这里的程序。

好了,现在不用抓狂了,因为了解了list_entry宏,接下来的事情就很简单了。

下面这个宏会得到链表中第一个结点的地址。

#define list_first_entry(ptr, type, member) \

       list_entry((ptr)->next, type, member)

真正遍历的宏登场了,整个便利过程看起来很简单,可能你对prefetch()陌生,它的作用是预取节点,以提高速度。

#define list_for_each(pos, head) \

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

          pos = pos->next)

我们再来看一开始我们举例的那个便利宏。注意它和上述便利宏的区别就是没有prefetch(),因为这个宏适合比较少结点的链表。

#define __list_for_each(pos, head) \

       for (pos = (head)->next; pos != (head); pos = pos->next)

下面两个宏是上述两个便利宏的安全版,我们看它安全在那里?它多了一个与pos同类型的n,每次将下一个结点的指针暂存起来,防止pos被释放时引起的链表断裂。

#define list_for_each_safe(pos, n, head) \

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

              pos = n, n = pos->next)

#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)

前面我们说过,用在list_for_each宏进行遍历的时候,我们很容易得到pos,我们都知道pos存储的是当前结点前后两个结点的地址。而通过list_entry宏可以获得当前结点的地址,进而得到这个结点中其他的成员变量。而下面两个宏则可以直接获得每个结点的地址,我们接下来看它是如何实现的。为了方便说明以及便于理解,我们用上文中的结构struct stu来举例。pos是指向struct stu结构的指针;list是一个双链表,同时也是这个结构中的成员,head便指向这个双链表;member其实就是这个结构体中的list成员。

for循环中,首先通过list_entry来获得第一个结点的地址;&pos->member != (head)其实就是&pos->list!=(head);它是用来检测当前list链表是否到头了;最后在利用list_entry宏来获得下一个结点的地址。这样整个for循环就可以依次获得每个结点的地址,进而再去获得其他成员。理解了list_for_each_entry宏,那么list_for_each_entry_reverse宏就显而易见了。

#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))

#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))

下面这两个宏是从当前结点的下一个结点开始继续(或反向)遍历。

#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))

#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))

接下来几个宏又分别是上述几个宏的安全版。安全原因上面已经说过,在此不再赘述。

1

list_for_each_entry_safe(pos, n, head, member)

2

list_for_each_entry_safe_continue(pos, n, head, member)


3

list_for_each_entry_safe_from(pos, n, head, member)

4

list_for_each_entry_safe_reverse(pos, n, head, member)

具体的使用实例下面看代码,这个是我写的一个小测试实例,就是针对一个常用的函数来测试了一下,其他的函数看了源码以后使用就不是什么问题了

#include<linux/list.h>就可以了*/

 

#include"list_head.h"

#include<stdio.h>

#include<string.h>

 

 

typedef struct server_detect_ftp{//一个结构体

       struct list_head list;

       char servername[20];

       int port;

       char username[30];

       char password[50];

}server;

 

/*typedef struct splice{//另一个结构体

       struct list_head old;

       struct list_head list;

       char servername[20];

       int port;

       char username[30];

       char password[50];

}splice;

*/

 

int main(){

       struct list_head head;//头部

       struct list_head new;

       server ftp_link;

       server ftp_linkl;

       server *entry;

       struct list_head *p;

       INIT_LIST_HEAD(&head);//初始化链表头

       INIT_LIST_HEAD(&new);

       strcpy(ftp_link.servername,"gzjz1234");

       ftp_link.port=34;

       strcpy(ftp_link.username,"helloword");

       strcpy(ftp_link.password,"helloword");

      

       strcpy(ftp_linkl.servername,"www.192.168.1.107");

       ftp_linkl.port=345;

       strcpy(ftp_linkl.username,"goodl");

       strcpy(ftp_linkl.password,"goodl");

      

      

       list_add(&ftp_link.list,&head);//添加到链表中去

       //list_add_tail(&ftp_linkl.list,&head);//添加的另一种方式

       list_add(&ftp_linkl.list,&new);

      

       list_splice(&new,&head);//把两个链表合并在一起

      

       //list_replace(&ftp_link.list,&ftp_linkl.list);//替换函数

       //list_del(&ftp_linkl.list);//删除函数

      

       list_for_each(p,&head){//遍历链表并取得每个节点的值,这里要看清楚 p 不是结构里的成员

              entry=list_entry(p,struct server_detect_ftp,list);//读取某个值

              printf("%d %s %s\n ",entry->port,entry->servername,entry->username);

       }

       return 0;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值