链表

常用链表和内核链表的区别

常用链表结构

通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。按照指针域的组织以及各个节点之间的联系形式,链表又可以分为单链表、双链表、循环链表等多种类型,下面分别给出这几类常见链表类型的示意图:






内核链表结构

链表数据结构的定义很简单(节选自[include/linux/list.h],以下所有代码,除非加以说明,其余均取自该文件):

struct list_head { struct list_head *next, *prev;};  list_head结构包含两个指向list_head结构的指针prev和next,由此可见,内核的链表具备双链表功能,实际上,通常它都组织成双循环链表。list_head从字面上理解,好像是头结点的意思。但从这里的代码来看却是普通结点的结构体。在后面的代码中将list_head当成普通的结点来处理。
和第一节介绍的双链表结构模型不同,这里的list_head没有数据域。在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。

linux内核链表与普通链表的示意图:



内核链表常用函数

inux内核链表的好处

    设计思想:尽可能的代码重用,化大堆的链表设计为单个链表。
    链表的构造:如果需要构造某类对象的特定列表,则在其结构中定义一个类型为list_head指针的成员,通过这个成员将这类对象连 接起来,形成所需列表,并通过通用链表函数对其进行操作。其优点是只需编写通用链表函数,即可构造和操作不同对象的列表,而无需为每类对象的每种列表编写专用函数,实现了代码的重用。如果想对某种类型创建链表,就把一个list_head类型的变量嵌入到该类型中,用list_head中的成员和相对应的处理函数来对链表进行遍历。如果想得到相应的结构的指针,使用list_entry可以算出来。

在并发执行的环境下,链表操作通常都应该考虑同步安全性问题,为了方便,Linux将这一操作留给应用自己处理。

遍历

      遍历是链表最经常的操作之一,为了方便核心应用遍历链表,Linux链表将遍历操作抽象成几个宏。在介绍遍历宏之前,我们先看看如何从链表中访问到我们真正需要的数据项。

a) 由链表节点到数据项变量

list_entry宏是用来根据list_head指针查找链表所嵌入的结构体的地址,具体实现是依赖宏container_of:

#define list_entry(ptr, type, member)    container_of(ptr, type, 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)

      container_of有三个参数, ptr是成员变量的指针, type是指结构体的类型, member是成员变量的名字。 container_of 的作用就是在已知某一个成员变量的名字、指针和结构体类型的情况下,计算结构体的指针,也就是计算结构体的起始地址。 计算的方法其实很简单,就是用该成员变量的指针减去它于type结构体起始位置的偏移量。在这个定义中,typeof( ((type *)0)->member ) 就是获得 member 的类型, 然后定义了一个临时的常量指针 __mptr, 指向 member 变量。 把 __mptr 转换成 char * 类型, 因为 offsetof 得到的偏移量是以字节为单位。 两者相减得到结构体的起始位置, 再强制转换成 type 类型。

     offsetof在这里,TYPE表示一个结构体的类型,MEMBER是结构体中的一个成员变量的名字。offsetof 宏的作用是计算成员变量 MEMBER 相对于结构体起始位置的内存偏移量,以字节(Byte)为单位。
b) 遍历宏

函数首先定义一个(struct list_head *)指针变量i,然后调用list_for_each(i,&nf_sockopts)进行遍历。在[include/linux/list.h]中,list_for_each()宏是这么定义的:

#define list_for_each(pos, head)

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

它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,直至又回到head(prefetch()可以不考虑,用于预取以提高遍历速度)。

大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说list_for_each()和list_entry()总是同时使用。对此Linux给出了一个list_for_each_entry()宏,与list_for_each()不同,这里的pos是数据项结构指针类型,而不是(struct list_head *)。  

#define list_for_each_entry(pos, head, member)……

某些应用需要反向遍历链表,Linux提供了list_for_each_prev()和list_for_each_entry_reverse()来完成这一操作,使用方法和上面介绍的list_for_each()、list_for_each_entry()完全相同。  


list_for_each()与list_for_each_safe()的区别

list_for_each()的定义:

[plain]  view plain  copy
 print ?
  1. /**  
  2.  * list_for_each    -   iterate over a list  
  3.  * @pos:    the &struct list_head to use as a loop counter.  
  4.  * @head:   the head for your list.  
  5.  */  
  6. #define list_for_each(pos, head) \  
  7.     for (pos = (head)->next, prefetch(pos->next); pos != (head); \  
  8.         pos = pos->next, prefetch(pos->next))  

list_for_each_safe()的定义:

[plain]  view plain  copy
 print ?
  1. /**  
  2.  * list_for_each_safe   -   iterate over a list safe against removal of list entry  
  3.  * @pos:    the &struct list_head to use as a loop counter.  
  4.  * @n:      another &struct list_head to use as temporary storage  
  5.  * @head:   the head for your list.  
  6.  */  
  7. #define list_for_each_safe(pos, n, head) \  
  8.     for (pos = (head)->next, n = pos->next; pos != (head); \  
  9.         pos = n, n = pos->next)  

由上面两个对比来看,list_for_each_safe()函数比list_for_each()多了一个中间变量n

当在遍历的过程中需要删除结点时,来看一下会出现什么情况:

list_for_each():list_del(pos)将pos的前后指针指向undefined state,导致kernel panic,list_del_init(pos)将pos前后指针指向自身,导                             致死循环。

list_for_each_safe():首先将pos的后指针缓存到n,处理一个流程后再赋回pos,避免了这种情况发生。


因此之遍历链表不删除结点时,可以使用list_for_each(),而当由删除结点操作时,则要使用list_for_each_safe()。

其他带safe的处理也是基于这个原因。


函数操作解析


参见内核链表操作解析: http://blog.youkuaiyun.com/suifengpiao_2011/article/details/54290369

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值