Linux 内核中的双向链表分析
Linux 中链表的设计思想
Linux中的双向链表将数据域和指针域分离,链表中的每个节点只含有指域。不同的对象节点的数据域不同,但是链表的操作方法是一样的,所以内核中提炼出了一种纯粹的链表操作方法,这种方法只操作链表相关的所有工作,数据域中那些和链表操作无关的东西操作方法各异,由具体的实现者去实现。相反,如果每个结构体对象内部都定义了自己的指针,将来要实现链表的操作,需要自己实现,每定义一个结构体对象,就需要自己实现一套链表的操作方法。
例如:
struct driver
{
char name[30];
int id;
struct driver_info info;
......
struct list_head head; // 内嵌的内核链表成员
}
结构体中没有定义链表,但是它包含了一个链表结构体head,可以通过操作head链表来管理 driver 结构体对象。通过操作head来操作driver,实质上就是通过操作结构体的某个成员变量来操作整个结构体变量。这里面要借助container_of宏定义来实现。
以上这种实现方法,结构体对象只专注于数据域的操作,不关注链表的操作。
双向链表结构体定义
链表由前趋(prev)和后继(next)两个指针字段组成。
/include/linux/List.h
struct list_head
{
struct list_head *next, *prev;
};
链表初始化,指向节点自己。
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = 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;
}
__list_add 操作示意图:
头部插入节点
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);
}
从图中看出,节点new插入到 head 和 head->prev 之间,即以head为参考,new 节点总是插入到链表的尾部。
关于头插和尾插的示意图:
链表的删除
相互指向对方
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
删除entry节点
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_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
container_of的功能介绍
根据一个结构体中的某个成员,计算出整个结构体的首地址
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
Linux中各种驱动对象之间的关联
在Linux内核中,属于同一类的不同对象通常用双向链表组织起来,有一部分代码的实现是代表对象的结构体直接作为链表的节点,节点中的prev指针和next指针分别指向该节点前序和后序的结构体。
object 对象通过链表链接在一起,比如同一种总线下的一类设备对象或者驱动对象,用链表维护。如果想找某个设备或者驱动,就如果遍历对应的链表。
kset作为设备容器,也是通过链表把一类设备关联起来。
参考:
https://zhuanlan.zhihu.com/p/74395679
https://zhuanlan.zhihu.com/p/74030627