【Linux内核】数据结构之环形双链表

本文深入解析Linux内核中的链表数据结构,包括单向、双向、环形单向和环形双向链表,重点介绍了环形双向链表的实现原理及操作函数如初始化、添加、删除、移动节点等,同时探讨了链表遍历方法。

链表是Linux内核中最简单、最普通的数据结构。链表是一种存放和操作可变数据元素(常称为节点)的数据结构。链表可以动态创建并插入,编译时无须知道具体需要创建多少个元素,每一个元素的创建时间可以不同,也无须占用连续内存区。

链表包含:单向链表、双向双链表、环形单向链表、环形双向双链表

因为环形双向链表提供了最大的灵活性,所以Linux内核的标准链表就是采用环形双向链表形式实现的。

链表数据结构定义在<linux/types.h>,链表初始化以及具体操作在<linux/list.h>

链表数据结构——list_head

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

定义了两个可以指向list_head结构的指针,其中next指针用来指向下一个链表节点,prev指针用来指向前一个链表节点。

 链表初始化——LIST_HEAD、INIT_LIST_HEAD

#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;
}

链表初始化之后list->prev指向list,list->next也指向list,list也是环形双向链表的头

添加链表节点——list_add、list_add_tail

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

static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

插入节点分为从链表头部插入list_add和链表尾部插入list_add_tail,通过调用__list_add函数进行实现

删除链表节点——list_del、__list_del_entry、list_del_init

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	WRITE_ONCE(prev->next, next);
}

static inline void __list_del_entry(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}

static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}

static inline void list_del_init(struct list_head *entry)
{
	__list_del_entry(entry);
	INIT_LIST_HEAD(entry);
}

从链表中删除一个节点,其中__list_del最后会设置被删除节点的前驱节点和后继结点指向LIST_POSITION1和LIST_POSITION2两个特殊值,这样设置是为了保证不在链表中的节点项不可访问,对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障。list_del_init删除指定链表并初始化它

移动链表节点——list_move、list_move_tail

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_move实现将链表节点移动到头部,list_move_tail实现将链表节点移动到尾部

检查链表是否为空——list_empty

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

检查节点是否为末节点——list_is_last

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

两个链表合并——list_replace、list_replace_init

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

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

list_replace合并两个链表,将old链表插入到new链表的后面;list_replace_init合并两个链表,并重新初始化原来(old)的链表

遍历链表——list_for_each、list_for_each_entry等

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

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

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

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

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

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

list_for_each就是单纯遍历环形双向链表,从前往后;list_for_each_prev,从后往前;

如果在链表中添加相关数据,改如何操作呢?Linux内核将链表节点直接放在数据结构中,然后把这些数据结构添加到以head为头的环形双向链表中,如下图所示:

list_for_each_entry其实就是一个for循环语句,拆分成如下三部分:

container_of细节查看offsetof与container_of宏

  • for循环初始化,pos指向链表第一个节点
pos = list_first_entry(head, typeof(*pos), member);

 

  • for执行条件,如果相等表示已经遍历到链表头了,无须再遍历,退出for循环
&pos->member != (head);
  • 每循环一次执行语句,pos指向下一个节点
pos = list_next_entry(pos, member)

list_for_each_entry是从链表头往后遍历,通过pos指针来获取保存在链表节点中的数据。

同理list_for_each_entry_reverse是从链表尾部往前遍历

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值