Linux内核双向链表的实现

本章介绍的内容是 Linux 内核中双向链表的经典实现和用法。其中,也会涉及到 Linux 内核中非常常用的两个经典宏定义offsetof和container_of。内容包括:
1. Linux中的两个经典宏定义

2. Linux中双向链表的经典实现

Linux中的两个经典宏定义

倘若你查看过Linux Kernel的源码,那么你对 offsetof 和 container_of 这两个宏应该不陌生。这两个宏最初是极客写出的,后来在Linux内核中被推广使用。

1. offsetof

1.1 offsetof介绍

定义:offsetof在linux内核的include/linux/stddef.h中定义。

1. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

说明:获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
(01)  ( (TYPE *)0 )   将零转型为TYPE类型指针,即TYPE类型的指针的地址是0。
(02)  ((TYPE *)0)->MEMBER     访问结构中的数据成员。
(03)  &( ( (TYPE *)0 )->MEMBER )     取出数据成员的地址。由于TYPE的地址是0,这里获取到的地址就是相对MEMBER在TYPE中的偏移。
(04)  (size_t)(&(((TYPE*)0)->MEMBER))     结果转换类型。对于32位系统而言,size_t是unsigned int类型;对于64位系统而言,size_t是unsigned long类型。

1.2 offsetof示例
代码(offset_test.c)


01. 1 #include <stdio.h>
02. 2
03. 3 // 获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
04. 4 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
05. 5
06. 6 struct student
07. 7 {
08. 8     char gender;
09. 9     int id;
10. 10     int age;
11. 11     char name[20];
12. 12 };
13. 13
14. 14 void main()
15. 15 {
16. 16     int gender_offset, id_offset, age_offset, name_offset;
17. 17
18. 18     gender_offset = offsetof(struct student, gender);
19. 19     id_offset = offsetof(struct student, id);
20. 20     age_offset = offsetof(struct student, age);
21. 21     name_offset = offsetof(struct student, name);
22. 22    
23. 23     printf('gender_offset = %d
24. ', gender_offset);
25. 24     printf('id_offset = %d
26. ', id_offset);
27. 25     printf('age_offset = %d
28. ', age_offset);
29. 26     printf('name_offset = %d
30. ', name_offset);
31. 27 }

结果

1. gender_offset = 0
2. id_offset = 4
3. age_offset = 8
4. name_offset = 12

说明:简单说说'为什么id的偏移值是4,而不是1'。我的运行环境是linux系统,32位的x86架构。这就意味着cpu的数据总线宽度为32,每次能够读取4字节数据。gcc对代码进行处理的时候,是按照4字节对齐的。所以,即使gender是char(一个字节)类型,但是它仍然是4字节对齐的!

1.3 offsetof图解

\

TYPE是结构体,它代表'整体';而MEMBER是成员,它是整体中的某一部分。
将offsetof看作一个数学问题来看待,问题就相当简单了:已知'整体'和该整体中'某一个部分',而计算该部分在整体中的偏移。

2. container_of

2.1 container_of介绍

定义:container_of在linux内核的include/linux/kernel.h中定义。

1. #define container_of(ptr, type, member) ({         
2. const typeof( ((type *)0)->member ) *__mptr = (ptr);   
3. (type *)( (char *)__mptr - offsetof(type,member) );})

说明:根据'结构体(type)变量'中的'域成员变量(member)的指针(ptr)'来获取指向整个结构体变量的指针。
(01) typeof( ( (type *)0)->member )     取出member成员的变量类型。
(02) const typeof( ((type *)0)->member ) *__mptr = (ptr)    定义变量__mptr指针,并将ptr赋值给__mptr。经过这一步,__mptr为member数据类型的常量指针,其指向ptr所指向的地址。
(04) (char *)__mptr    将__mptr转换为字节型指针。
(05) offsetof(type,member))    就是获取'member成员'在'结构体type'中的位置偏移。
(06) (char *)__mptr - offsetof(type,member))    就是用来获取'结构体type'的指针的起始地址(为char *型指针)。
(07) (type *)( (char *)__mptr - offsetof(type,member) )    就是将'char *类型的结构体type的指针'转换为'type *类型的结构体type的指针'。

2.2 container_of示例

代码(container_test.c)


01. 1 #include <stdio.h>
02. 2 #include <string.h>
03. 3
04. 4 // 获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
05. 5 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
06. 6
07. 7 // 根据'结构体(type)变量'中的'域成员变量(member)的指针(ptr)'来获取指向整个结构体变量的指针
08. 8 #define container_of(ptr, type, member) ({         
09. 9     const typeof( ((type *)0)->member ) *__mptr = (ptr);   
10. 10     (type *)( (char *)__mptr - offsetof(type,member) );})
11. 11
12. 12 struct student
13. 13 {
14. 14     char gender;
15. 15     int id;
16. 16     int age;
17. 17     char name[20];
18. 18 };
19. 19
20. 20 void main()
21. 21 {
22. 22     struct student stu;
23. 23     struct student *pstu;
24. 24
25. 25     stu.gender = '1';
26. 26     stu.id = 9527;
27. 27     stu.age = 24;
28. 28     strcpy(stu.name, 'zhouxingxing');
29. 29
30. 30     // 根据'id地址' 获取 '结构体的地址'。
31. 31     pstu = container_of(&stu.id, struct student, id);
32. 32
33. 33     // 根据获取到的结构体student的地址,访问其它成员
34. 34     printf('gender= %c
35. ', pstu->gender);
36. 35     printf('age= %d
37. ', pstu->age);
38. 36     printf('name= %s
39. ', pstu->name);
40. 37 }

结果

1. gender= 1
2. age= 24
3. name= zhouxingxing

2.3 container_of图解
\

type是结构体,它代表'整体';而member是成员,它是整体中的某一部分,而且member的地址是已知的。
将offsetof看作一个数学问题来看待,问题就相当简单了:已知'整体'和该整体中'某一个部分',要根据该部分的地址,计算出整体的地址。

Linux中双向链表的经典实现

1. Linux中双向链表介绍

Linux双向链表的定义主要涉及到两个文件:
include/linux/types.h
include/linux/list.h

Linux中双向链表的使用思想
它是将双向链表节点嵌套在其它的结构体中;在遍历链表的时候,根据双链表节点的指针获取'它所在结构体的指针',从而再获取数据。

我举个例子来说明,可能比较容易理解。假设存在一个社区中有很多人,每个人都有姓名和年龄。通过双向链表将人进行关联的模型图如下:
\

person代表人,它有name和age属性。为了通过双向链表对person进行链接,我们在person中添加了list_head属性。通过list_head,我们就将person关联起来了。

1. struct person
2. {
3. int age;
4. char name[20];
5. struct list_head list;
6. };

2. Linux中双向链表的源码分析

(01). 节点定义

1. struct list_head {
2. struct list_head *next, *prev;
3. };

虽然名称list_head,但是它既是双向链表的表头,也代表双向链表的节点。

(02). 初始化节点

01. #define LIST_HEAD_INIT(name) { &(name), &(name) }
02.  
03. #define LIST_HEAD(name)
04. struct list_head name = LIST_HEAD_INIT(name)
05.  
06. static inline void INIT_LIST_HEAD(struct list_head *list)
07. {
08. list->next = list;
09. list->prev = list;
10. }

LIST_HEAD的作用是定义表头(节点):新建双向链表表头name,并设置name的前继节点和后继节点都是指向name本身。
LIST_HEAD_INIT的作用是初始化节点:设置name节点的前继节点和后继节点都是指向name本身。
INIT_LIST_HEAD和LIST_HEAD_INIT一样,是初始化节点:将list节点的前继节点和后继节点都是指向list本身。

(03). 添加节点

01. static inline void __list_add(struct list_head *new,
02. struct list_head *prev,
03. struct list_head *next)
04. {
05. next->prev = new;
06. new->next = next;
07. new->prev = prev;
08. prev->next = new;
09. }
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(new, prev, next)的作用是添加节点:将new插入到prev和next之间。在linux中,以'__'开头的函数意味着是内核的内部接口,外部不应该调用该接口。
list_add(new, head)的作用是添加new节点:将new添加到head之后,是new称为head的后继节点。
list_add_tail(new, head)的作用是添加new节点:将new添加到head之前,即将new添加到双链表的末尾。

(04). 删除节点

01. static inline void __list_del(struct list_head * prev, struct list_head * next)
02. {
03. next->prev = prev;
04. prev->next = next;
05. }
06.  
07. static inline void list_del(struct list_head *entry)
08. {
09. __list_del(entry->prev, entry->next);
10. }
11.  
12. static inline void __list_del_entry(struct list_head *entry)
13. {
14. __list_del(entry->prev, entry->next);
15. }
16.  
17. static inline void list_del_init(struct list_head *entry)
18. {
19. __list_del_entry(entry);
20. INIT_LIST_HEAD(entry);
21. }

__list_del(prev, next) 和__list_del_entry(entry)都是linux内核的内部接口。
__list_del(prev, next) 的作用是从双链表中删除prev和next之间的节点。
__list_del_entry(entry) 的作用是从双链表中删除entry节点。

list_del(entry) 和 list_del_init(entry)是linux内核的对外接口。
list_del(entry) 的作用是从双链表中删除entry节点。
list_del_init(entry) 的作用是从双链表中删除entry节点,并将entry节点的前继节点和后继节点都指向entry本身。

(05). 替换节点

1. static inline void list_replace(struct list_head *old,
2. struct list_head *new)
3. {
4. new->next = old->next;
5. new->next->prev = new;
6. new->prev = old->prev;
7. new->prev->next = new;
8. }

list_replace(old, new)的作用是用new节点替换old节点。

(06). 判断双链表是否为空

1. static inline int list_empty(const struct list_head *head)
2. {
3. return head->next == head;
4. }

list_empty(head)的作用是判断双链表是否为空。它是通过区分'表头的后继节点'是不是'表头本身'来进行判断的。

(07). 获取节点

1. #define list_entry(ptr, type, member)
2. container_of(ptr, type, member)

list_entry(ptr, type, member) 实际上是调用的container_of宏。
它的作用是:根据'结构体(type)变量'中的'域成员变量(member)的指针(ptr)'来获取指向整个结构体变量的指针。

(08). 遍历节点

1. #define list_for_each(pos, head)
2. for (pos = (head)->next; pos != (head); pos = pos->next)
3.  
4. #define list_for_each_safe(pos, n, head)
5. for (pos = (head)->next, n = pos->next; pos != (head);
6. pos = n, n = pos->next)

list_for_each(pos, head)和list_for_each_safe(pos, n, head)的作用都是遍历链表。但是它们的用途不一样!
list_for_each(pos, head)通常用于获取节点,而不能用到删除节点的场景。
list_for_each_safe(pos, n, head)通常删除节点的场景。

3. Linux中双向链表的使用示例

双向链表代码(list.h)


001. 1 #ifndef _LIST_HEAD_H
002. 2 #define _LIST_HEAD_H
003. 3
004. 4 // 双向链表节点
005. 5 struct list_head {
006. 6     struct list_head *next, *prev;
007. 7 };
008. 8
009. 9 // 初始化节点:设置name节点的前继节点和后继节点都是指向name本身。
010. 10 #define LIST_HEAD_INIT(name) { &(name), &(name) }
011. 11
012. 12 // 定义表头(节点):新建双向链表表头name,并设置name的前继节点和后继节点都是指向name本身。
013. 13 #define LIST_HEAD(name)
014. 14     struct list_head name = LIST_HEAD_INIT(name)
015. 15
016. 16 // 初始化节点:将list节点的前继节点和后继节点都是指向list本身。
017. 17 static inline void INIT_LIST_HEAD(struct list_head *list)
018. 18 {
019. 19     list->next = list;
020. 20     list->prev = list;
021. 21 }
022. 22
023. 23 // 添加节点:将new插入到prev和next之间。
024. 24 static inline void __list_add(struct list_head *new,
025. 25                   struct list_head *prev,
026. 26                   struct list_head *next)
027. 27 {
028. 28     next->prev = new;
029. 29     new->next = next;
030. 30     new->prev = prev;
031. 31     prev->next = new;
032. 32 }
033. 33
034. 34 // 添加new节点:将new添加到head之后,是new称为head的后继节点。
035. 35 static inline void list_add(struct list_head *new, struct list_head *head)
036. 36 {
037. 37     __list_add(new, head, head->next);
038. 38 }
039. 39
040. 40 // 添加new节点:将new添加到head之前,即将new添加到双链表的末尾。
041. 41 static inline void list_add_tail(struct list_head *new, struct list_head *head)
042. 42 {
043. 43     __list_add(new, head->prev, head);
044. 44 }
045. 45
046. 46 // 从双链表中删除entry节点。
047. 47 static inline void __list_del(struct list_head * prev, struct list_head * next)
048. 48 {
049. 49     next->prev = prev;
050. 50     prev->next = next;
051. 51 }
052. 52
053. 53 // 从双链表中删除entry节点。
054. 54 static inline void list_del(struct list_head *entry)
055. 55 {
056. 56     __list_del(entry->prev, entry->next);
057. 57 }
058. 58
059. 59 // 从双链表中删除entry节点。
060. 60 static inline void __list_del_entry(struct list_head *entry)
061. 61 {
062. 62     __list_del(entry->prev, entry->next);
063. 63 }
064. 64
065. 65 // 从双链表中删除entry节点,并将entry节点的前继节点和后继节点都指向entry本身。
066. 66 static inline void list_del_init(struct list_head *entry)
067. 67 {
068. 68     __list_del_entry(entry);
069. 69     INIT_LIST_HEAD(entry);
070. 70 }
071. 71
072. 72 // 用new节点取代old节点
073. 73 static inline void list_replace(struct list_head *old,
074. 74                 struct list_head *new)
075. 75 {
076. 76     new->next = old->next;
077. 77     new->next->prev = new;
078. 78     new->prev = old->prev;
079. 79     new->prev->next = new;
080. 80 }
081. 81
082. 82 // 双链表是否为空
083. 83 static inline int list_empty(const struct list_head *head)
084. 84 {
085. 85     return head->next == head;
086. 86 }
087. 87
088. 88 // 获取'MEMBER成员'在'结构体TYPE'中的位置偏移
089. 89 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
090. 90
091. 91 // 根据'结构体(type)变量'中的'域成员变量(member)的指针(ptr)'来获取指向整个结构体变量的指针
092. 92 #define container_of(ptr, type, member) ({         
093. 93     const typeof( ((type *)0)->member ) *__mptr = (ptr);   
094. 94     (type *)( (char *)__mptr - offsetof(type,member) );})
095. 95
096. 96 // 遍历双向链表
097. 97 #define list_for_each(pos, head)
098. 98     for (pos = (head)->next; pos != (head); pos = pos->next)
099. 99
100. 100 #define list_for_each_safe(pos, n, head)
101. 101     for (pos = (head)->next, n = pos->next; pos != (head);
102. 102         pos = n, n = pos->next)
103. 103
104. 104 #define list_entry(ptr, type, member)
105. 105     container_of(ptr, type, member)
106. 106
107. 107 #endif

双向链表测试代码(test.c)


01. 1 #include <stdio.h>
02. 2 #include <stdlib.h>
03. 3 #include <string.h>
04. 4 #include 'list.h'
05. 5
06. 6 struct person
07. 7 {
08. 8     int age;
09. 9     char name[20];
10. 10     struct list_head list;
11. 11 };
12. 12
13. 13 void main(int argc, char* argv[])
14. 14 {
15. 15     struct person *pperson;
16. 16     struct person person_head;
17. 17     struct list_head *pos, *next;
18. 18     int i;
19. 19
20. 20     // 初始化双链表的表头
21. 21     INIT_LIST_HEAD(&person_head.list);
22. 22
23. 23     // 添加节点
24. 24     for (i=0; i<5; i++)
25. 25     {
26. 26         pperson = (struct person*)malloc(sizeof(struct person));
27. 27         pperson->age = (i+1)*10;
28. 28         sprintf(pperson->name, '%d', i+1);
29. 29         // 将节点链接到链表的末尾
30. 30         // 如果想把节点链接到链表的表头后面,则使用 list_add
31. 31         list_add_tail(&(pperson->list), &(person_head.list));
32. 32     }
33. 33
34. 34     // 遍历链表
35. 35     printf('==== 1st iterator d-link ====
36. ');
37. 36     list_for_each(pos, &person_head.list)
38. 37     {
39. 38         pperson = list_entry(pos, struct person, list);
40. 39         printf('name:%-2s, age:%d
41. ', pperson->name, pperson->age);
42. 40     }
43. 41
44. 42     // 删除节点age为20的节点
45. 43     printf('==== delete node(age:20) ====
46. ');
47. 44     list_for_each_safe(pos, next, &person_head.list)
48. 45     {
49. 46         pperson = list_entry(pos, struct person, list);
50. 47         if(pperson->age == 20)
51. 48         {
52. 49             list_del_init(pos);
53. 50             free(pperson);
54. 51         }
55. 52     }
56. 53
57. 54     // 再次遍历链表
58. 55     printf('==== 2nd iterator d-link ====
59. ');
60. 56     list_for_each(pos, &person_head.list)
61. 57     {
62. 58         pperson = list_entry(pos, struct person, list);
63. 59         printf('name:%-2s, age:%d
64. ', pperson->name, pperson->age);
65. 60     }
66. 61
67. 62     // 释放资源
68. 63     list_for_each_safe(pos, next, &person_head.list)
69. 64     {
70. 65         pperson = list_entry(pos, struct person, list);
71. 66         list_del_init(pos);
72. 67         free(pperson);
73. 68     }
74. 69     
75. 70 }

运行结果

01. ==== 1st iterator d-link ====
02. name:1 , age:10
03. name:2 , age:20
04. name:3 , age:30
05. name:4 , age:40
06. name:5 , age:50
07. ==== delete node(age:20) ====
08. ==== 2nd iterator d-link ====
09. name:1 , age:10
10. name:3 , age:30
11. name:4 , age:40
12. name:5 , age:50


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值