linux内核数据结构之链表

Posted AlanTu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核数据结构之链表相关的知识,希望对你有一定的参考价值。

1、前言

   最近写代码需用到链表结构,正好公共库有关于链表的。第一眼看时,觉得有点新鲜,和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域。后来看代码注释发现该代码来自linux内核,在linux源代码下include/Lish.h下。这个链表具备通用性,使用非常方便。只需要在结构定义一个链表结构就可以使用。

2、链表介绍

  链表是非常基本的数据结构,根据链个数分为单链表、双链表,根据是否循环分为单向链表和循环链表。通常定义定义链表结构如下:

typedef struct node
{
     ElemType data;      //数据域
     struct node *next;  //指针域
}node, *list;

链表中包含数据域和指针域。链表通常包含一个头结点,不存放数据,方便链表操作。单向循环链表结构如下图所示:

双向循环链表结构如下图所示:

  这样带数据域的链表降低了链表的通用性,不容易扩展。linux内核定义的链表结构不带数据域,只需要两个指针完成链表的操作。将链表节点加入数据结构,具备非常高的扩展性,通用性。链表结构定义如下所示:

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

链表结构如下所示:

  需要用链表结构时,只需要在结构体中定义一个链表类型的数据即可。例如定义一个app_info链表,

复制代码
1 typedef struct application_info
2 {
3     uint32_t  app_id;
4     uint32_t  up_flow;
5     uint32_t  down_flow;
6     struct    list_head app_info_head;  //链表节点
7 }app_info;
复制代码

定义一个app_info链表,app_info app_info_list;通过app_info_head进行链表操作。根据C语言指针操作,通过container_of和offsetof,可以根据app_info_head的地址找出app_info的起始地址,即一个完整ap_info结构的起始地址。可以参考:http://www.cnblogs.com/Anker/p/3472271.html

3、linux内核链表实现

  内核实现的是双向循环链表,提供了链表操作的基本功能。

(1)初始化链表头结点

复制代码
#define LIST_HEAD_INIT(name) { &(name), &(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_HEAD宏创建一个链表头结点,并用LIST_HEAD_INIT宏对头结点进行赋值,使得头结点的前驱和后继指向自己。

INIT_LIST_HEAD函数对链表进行初始化,使得前驱和后继指针指针指向头结点。

(2)插入节点

复制代码
 1 static inline void __list_add(struct list_head *new,
 2                   struct list_head *prev,
 3                   struct list_head *next)
 4 {
 5     next->prev = new;
 6     new->next = next;
 7     new->prev = prev;
 8     prev->next = new;
 9 }
10 
11 static inline void list_add(struct list_head *new, struct list_head *head)
12 {
13     __list_add(new, head, head->next);
14 }
15 
16 static inline void list_add_tail(struct list_head *new, struct list_head *head)
17 {
18     __list_add(new, head->prev, head);
19 }
复制代码

  插入节点分为从链表头部插入list_add和链表尾部插入list_add_tail,通过调用__list_add函数进行实现,head->next指向之一个节点,head->prev指向尾部节点。

(3)删除节点

复制代码
 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 
 7 static inline void list_del(struct list_head *entry)
 8 {
 9     __list_del(entry->prev, entry->next);
10     entry->next = LIST_POISON1;
11     entry->prev = LIST_POISON2;
12 }
复制代码

  从链表中删除一个节点,需要改变该节点前驱节点的后继结点和后继结点的前驱节点。最后设置该节点的前驱节点和后继结点指向LIST_POSITION1和LIST_POSITION2两个特殊值,这样设置是为了保证不在链表中的节点项不可访问,对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障

复制代码
/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
复制代码

(4)移动节点

复制代码
 1 /**
 2  * list_move - delete from one list and add as another\'s head
 3  * @list: the entry to move
 4  * @head: the head that will precede our entry
 5  */
 6 static inline void list_move(struct list_head *list, struct list_head *head)
 7 {
 8     __list_del(list->prev, list->next);
 9     list_add(list, head);
10 }
11 
12 /**
13  * list_move_tail - delete from one list and add as another\'s tail
14  * @list: the entry to move
15  * @head: the head that will follow our entry
16  */
17 static inline void list_move_tail(struct list_head *list,
18                   struct list_head *head)
19 {
20     __list_del(list->prev, list->next);
21     list_add_tail(list, head);
22 }
复制代码

move将一个节点移动到头部或者尾部。

(5)判断链表

复制代码
 1 /**
 2  * list_is_last - tests whether @list is the last entry in list @head
 3  * @list: the entry to test
 4  * @head: the head of the list
 5  */
 6 static inline int list_is_last(const struct list_head *list,
 7                 const struct list_head *head)
 8 {
 9     return list->next == head;
10 }
11 
12 /**
13  * list_empty - tests whether a list is empty
14  * @head: the list to test.
15  */
16 static inline int list_empty(const struct list_head *head)
17 {
18     return head->next == head;
19 }
复制代码

list_is_last函数判断节点是否为末尾节点,list_empty判断链表是否为空。

(6)遍历链表

复制代码
 1 /**
 2  * list_entry - get the struct for this entry
 3  * @ptr:    the &struct list_head pointer.
 4  * @type:    the type of the struct this is embedded in.
 5  * @member:    the name of the list_struct within the struct.
 6  */
 7 #define list_entry(ptr, type, member) \\
 8     container_of(ptr, type, member)
 9 
10 /**
11  * list_first_entry - get the first element from a list
12  * @ptr:    the list head to take the element from.
13  * @type:    the type of the struct this is embedded in.
14  * @member:    the name of the list_struct within the struct.
15  *
16  * Note, that list is expected to be not empty.
17  */
18 #define list_first_entry(ptr, type, member) \\
19     list_entry((ptr)->next, type, member)
20 
21 /**
22  * list_for_each    -    iterate over a list
23  * @pos:    the &struct list_head to use as a loop cursor.
24  * @head:    the head for your list.
25  */
26 #define list_for_each(pos, head) \\
27     for (pos = (head)->next; prefetch(pos->next), pos != (head); \\
28             pos = pos->next)
复制代码

宏list_entity获取链表的结构,包括数据域。list_first_entry获取链表第一个节点,包括数据源。list_for_each宏对链表节点进行遍历。

4、测试例子

编写一个简单使用链表的程序,从而掌握链表的使用。

自定义个类似的list结构如下所示:mylist.h

复制代码
 1 # define POISON_POINTER_DELTA 0
 2 
 3 #define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
 4 #define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
 5 
 6 //计算member在type中的位置
 7 #define offsetof(type, member)  (size_t)(&((type*)0)->member)
 8 //根据member的地址获取type的起始地址
 9 #define container_of(ptr, type, member) ({          \\
10         const typeof(((type *)0)->member)*__mptr = (ptr);    \\
11     (type *)((char *)__mptr - offsetof(type, member)); })
12 
13 //链表结构
14 struct list_head
15 {
16     struct list_head *prev;
17     struct list_head *next;
18 };
19 
20 static inline void init_list_head(struct list_head *list)
21 {
22     list->prev = list;
23     list->next = list;
24 }
25 
26 static inline void __list_add(struct list_head *new,
27     struct list_head *prev, struct list_head *next)
28 {
29     prev->next = new;
30     new->prev = prev;
31     new->next = next;
32     next->prev = new;
33 }
34 
35 //从头部添加
36 static inline void list_add(struct list_head *new , struct list_head *head)
37 {
38     __list_add(new, head, head->next);
39 }
40 //从尾部添加
41 static inline void list_add_tail(struct list_head *new, struct list_head *head)
42 {
43     __list_add(new, head->prev, head);
44 }
45 
46 static inline  void __list_del(struct list_head *prev, struct list_head *next)
47 {
48     prev->next = next;
49     next->prev = prev;
50 }
51 
52 static inline void list_del(struct list_head *entry)
53 {
54     __list_del(entry->prev, entry->next);
55     entry->next = LIST_POISON1;
56     entry->prev = LIST_POISON2;
57 }
58 
59 static inline void list_move(struct list_head *list, struct list_head *head)
60 {
61         __list_del(list->prev, list->next);
62         list_add(list, head);
63 }
64 
65 static inline void list_move_tail(struct list_head *list,
66                       struct list_head *head)
67 {
68         __list_del(list->prev, list->next);
69         list_add_tail(list, head);
70 }
71 #define list_entry(ptr, type, member) \\
72     container_of(ptr, type, member)
73 
74 #define list_first_entry(ptr, type, member) \\
75     list_entry((ptr)->next, type, member)
76 
77 #define list_for_each(pos, head) \\
78     for (pos = (head)->next; pos != (head); pos = pos->next)
复制代码

mylist.c如下所示:

复制代码
 1 /**@brief 练习使用linux内核链表,功能包括:
 2  * 定义链表结构,创建链表、插入节点、删除节点、移动节点、遍历节点
 3  *
 4  *@auther Anker @date 2013-12-15
 5  **/
 6 #include <stdio.h>
 7 #include <inttypes.h>
 8 #include <stdlib.h>
 9 #include <errno.h>
10 #include "mylist.h"
11 //定义app_info链表结构
12 typedef struct application_info
13 {
14     uint32_t  app_id;
15     uint32_t  up_flow;
16     uint32_t  down_flow;
17     struct    list_head app_info_node;//链表节点
18 }app_info;
19 
20 
21 app_info* get_app_info(uint32_t app_id, uint32_t up_flow, uint32_t down_flow)
22 {
23     app_info *app = (app_info*)malloc(sizeof(app_info));
24     if (app == NULL)
25     {
26     fprintf(stderr, "Failed to malloc memory, errno:%u, reason:%s\\n",
27         errno, strerror(errno));
28     return NULL;
29     }
30     app->app_id = app_id;
31     app->up_flow = up_flow;
32     app->down_flow = down_flow;
33     return app;
34 }
35 static void for_each_app(const struct list_head *head)
36 {
37     struct list_head *pos;
38     app_info *app;
39     //遍历链表
40     list_for_each(pos, head)
41     {
42     app = list_entry(pos, app_info, app_info_node);
43     printf("ap_id: %u\\tup_flow: %u\\tdown_flow: %u\\n",
44         app->app_id, app->up_flow, app->down_flow);
45 
46     }
47 }
48 
49 void destroy_app_list(struct list_head *head)
50 {
51     struct list_head *pos = head->next;
52     struct list_head *tmp = NULL;
53     while (pos != head)
54     {
55     tmp = pos->next;
56     list_del(pos);
57     pos = tmp;
58     }
59 }
60 
61 
62 int main()
63 {
64     //创建一个app_info
65     app_info * app_info_list = (app_info*)malloc(sizeof(app_info));
66     app_info *app;
67     if (app_info_list == NULL)
68     {
69     fprintf(stderr, "Failed to malloc memory, errno:%u, reason:%s\\n",
70         errno, strerror(errno));
71     return -1;
72     }
73     //初始化链表头部
74     struct list_head *head = &app_info_list->app_info_node;
75     init_list_head(head);
76     //插入三个app_info
77     app = get_app_info(1001, 100, 200);
78     list_add_tail(&app->app_info_node, head);
79     app = get_app_info(1002, 80, 100);
80     list_add_tail(&app->app_info_node, head);
81     app = get_app_info(1003, 90, 120);
82     list_add_tail(&app->app_info_node, head);
83     printf("After insert three app_info: \\n");
84     for_each_app(head);
85     //将第一个节点移到末尾
86     printf("Move first node to tail:\\n");
87     list_move_tail(head->next, head);
88     for_each_app(head);
89     //删除最后一个节点
90     printf("Delete the last node:\\n");
91     list_del(head->prev);
92     for_each_app(head);
93     destroy_app_list(head);
94     free(app_info_list);
95     return 0;
96 }
复制代码

测试结果如下所示:

参考网址:

https://www.ibm.com/developerworks/cn/linux/kernel/l-chain/

 

 

 

深入分析 Linux 内核链表

一、 链表数据结构简介

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

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

1. 单链表

图1 单链表

图1 单链表

单链表是最简单的一类链表,它的特点是仅有一个指针域指向后继节点(next),因此,对单链表的遍历只能从头至尾(通常是NULL空指针)顺序进行。

2. 双链表

图2 双链表

图2 双链表

通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点(如图2中虚线部分),就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。

3. 循环链表

循环链表的特点是尾节点的后继指向首节点。前面已经给出了双循环链表的示意图,它的特点是从任意一个节点出发,沿两个方向的任何一个,都能找到链表中的任意一个数据。如果去掉前驱指针,就是单循环链表。

在Linux内核中使用了大量的链表结构来组织数据,包括设备列表以及各种功能模块中的数据组织。这些链表大多采用在[include/linux/list.h]实现的一个相当精彩的链表数据结构。本文的后继部分就将通过示例详细介绍这一数据结构的组织和使用。

二、 Linux 2.6内核链表数据结构的实现

尽管这里使用2.6内核作为讲解的基础,但实际上2.4内核中的链表结构和2.6并没有什么区别。不同之处在于2.6扩充了两种链表数据结构:链表的读拷贝更新(rcu)和HASH链表(hlist)。这两种扩展都是基于最基本的list结构,因此,本文主要介绍基本链表结构,然后再简要介绍一下rcu和hlist。

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

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

list_head结构包含两个指向list_head结构的指针prev和next,由此可见,内核的链表具备双链表功能,实际上,通常它都组织成双循环链表。

和第一节介绍的双链表结构模型不同,这里的list_head没有数据域。在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。

在数据结构课本中,链表的经典定义方式通常是这样的(以单链表为例):

struct list_node {
    struct list_node *next;
    ElemType    data;
};

因为ElemType的缘故,对每一种数据项类型都需要定义各自的链表结构。有经验的C++程序员应该知道,标准模板库中的<list>采用的是C++ Template,利用模板抽象出和数据项类型无关的链表操作接口。

在Linux内核链表中,需要用链表组织起来的数据通常会包含一个struct list_head成员,例如在[include/linux/netfilter.h]中定义了一个nf_sockopt_ops结构来描述Netfilter为某一协议族准备的getsockopt/setsockopt接口,其中就有一个(struct list_head list)成员,各个协议族的nf_sockopt_ops结构都通过这个list成员组织在一个链表中,表头是定义在[net/core/netfilter.c]中的nf_sockopts(struct list_head)。从下图中我们可以看到,这种通用的链表结构避免了为每个数据项类型定义自己的链表的麻烦。Linux的简捷实用、不求完美和标准的风格,在这里体现得相当充分。

图3 nf_sockopts链表示意图

图3 nf_sockopts链表示意图

三、 链表操作接口

1. 声明和初始化

实际上Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来的呢?让我们来看看LIST_HEAD()这个宏:

#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)

当我们用LIST_HEAD(nf_sockopts)声明一个名为nf_sockopts的链表头时,它的next、prev指针都初始化为指向自己,这样,我们就有了一个空链表,因为Linux用头指针的next是否指向自己来判断链表是否为空:

static inline int list_empty(const struct list_head *head)
{
        return head->next == head;
}

除了用LIST_HEAD()宏在声明的时候初始化一个链表以外,Linux还提供了一个INIT_LIST_HEAD宏用于运行时初始化链表:

#define INIT_LIST_HEAD(ptr) do { \\
    (ptr)->next = (ptr); (ptr)->prev = (ptr); \\
} while (0)

我们用INIT_LIST_HEAD(&nf_sockopts)来使用它。

2. 插入/删除/合并

a) 插入

对链表的插入操作有两种:在表头插入和在表尾插入。Linux为此提供了两个接口:

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

因为Linux链表是循环表,且表头的next、prev分别指向链表中的第一个和最末一个节点,所以,list_add和list_add_tail的区别并不大,实际上,Linux分别用

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

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

来实现两个接口,可见,在表头插入是插入在head之后,而在表尾插入是插入在head->prev之后。

假设有一个新nf_sockopt_ops结构变量new_sockopt需要添加到nf_sockopts链表头,我们应当这样操作:

list_add(&new_sockopt.list, &nf_sockopts);

从这里我们看出,nf_sockopts链表中记录的并不是new_sockopt的地址,而是其中的list元素的地址。如何通过链表访问到new_sockopt呢?下面会有详细介绍。

b) 删除

static inline void list_del(struct list_head *entry);

当我们需要删除nf_sockopts链表中添加的new_sockopt项时,我们这么操作:

list_del(&new_sockopt.list);

被剔除下来的new_sockopt.list,prev、next指针分别被设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问--对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障。与之相对应,list_del_init()函数将节点从链表中解下来之后,调用LIST_INIT_HEAD()将节点置为空链状态。

c) 搬移

Linux提供了将原本属于一个链表的节点移动到另一个链表的操作,并根据插入到新链表的位置分为两类:

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

例如list_move(&new_sockopt.list,&nf_sockopts)会把new_sockopt从它所在的链表上删除,并将其再链入nf_sockopts的表头。

d) 合并

除了针对节点的插入、删除操作,Linux链表还提供了整个链表的插入功能:

static inline void list_splice(struct list_head *list, struct list_head *head);

假设当前有两个链表,表头分别是list1和list2(都是struct list_head变量),当调用list_splice(&list1,&list2)时,只要list1非空,list1链表的内容将被挂接在list2链表上,位于list2和list2.next(原list2表的第一个节点)之间。新list2链表将以原list1表的第一个节点为首节点,而尾节点不变。如图(虚箭头为next指针):

图4 链表合并list_splice(&list1,&list2)

图4 链表合并list_splice(&list1,&list2)

当list1被挂接到list2之后,作为原表头指针的list1的next、prev仍然指向原来的节点,为了避免引起混乱,Linux提供了一个list_splice_init()函数:

static inline void list_splice_init(struct list_head *list, struct list_head *head);以上是关于linux内核数据结构之链表的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核数据结构之链表

Linux(内核剖析):14---内核数据结构之链表(struct list_head)

Java数据结构线性表之链表

Java数据结构线性表之链表

Java数据结构线性表之链表

JavaScript数据结构之链表