内核双向循环链表
与普通双向循环链表之所以不同,是因为内核双向循环链表没有数据部分,所以要使用就需要创建一个结构体,里面的成员为你的数据部分,以及双向循环链表,即将双向循环链表作为一个结构体成员进行封装,如:
struct stu{
char name[128];
int math;
int id;
struct list_head list;
};
在Linux源代码树的include/linux/list.h
文件中,采用了一种类型无关的双循环链表实现方式。其思想是将指针prev
和next
从具体的数据结构中提取出来构成一种通用的"双链表"数据结构list_head
。如果需要构造某类对象的特定链表,则在其结构(被称为宿主数据结构)中定义一个类型为list_head
类型的成员,通过这个成员将这类对象连接起来,形成所需链表,并通过通用链表函数对其进行操作。其优点是只需编写通用链表函数,即可构造和操作不同对象的链表,而无需为每类对象的每种列表编写专用函数,实现了代码的重用。
struct list_head
数据结构如下:
struct list_head {
struct list_head *next, *prev;
};
定义和初始化
Linux 的每个双循环链表都有一个链表头,链表头也是一个节点,只不过它不嵌入到宿主数据结构中,即不能利用链表头定位到对应的宿主结构,但可以由之获得虚拟的宿主结构指针。
LIST_HEAD()
宏可以同时完成定义链表头,并初始化这个双循环链表为空。例如:LIST_HEAD(head)
添加节点
头插
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
list_add()
调用底层__list_add()
static inline void __list_add(struct list *new,
struct list *prev,
struct list *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
普通的在两个非空结点中插入一个结点,注意new
、prev
、next
都不能是空值。prev
可以等于next
,此时在只含头节点的链表中插入新节点。
尾插
static inline void list_add_tail(struct list *new, struct list *head)
{
__list_add(new, head->prev, head);
}
list_add和list_add_tail虽然原型一样,但调用底层函数__list_add时传递了不同的参数,从而实现了在head指向节点之前或之后添加新的对象。
删除节点
底层实现:
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
调用:
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)
相当于NULL,使其节点不可被访问
获取宿主对象指针
container_of(pos, type ,member)
#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型。
offsetof(type,member)
就是取结构体中的成员相对于地址0的偏移地址,也就是成员变量相对于结构体变量首地址的偏移。
-
((TYPE *)0) 将零转型为TYPE类型指针;
-
((TYPE *)0)->MEMBER 访问结构中的数据成员;
-
&(TYPE *)0 )->MEMBER )取出数据成员的地址;
-
(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型,结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;
用结构体成员变量的地址减去偏移量则是结构体的首地址。
遍历
#define list_for_each_safe(pos, n, head) /
for (pos = (head)->next, n = pos->next; pos != (head); /
pos = n, n = pos->next)
在for循环中n暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。
#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)
list_for_each_entry_safe()
函数可以不用先使用container_of
获得宿主结构体指针,也将pos->next
保存了起来,所以比较安全。