Linux 中的链表list 和 hlist

前言

在网上发现关于此的文章转载到处都是。我也就不再做重复性的工作了,将我觉得重要的做下笔记就好了,整理下思路。

概况

链表是经典的组织有序数据的数据结构,是线性表的一种重要实现方式(另外一种是数组)。其基本思想是通过指针将一系列数据节点连接成一条数据链,达到有序组织数据的目的。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。链表的开销主要是访问的顺序性和组织链的空间损失。

通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。按照指针域的组织以及各个节点之间的联系形式,链表有许多变种,如单链表,双链表,带头(尾)节点的链表,循环链表等。其中在参考文献中讨论相关各个结构的效率问题。

View Code
数组的特点

1 .固定空间,并且空间连续
2 .连续存储,支持随机访问
3 .数据局部性好
4 .插入删除节点开销比较大,要移动元素以维持随机访问的特性。

所以,对于经常访问元素时,我们采用数组的方式来实现;如果是经常插入和删除元素时,我们采用链表来实现。

单链表只能单向遍历,而双链表可以在链表的任意节点进行遍历全链表,但是也带来了内存的开销(小数据无所谓,当数据量大到一定程度,开销还是很可观的)。

图示中演示的是带头节点的链表,即浪费一个节点,换取插入与删除的一致性(不必判断链表插入时或删除后是否为空的情况)。

                                                 单链表示意图

                                  双链表示意图

Linux下的实现

Linux 内核中使用了大量的链表结构来组织数据,包括设备列表以及各种功能模块中的数据组织。这些链表大多采用在 [include/linux/list.h] 实现的一个相当精彩的链表数据结构。

数据结构书上演示的程序,一般是节点中包含数据,定义如下

 

typedef int data_t ;
struct
node
{
struct node *next;
struct node *priv;
data_t data;//真实数据
}

但是在Linux 的实现中,节点与数据是分离的。 如果一个结构需要用到链表结构,直接包含链表hlist_head或list_head即可。如此在链表中链表头只需用一个struct list_head类型来表示即可,不管链表中节点的数据结构多么庞大,链表头只需要占4个字节或者8个字节。如下是Linux hlist list的定义。

/* *单链表 */
struct hlist_head {
struct hlist_node * first;
};
struct hlist_node {
struct hlist_node * next, ** pprev;
};


/* *双链表 */
struct list_head {
struct list_head * next, ** prev;
};

对于定义,我们或许更加习惯分开成为如下格式

struct list_head {
struct list_head * next;
struct list_head * prev;
};

注:hlist是单链表的实现 (h 代表 hash ,用在 hash ) list 是双链表的实现( task 等都有赖于该实现)

请注意hlist 的实现, prev 用到了二级指针,这种差异带来了相关插入删除等操作的不同(在后文分析)。图示给出了多个节点链接的情况(很多图示没有第一个节点的 pprev 域指向 first 的指针,请注意)。

Linux 内核链表中,需要用链表组织起来的数据通常会包含一个struct list_head成员。

如参考文献1 中就给出了nf_sockopt_ops结构的示意图

struct nf_sockopt_ops的定义为

struct nf_sockopt_ops
{
struct list_head list;
u_int8_t pf;
……
int ( * set )( struct sock * sk, int optval, void __user * user, unsigned int len);
……
int ( * compat_get)( struct sock * sk, int optval,
void __user * user, int * len);
……
struct module * owner;
};

在nf_sockopt_ops开始的部分就有一个struct list_head list,注意不是指针(占用8 字节 @32 位机器上)。

对于RCU 的讨论请参考 RCU 同步机制》

参考文献
原文地址:https://www.cnblogs.com/westfly/p/1977201.html