1.内核链表介绍
在Linux
内核中提供了一种通用的双向循环链表来组织数据,这种链表方便使用,且只有一个头文件,方便移植。文件路径:include\linux\list.h
。
头结点表示:
/**
* The linkage struct for list nodes. This struct must be part of your
* to-be-linked struct. struct list_head is required for both the head of the
* list and for each list node.
*
* Position and name of the struct list_head field is irrelevant.
* There are no requirements that elements of a list are of the same type.
* There are no requirements for a list head, any struct list_head can be a list
* head.
*/
/* 链表结点元素 */
struct list_head {
struct list_head *next, *prev;
};
头结点一般是不使用来存储数据的,只是用来维护接结点之间的关系。一般放在存储信息结点的第一个元素。
/* 数据节点 */
struct lst_node
{
struct list_head list;
int num;
/* other elements*/
};
2.内核链表操作API接口
2.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)
{
WRITE_ONCE(list->next, list);
list->prev = list;
}
Eg:
static struct list_head g_topo_header;
LIST_HEAD_INIT(g_topo_header);
INIT_LIST_HEAD(&g_topo_header);
/* 初始化一个变量名为 g_topo_hdr的链表结点*/
LIST_HEAD(g_topo_hdr);
在实际使用中我们一般使用INIT_LIST_HEAD
就可以了。
2.2 添加数据节点
static inline void
__list_add(struct list_head *entry,
struct list_head *prev, struct list_head *next)
{
next->prev = entry;
entry->next = next;
entry->prev = prev;
prev->next = entry;
}
/**
* Insert a new element after the given list head. The new element does not
* need to be initialised as empty list.
* The list changes from:
* head → some element → ...
* to
* head → new element → older element → ...
*
* Example:
* struct foo *newfoo = malloc(...);
* list_add(&newfoo->entry, &bar->list_of_foos);
*
* @param entry The new element to prepend to the list.
* @param head The existing list.
*/
static inline void
list_add(struct list_head *entry, struct list_head *head)
{
__list_add(entry, head, head->next);
}
这种添加的方式其实就是头插法。
尾插法:
/**
* Append a new element to the end of the list given with this list head.
*
* The list changes from:
* head → some element → ... → lastelement
* to
* head → some element → ... → lastelement → new element
*
* Example:
* struct foo *newfoo = malloc(...);
* list_add_tail(&newfoo->entry, &bar->list_of_foos);
*
* @param entry The new element to prepend to the list.
* @param head The existing list.
*/
static inline void
list_add_tail(struct list_head *entry, struct list_head *head)
{
__list_add(entry, head->prev, head);
}
2.3 删除节点
static inline void
__list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
/* 删除节点 entry */
static inline void
list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
/* 删除节点 entry 并对其初始化 */
static inline void
list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
注意:这里删除节点的意思是把节点从链表上移除,并不是释放节点的内存,节点的内存需要手动去维护。
2.4 移动节点到尾部
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}
2.5 链表判空
/**
* Check if the list is empty.
*
* Example:
* list_empty(&bar->list_of_foos);
*
* @return True if the list contains one or more elements or False otherwise.
*/
static inline bool
list_empty(struct list_head *head)
{
return head->next == head;
}
2.6 获取首尾数据节点
前面说到的接口都是对链表结点struct list
的操作的,那么我们怎么去获取数据节点呢?
/**
* ptr: 链表结点的地址
* type: 数据节点的类型
* member: 链表结点的名字
*/
#ifndef container_of
#define container_of(ptr, type, member) \
(type *)((char *)(ptr) - (char *) &((type *)0)->member)
#endif
/*
获取包含链表结点指针ptr的数据节点的地址
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
通过上面的宏定义我们就可以获取包含链表结点的数据节点的地址,进而可以访问数据节点的其他数据域。
我们知道,定义一个结构体的时候,各个域 的地址是向上增长的。
利用0地址的特殊性,通过节点的一个域的地址与结构体节点的偏移量。
然后用节点域的地址减去偏移量就能够得到结构体的地址。得到结构体的地址,就可以访问结构体的其他成员。
其中在标准库里面已经定义了这个求结构体域偏移量的宏。
#include <stddef.h>
#define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)
获取首尾数据节点
/* ptr 链表头结点地址 */
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
#define list_last_entry(ptr, type, member) \
list_entry((ptr)->prev, type, member)
2.7 变量链表
#define __container_of(ptr, sample, member) \
(void *)container_of((ptr), typeof(*(sample)), member)
/**
* 删除不安全的,只用于遍历修改数据域
* This macro is not safe for node deletion. Use list_for_each_entry_safe
* instead.
*
* @param pos Iterator variable of the type of the list elements.
* @param head List head
* @param member Member name of the struct list_head in the list elements.
*
*/
#define list_for_each_entry(pos, head, member) \
for (pos = __container_of((head)->next, pos, member); \
&pos->member != (head); \
pos = __container_of(pos->member.next, pos, member))
/**
* Loop through the list, keeping a backup pointer to the element. This
* macro allows for the deletion of a list element while looping through the
* list.
* 删除节点安全
* See list_for_each_entry for more details.
*/
#define list_for_each_entry_safe(pos, tmp, head, member) \
for (pos = __container_of((head)->next, pos, member), \
tmp = __container_of(pos->member.next, pos, member); \
&pos->member != (head); \
pos = tmp, tmp = __container_of(pos->member.next, tmp, member))
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = __container_of((head)->prev, pos, member); \
&pos->member != (head); \
pos = __container_of(pos->member.prev, pos, member))
/* 从指定节点的下一个节点开始遍历到头部 */
#define list_for_each_entry_continue(pos, head, member) \
for (pos = __container_of(pos->member.next, pos, member); \
&pos->member != (head); \
pos = __container_of(pos->member.next, pos, member))
#define list_for_each_entry_continue_reverse(pos, head, member) \
for (pos = __container_of(pos->member.prev, pos, member); \
&pos->member != (head); \
pos = __container_of(pos->member.prev, pos, member))
/* 从指定节点开始遍历到头部 */
#define list_for_each_entry_from(pos, head, member) \
for (; \
&pos->member != (head); \
pos = __container_of(pos->member.next, pos, member))
3. 在开发中使用内核链表
#include <stdio.h>
#include <stdlib.h>
#include "./list.h"
struct list_head g_lst_hdr;
struct lst_node
{
struct list_head list;
int num;
};
int main()
{
int i = 0;
INIT_LIST_HEAD(&g_lst_hdr);
for (i = 0; i < 10; ++i) {
struct lst_node *node = (struct lst_node *)malloc(sizeof(struct lst_node));
if (node == NULL) {
continue;
}
INIT_LIST_HEAD(&node->list);
//node->list = {&(node->list), &(node->list)};
node->num = i;
list_add(&node->list, &g_lst_hdr);
}
struct lst_node *pos = NULL;
list_for_each_entry(pos, &g_lst_hdr, list) {
printf(" == %d \n", pos->num);
}
struct lst_node *p = NULL;
struct lst_node *n = NULL;
list_for_each_entry_safe(p, n, &g_lst_hdr, list) {
if (p->num == 5) {
list_del(&p->list);
free(p);
}
}
printf("\n========================\n");
pos = NULL;
list_for_each_entry_reverse(pos, &g_lst_hdr, list) {
printf(" == %d \n", pos->num);
}
p = n = NULL;
list_for_each_entry_safe(p, n, &g_lst_hdr, list) {
list_del(&p->list);
free(p);
}
return 0;
}
总结:
- 链表初始化的时候要注意是传递地址还是变量名。
- 遍历链表的时候需要注意传递的是链表结点的地址还是数据节点的地址。
- 遍历链表的时候需要特别注意是否是安全的,特别是在遍历的同时需要删除节点的时候。
- 链表的删除并不是释放节点的内存,节点的内存需要手动维护。