总体结构

内核链表的优点在于:链表节点可以是不同类型的结构体
内核链表是双向链表:
内核链表-链表的总体结构.png

注:

  1. 链表节点结构体的类型可以不同,但必须要有 struct list_head 类型的成员
  2. 需要由单独的头节点,头节点并不存储内容

太长不看

涉及到的所有操作有:

  1. // 重要的宏定义
  2. #define offsetof(type, member) ...
  3. #define container_of(ptr, type, member) ...
  4. // 链表头的定义
  5. struct list_head { struct list_head *next, *prev; };
  6. // 链表头初始化的三种方式
  7. #define LIST_HEAD_INIT(name) ...
  8. #define LIST_HEAD(name) ...
  9. static inline void INIT_LIST_HEAD(struct list_head *list);
  10. // 访问某个节点
  11. #define list_entry(ptr, type, member) ...
  12. #define list_first_entry(ptr, type, member) ...
  13. // 判断链表为空
  14. static inline int list_empty(const struct list_head *head);
  15. // 遍历的三种方法
  16. #define list_for_each(pos, head) ...
  17. #define list_for_each_prev(pos, head) ...
  18. #define list_for_each_entry(pos, head, member) ...
  19. #define list_for_each_entry_safe(pos, n, head, member) ...
  20. // 添加节点
  21. static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next);
  22. static inline void list_add(strcut list_head *new, struct list_head *head);
  23. static inline void list_add_tail(struct list *new, struct list_head *head);
  24. // 删除节点
  25. static inline void __list_del(struct list_head *prev, struct list_head *next);
  26. static inline void list_del(struct list_head *entry);
  27. static inline void list_del_init(struct list_head *entry);

基本操作

内核链表的宏定义

内核链表能够连接不同类型节点的特性,是由这两个宏来实现的:

  1. // 取得结构体的成员偏移
  2. #define offsetof(type, member) ((size_t)&((type *)0)->member)
  3. // 已知成员地址,取得结构体地址
  4. #define container_of(ptr, type, member) ({\
  5. const typeof(((type *)0)->member) *__mptr = (ptr); \
  6. (type *)((char *)__mptr - offsetof(type, member));})
  7. // 注:type是指结构体的类型;member是指结构体成员的名称

链节的定义

  1. // 链表头的定义
  2. struct list_head {
  3. struct list_head *next, *prev;
  4. };

链表节点需要包含 struct list_head 类型的成员

初始化链表头

相关定义:(位于 linux 源码的 include/linux/list.h

  1. // 链表头的初始化
  2. #define LIST_HEAD_INIT(name) {&(name), &(name)}
  3. // 链表头的定义和初始化一起进行
  4. #define LIST_HEAD(name) \
  5. struct list_head name = LIST_HEAD_INIT(name)
  6. // 初始化链表头节点
  7. // 注意和宏定义 LIST_HEAD_INIT 的写法区分开
  8. static inline void INIT_LIST_HEAD(struct list_head *list)
  9. {
  10. list->next = list;
  11. list->prev = list;
  12. }

初始化链表头节点的三种方法:

  1. // 方法1
  2. struct list_head head1 = LIST_HEAD_INIT(head1);
  3. // 方法2
  4. struct list_head head2;
  5. INIT_LIST_HEAD(head2);
  6. // 方法3
  7. LIST_HEAD(head3);

访问某个节点

已知节点的 struct list_head 成员的地址,访问该节点
相关定义:

  1. #define list_entry(ptr, type, member) \
  2. container_of(ptr, type, member)
  3. // 已知链表头,访问第一个节点
  4. #define list_first_entry(ptr, type, member) \
  5. list_entry((ptr)->next, type, member)

判断节点是否为空

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

遍历

不同类型节点组成的链表也可以遍历,只要节点中的 struct list_head 成员的名称相同即可。

1. 遍历的第一种方法
  1. // 向后遍历
  2. #define list_for_each(pos, head) \
  3. for (pos = (head)->next; pos != (head); pos = pos->next)
  4. // 向前遍历
  5. #define list_for_each_prev(pos, head) \
  6. for (pos = (head)->prev; post != (head); post = pos->prev)

注: pos 是一个 struct list_head * 类型的循环变量,需要额外提供

使用方法:

  1. // 链表节点的定义
  2. struct Node {
  3. struct list_head link;
  4. char name[256];
  5. };
  6. list_head *pos = NULL; // 循环变量
  7. LIST_HEAD(head); // 链表头
  8. // 遍历链表
  9. list_for_each(pos, head) {
  10. printf("name is %s\n", list_entry(pos, struct Node, link));
  11. }

2. 遍历的第二种方法
  1. #define list_for_each_entry(pos, head, member) \
  2. for (pos = list_entry((head)->next, typeof(*pos), member); \
  3. &pos->member != (head); \
  4. pos = list_entry(pos->member.next, typeof(*pos), member))

注:和第一种的区别:第一种的循环变量 posstruct list_head * 类型的;而第二种的循环变量 pos 是节点类型的。

使用方法:

  1. // 链表节点的定义
  2. struct Node {
  3. struct list_head link;
  4. char name[256];
  5. };
  6. struct Node *pos = NULL; // 循环变量
  7. LIST_HEAD(head); // 链表头
  8. // 遍历链表
  9. list_for_each_entry(pos, head, link) {
  10. printf("name is %s\n", pos->name);
  11. }

3. 第三种遍历方法

支持在遍历的过程中删除节点
定义:

  1. #define list_for_each_entry_safe(pos, n, head, member) \
  2. for (pos = list_entry((head)->next, typeof(*pos), member), \
  3. n = list_entry(pos->member.next, typeof(*pos), member); \
  4. &pos->member != (head); \
  5. pos = n, n = list_entry(n->member.next, typepof(*n), member))

使用:

  1. // 链表节点的定义
  2. struct Node {
  3. struct list_head link;
  4. char name[256];
  5. };
  6. struct Node *pos = NULL; // 循环变量
  7. struct Node *n = NULL; // 循环变量
  8. LIST_HEAD(head); // 链表头
  9. // 遍历链表
  10. list_for_each_entry_safe(pos, n, head, link) {
  11. printf("name is %s\n", pos->name);
  12. list_del(pos->link);
  13. free(pos);
  14. }

添加节点

定义:

  1. // 在两个节点之间添加一个节点
  2. static inline void __list_add(struct list_head *new,
  3. struct list_head *prev,
  4. struct list_head *next)
  5. {
  6. next->prev = new;
  7. new->next = next;
  8. new->prev = prev;
  9. prev->next = new;
  10. }
  11. // 在链表开头添加一个节点
  12. static inline void list_add(strcut list_head *new, struct list_head *head)
  13. {
  14. __list_add(new, head, head->next);
  15. }
  16. // 在链表末尾添加一个节点
  17. static inline void list_add_tail(struct list *new, struct list_head *head)
  18. {
  19. __list_add(new, head->prev, head);
  20. }

关于 __list_add 函数的示意图:
内核链表-__lits_add.png

删除节点

注:删除节点仅仅是从链表上摘除,并不涉及内存管理。该释放的内存还是要手动手动释放

定义:

  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. // 节点的 prev 和 next 都是 NULL
  8. static inline void list_del(struct list_head *entry)
  9. {
  10. __list_del(entry->prev, entry->entry);
  11. entry->next = entry->pre = NULL;
  12. }
  13. // 从链表中摘下一个节点
  14. // 节点的 prev 和 next 指向自身
  15. static inline void list_del_init(struct list_head *entry)
  16. {
  17. __list_del(entry->prev, entry->next);
  18. INIT_LIST_HEAD(entry);
  19. }

函数 __list_del 的示意图:
内核链表-__list_del.png

资源文件

内核链表.drawio