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

被折叠的 条评论
为什么被折叠?



