双向链表是Linux中非常重要和基础的一个数据结构,它在Linux内核中是一个基本类型
Linux内核中的链表
一个常见的双向链表可以被定义为
struct my_list{
void *mydata;
struct my_list *next;
struct my_list *prev;
};
不同的使用方法会构造出不同的数据结构
- 先进先出是队列
- 只对后继操作是栈
- 两个节点指向子树就是二叉树
- …
链表基本功能的实现
定义
Linux中的链表定义为:
struct list_head{
struct list_head *next, *prev;
};
在linux/list.h中有链表的声明和初始化的宏
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
判空
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
非常简单清晰,判断head的next是否指向head本身即可。
插入
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
#ifndef CONFIG_DEBUG_LIST
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;
prev->next = new;
}
#else
extern void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next);
#endif
这里主要是提供了一个默认的实现方式,或者通过extern实现也行。
默认的方式很简单清晰,就是分别将prev的后继指向new,next的前驱指向new,new的前驱和后继分别指向prev和next。
遍历
Linux中通过定义一个宏来实现遍历
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
定位
找到节点的位置如何定位到该节点呢?
linux提供了一个定位的宏
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
这里用container_of对宏进行了封装, 本体应该是这样的:
#define list_entry(ptr, tpye, member) \
((type*)((char*)(ptr) - (unsigned long)(&((type*)0)->member)))
这个实现的功能就是,获得ptr的位置,然后减去member的偏移量,就可以得到该节点的起始地址了,就可以对节点进行操作,而不只单纯的对节点内部的某一成员进行操作。
删除
双向节点的删除操作
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_del_entry(struct list_head *entry);
extern void list_del(struct list_head *entry);
#endif
外部调用list_del函数,传入一个entry的指针,让该entry的前驱和后继相互指向,本身置空。
大道至简,linux中的双链表定义非常简单,无需畏难。之后的很多数据结构都需要基于双链表进行构造。
参考资料
- 学堂在线-Linux内核分析与应用:https://next.xuetangx.com/course/XIYOU08091001441/14767915