Linux 双向链表设计思想

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值