Linux内核中的双向链表list_head

双向链表在 Linux 内核中使用非常多,它是内核各种队列、栈的基础,相关的结构定义和函数均在include/linux/list.h中定义,下面介绍下其原理及使用方法。

1、结构体定义

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

双向链表结构体很简单,有 prev 和 next 两个指针,分别指向链表的前一节点和后一节点。这里专门讲下空链表,空链表指链表的 prev 和 next 均指向自身,非空链表指除表头外,至少还有一个节点。

函数 list_empty 用来判断是否为空链表,函数 list_is_singular 用来判断链表是否只有一个 entry 节点(除表头外)。

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

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

2、创建和初始化链表

1)使用 LIST_HEAD 定义并初始化链表
#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

如:LIST_HEAD(mylist);

把宏展开即为:

struct list_head mylist = { &(mylist), &(mylist) };

定义的链表 mylist 的 prev 和 next 均指向自身,表示初始化为空链表(注意:链表初始化非常重要,否则使用过程容易产生内核crash)。

2)先定义链表,再使用 INIT_LIST_HEAD 进行初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
	WRITE_ONCE(list->next, list);
	list->prev = list;
}

如:struct list_head mylist;INIT_LIST_HEAD(&mylist);

与第一种方式类似,宏 INIT_LIST_HEAD 将链表头的 prev 和 next 都是指向自身,表示空链表。

3、增/删/改/分/合/遍历

1)增加(插入)

先看看内部函数 __list_add,实现了将 new 节点插入到 prev 和 next 之间(prev和next必须是连续的节点)。

static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	if (!__list_add_valid(new, prev, next))
		return;

	next->prev = new;
	new->next = next;
	new->prev = prev;
	WRITE_ONCE(prev->next, new);
}

外部函数有两个: list_add 和 list_add_tail ,分别实现了将新节点 new 插入到现有节点 head 之后或之前。如果现有节点 head 是链表头,则意味着将新节点插入到链表头和链表尾。可以根据实际需要分别进行调用。

//将 new 节点插入到 head 节点之后,一般用于栈操作
static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

//将 new 节点插入到 head 节点之前,一般用于队列操作
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}
2)删除

同样,先看看内部函数 __list_add,实现了删除  prev 和 next 之间的节点。另外一个内部函数__list_del_entry在删除之前,先校验 entry 是否有效。

//删除 pre 和 next 之间的节点
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	WRITE_ONCE(prev->next, next);
}

//删除之前先校验 entry 是否是有效的链表节点,有效才进行删除
static inline void __list_del_entry(struct list_head *entry)
{
	if (!__list_del_entry_valid(entry))
		return;

	__list_del(entry->prev, entry->next);
}

外部函数也有两个:list_del 和 list_del_init。前者删除 entry 节点,并将 entry 节点的 pre 和 next置为非法的指针地址(即无效节点,如使用会报页错误),这样 list_empty(entry) 不会返回 true。 后者删除 entry 节点之后,将 entry 初始化为空链表(使 prev 和 next 都指向自己)。

//删除 entry 节点,并将 entry 置为无效,防止误用
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}

//删除 entry 节点,并将 entry 初始化为空链表
static inline void list_del_init(struct list_head *entry)
{
	__list_del_entry(entry);
	INIT_LIST_HEAD(entry);
}
3)修改

list_replace 实现 new 节点替换 old 节点。

static inline void list_replace(struct list_head *old,
				struct list_head *new)
{
	new->next = old->next;
	new->next->prev = new;
	new->prev = old->prev;
	new->prev->next = new;
}

list_replace_init 实现 new 节点替换 old 节点之后,还将old初始化为空链表。

static inline void list_replace_init(struct list_head *old,
				     struct list_head *new)
{
	list_replace(old, new);
	INIT_LIST_HEAD(old);
}

list_swap 实现两个节点的交换。

static inline void list_swap(struct list_head *entry1,
			     struct list_head *entry2)
{
	struct list_head *pos = entry2->prev;

	list_del(entry2);
	list_replace(entry1, entry2);
	if (pos == entry1)
		pos = entry2;
	list_add(entry1, pos);
}

list_move 和 void list_move_tail 实现将 list 从原链表中删除,并添加到新链表的表头或表尾。

static inline void list_move(struct list_head *list, struct list_head *head)
{
	__list_del_entry(list);
	list_add(list, head);
}

static inline void list_move_tail(struct list_head *list,
				  struct list_head *head)
{
	__list_del_entry(list);
	list_add_tail(list, head);
}

list_bulk_move_tail 实现将从 first 到 last 之间的链表段移动到链表的尾部。

static inline void list_bulk_move_tail(struct list_head *head,
				       struct list_head *first,
				       struct list_head *last)
{
	first->prev->next = last->next;
	last->next->prev = first->prev;

	head->prev->next = first;
	first->prev = head->prev;

	last->next = head;
	head->prev = last;
}
4)分割

内部函数 __list_cut_position 实现以 entry 节点为分割点,将链表 head 分成2个链表,新的链表表头为 list。

static inline void __list_cut_position(struct list_head *list,
		struct list_head *head, struct list_head *entry)
{
	struct list_head *new_first = entry->next;
	list->next = head->next;
	list->next->prev = list;
	list->prev = entry;
	entry->next = list;
	head->next = new_first;
	new_first->prev = head;
}

函数 list_cut_position 主要对链表 head 进行了空链表以及单entry链表等情况判断,防止异常的调用。另外,如果 entry 就是 head,将不进行分割。如果不是以上情况,再调用内部函数进行分割。

static inline void list_cut_position(struct list_head *list,
		struct list_head *head, struct list_head *entry)
{
	if (list_empty(head))
		return;
	if (list_is_singular(head) &&
		(head->next != entry && head != entry))
		return;
	if (entry == head)
		INIT_LIST_HEAD(list);
	else
		__list_cut_position(list, head, entry);
}
5)合并

内部函数 __list_splice 实现将链表 list 插入到另一个链表的 pre 和 next 之间。

static inline void __list_splice(const struct list_head *list,
				 struct list_head *prev,
				 struct list_head *next)
{
	struct list_head *first = list->next;
	struct list_head *last = list->prev;

	first->prev = prev;
	prev->next = first;

	last->next = next;
	next->prev = last;
}

 函数 list_splice 和 list_splice_tail 分别实现将链表 list 合并到另一个链表 head 的头部和尾部。

static inline void list_splice(const struct list_head *list,
				struct list_head *head)
{
	if (!list_empty(list))
		__list_splice(list, head, head->next);
}

static inline void list_splice_tail(struct list_head *list,
				struct list_head *head)
{
	if (!list_empty(list))
		__list_splice(list, head->prev, head);
}
6)链表的遍历

主要通过 list_for_each 和 list_for_each_prev 两个宏对链表 head 进行正向或反向遍历。

#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_prev(pos, head) \
	for (pos = (head)->prev; pos != (head); pos = pos->prev)

5、内核中的使用

list_head 在内核中使用非常广泛,一般作为其他结构体的成员,并通过list_for_each_entry、list_entry 等宏对链表所在结构体进行遍历,从而使得所在的结构体作为链表的实际数据,下面通过实例进行分析。

1)list_entry

list_entry 内部又调用了 container_of,其作用是根据结构体(类型为type)中的 list_head* 成员变量 member 和此变量的指针 ptr,返回指向结构体(类型为type)的指针。实现原理很简单,参见下面的代码。

// 返回结构体类型 type 的成员 member,在结构体中的偏移量
#define offsetof(type, member) ((size_t)&((t *)0)->m)  

// 根据结构体类型 type 的成员 member 的指针 ptr,获取指向整个结构体变量的指针
#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)
2)list_for_each_entry

list_for_each_entry 是一个 for 循环,用传入的 pos (类型为 list_head 所在的结构体)作为循环变量,从链表表头 head 开始,逐项向后(next 方向)移动 pos,一直回到head,member 为结构体中 list_head* 类型的成员。

#define list_first_entry(ptr, type, member) \
	list_entry((ptr)->next, type, member)

#define list_next_entry(pos, member) \
	list_entry((pos)->member.next, typeof(*(pos)), member)

#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     !list_entry_is_head(pos, head, member);			\
	     pos = list_next_entry(pos, member))

变量的初始化:pos = list_entry((head)->next, typeof(*pos), member),每次pos拿到的都是链表所在的结构体。

执行条件:  &pos->member != (head),确定拿到的成员不是head。是head的话,表示list已遍历完。

每循环一次执行 pos = list_entry(pos->member.next, typeof(*pos), member)),是pos指定链表中下一个成员,其实际还是链表所在的结构体

以上中用到typeof(),它是取变量的类型,这里是取指针pos所指向数据的类型。

6、代码实例

下面代码实例,展示了 list_head 的创建和初始化、添加、删除、遍历等操作。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/list.h>
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ioriwc");

//定义结构体
typdef struct _Person {
    char name[50];
    int  id;
    struct list_head list;
}Person;

Person *g_person = NULL;

int mylist_init() {
    int i=0;
    struct list_head *lh_pos;
    Person* p_pos;
    
    //创建并初始化链表
    LIST_HEAD(person_list);
 
    g_person = kmalloc(sizeof(Person)*5, GFP_KERNEL);
    
    for (i=0; i<5; i++) {
        sprintf(g_person[i].name, "Person-%d", i+1);
        g_person[i].id = i+1;

        // 添加到链表中
        list_add(&(g_person[i].list), &person_list);
    }
 
    //遍历打印-方法1
    list_for_each(lh_pos, &person_list) {
        Person * tmp = list_entry(lh_pos, Person, list);
        printk("person %d name: %s\n", tmp->id, tmp->name);
    }

    //遍历打印-方法2
    list_for_each_entry(p_pos, &person_list, list) {
        printk("person %d name: %s\n", p_pos->num, p_pos->name);
    }
    
    return 0;
}
 
void mylist_exit() {
    int i;

    for (int i=0; i<5; i++) {
        // 从链表中删除节点
        list_del(&(g_person[i].list));
    }
    
    kfree(g_person);
}
 
module_init(mylist_init);
module_exit(mylist_exit);

  • 45
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值