概述
//Linux内核链表的精彩实现
①
//链表数据结构的定义很简单([include/linux/list.h],以下所有代码
struct list_head{
struct list_head *next,*prev;
};
//一般此结构可以实现双循环链表的功能
//②.链表接口操作
//1. 声明和初始化
/*实际上Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来
的呢 ?让我们来看看LIST_HEAD()这个宏:*/
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
/*当我们用LIST_HEAD(nf_sockopts)声明一个名为nf_sockopts的链表头时,它的next、prev指针都
初始化为指向自己,这样,我们就有了一个空链表,因为Linux用头指针的next是否指向自己来
判断链表是否为空:*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
/*除了用LIST_HEAD()宏在声明的时候初始化一个链表以外,Linux还提供了一个
INIT_LIST_HEAD宏用于运行时初始化链表:*/
#define INIT_LIST_HEAD(ptr) do {
(ptr)->next = (ptr); (ptr)->prev = (ptr);
} while (0)
/*③插入/删除/合并
a 插入
对链表的插入操作有两种:在表头插入和在表尾插入。Linux为此提供了两个接口:
*/
static inline void list_add(struct list_head *new, struct list_head *head);
static inline void list_add_tail(struct list_head *new, struct list_head *head);
/*表头插入函数list_add的实现*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/*__list_add的实现*/
void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
WARN(next->prev != prev,
"list_add corruption. next->prev should be "
"prev (%p), but was %p. (next=%p).n",
prev, next->prev, next);
WARN(prev->next != next,
"list_add corruption. prev->next should be "
"next (%p), but was %p. (prev=%p).n",
next, prev->next, prev);
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
/*表头插入函数list_add_tail的实现*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
/*list_add_tail的实现*/
void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
WARN(next->prev != prev,
"list_add corruption. next->prev should be "
"prev (%p), but was %p. (next=%p).n",
prev, next->prev, next);
WARN(prev->next != next,
"list_add corruption. prev->next should be "
"next (%p), but was %p. (prev=%p).n",
next, prev->next, prev);
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
/*b 删除*/
static inline void list_del(struct list_head *entry);
void list_del(struct list_head *entry)
{
WARN(entry->prev->next != entry,
"list_del corruption. prev->next should be %p, "
"but was %pn", entry, entry->prev->next);
WARN(entry->next->prev != entry,
"list_del corruption. next->prev should be %p, "
"but was %pn", entry, entry->next->prev);
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
/*d 合并
除了针对节点的插入、删除操作,Linux链表还提供了整个链表的插入功能:
*/
static inline void list_splice(struct list_head *list, struct list_head *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(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;
}
/*假设当前有两个链表,表头分别是list1和list2(都是struct list_head变量),当调用
list_splice(&list1,&list2)时,只要list1非空,list1链表的内容将被挂接在list2链表上
位于list2和list2.next之间。新list2链表将以原list1表的第一个节点为首节点,
而尾节点不变。
当list1被挂接到list2之后,作为原表头指针的list1的next、prev仍然指向原来的节点,
为了避免引起混乱,Linux提供了一个list_splice_init()函数:
*/
static inline void list_splice_init(struct list_head *list, struct list_head *head);
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
/*④.遍历
遍历是链表最经常的操作之一,为了方便核心应用遍历链表,Linux链表将遍历操作抽象成几
个宏。在介绍遍历宏之前,我们先看看如何从链表中访问到我们真正需要的数据项。
a) 由链表节点到数据项变量
我们知道,Linux链表中仅保存了数据项结构中list_head成员变量的地址,那么我们如何通过这
个list_head成员访问到作为它的所有者的节点数据呢?Linux为此提供了一个
list_entry(ptr,type,member)宏,其中ptr是指向该数据中list_head成员的指针,也就是存储在链表
中的地址值,type是数据项的类型,member则是数据项类型定义中list_head成员的变量名
*/
#define list_entry(ptr, type, member)
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({
const typeof(((type *)0)->member) * __mptr = (ptr);
(type *)((char *)__mptr - offsetof(type, member)); })
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/*这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在与结构中的偏移量,然后根
据成员变量的地址反过来得出属主结构变量的地址。
container_of()和offsetof()并不仅用于链表操作,这里最有趣的地方是((type *)0)->member,它将
0地址强制"转换"为type结构的指针,再访问到type结构中的member成员。在container_of宏中,
它用来给typeof()提供参数(typeof()是gcc的扩展,和sizeof()类似),以获得member成员的数
据类型;在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对
于结构变量的偏移量。*/
/*遍历宏
大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说list_for_each()和
list_entry()总是同时使用。对此Linux给出了一个list_for_each_entry()宏:
*/
//#define list_for_each_entry(pos, head, member)
#define list_for_each_entry(pos, head, member)
for (pos = list_entry((head)->next, typeof(*pos), member);
&pos->member != (head);
pos = list_entry(pos->member.next, typeof(*pos), member))
最后
以上就是无限未来为你收集整理的Linux内核链表的精彩实现的全部内容,希望文章能够帮你解决Linux内核链表的精彩实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复