linux内核部件分析(四)——更强的链表klist

本文详细介绍了Linux内核中的一种特殊链表结构——klist,它是针对特定场景,如设备驱动模型,从通用的list_head发展出来的。klist结合了kref引用计数机制,允许动态删除节点,同时在遍历时能忽略已请求删除的节点,避免了全局锁的使用。klist的实现包括结构体定义、节点操作、链表初始化和遍历等功能,为处理动态变化的数据结构提供了高效解决方案。
摘要由CSDN通过智能技术生成

     前面我们说到过list_head,这是linux中通用的链表形式,双向循环链表,功能强大,实现简单优雅。可如果您认为list_head就是链表的极致,应该在linux链表界一统天下,那可就错了。据我所知,linux内核代码中至少还有两种链表能占有一席之地。一种就是hlist,一种就是本节要介绍的klist。虽然三者不同,但hlist和klist都可以看成是从list_head中发展出来的,用于特殊的链表使用情景。hlist是用于哈希表中。众所周知,哈希表主要就是一个哈希数组,为了解决映射冲突的问题,常常把哈希数组的每一项做成一个链表,这样有多少重复的都可以链进去。但哈希数组的项很多,list_head的话每个链表头都需要两个指针的空间,在稀疏的哈希表中实在是一种浪费,于是就发明了hlist。hlist有两大特点,一是它的链表头只需要一个指针,二是它的每一项都可以找到自己的前一节点,也就是说它不再循环,但仍是双向。令人不解的是,hlist的实现太绕了,比如它明明可以直接指向前一节点,却偏偏指向指针地址,还是前一节点中指向后一节点的指针地址。即使这种设计在实现时占便宜,但它理解上带来的不便已经远远超过实现上带来的小小便利。

    同hlist一样,klist也是为了适应某类特殊情形的要求。考虑一个被简化的情形,假设一些设备被链接在设备链表中,一个线程命令卸载某设备,即将其从设备链表中删除,但这时该设备正在使用中,这时就出现了冲突。当前可以设置临界区并加锁,但因为使用一个设备而锁住整个设备链表显然是不对的;又或者可以从设备本身做文章,让线程阻塞,这当然也可以。但我们上节了解了kref,就该知道linux对待这种情况的风格,给它一个引用计数kref,等计数为零就删除。klist就是这么干的,它把kref直接保存在了链表节点上。之前说到有线程要求删除设备,之前的使用仍存在,所以不能实际删除,但不应该有新的应用访问到该设备。klist就提供了一种让节点在链表上隐身的方法。下面还是来看实际代码吧。

    klist的头文件是include/linux/klist.h,实现在lib/klist.c。

struct klist_node;
struct klist {
	spinlock_t		k_lock;
	struct list_head	k_list;
	void			(*get)(struct klist_node *);
	void			(*put)(struct klist_node *);
} __attribute__ ((aligned (4)));

#define KLIST_INIT(_name, _get, _put)					\
	{ .k_lock	= __SPIN_LOCK_UNLOCKED(_name.k_lock),		\
	  .k_list	= LIST_HEAD_INIT(_name.k_list),			\
	  .get		= _get,						\
	  .put		= _put, }

#define DEFINE_KLIST(_name, _get, _put)					\
	struct klist _name = KLIST_INIT(_name, _get, _put)

extern void klist_init(struct klist *k, void (*get)(struct klist_node *),
		       void (*put)(struct klist_node *));

struct klist_node {
	void			*n_klist;	/* never access directly */
	struct list_head	n_node;
	struct kref		n_ref;
};

可以看到,klist的链表头是struct klist结构,链表节点是struct klist_node结构。先看struct klist,除了包含链表需要的k_list,还有用于加锁的k_lock。剩余的get()和put()函数是用于struct klist_node嵌入在更大的结构中,这样在节点初始时调用get(),在节点删除时调用put(),以表示链表中存在对结构的引用。再看struct klist_node,除了链表需要的n_node,还有一个引用计数n_ref。还有一个比较特殊的指针n_klist,n_klist是指向链表头struct klist的,但它的第0位用来表示是否该节点已被请求删除,如果已被请求删除则在链表循环时是看不到这一节点的,循环函数将其略过。现在你明白为什么非要在struct klist的定义后加上__attribute__((aligned(4)))。不过说实话这样在x86下仍然不太保险,但linux选择了相信gcc,毕竟是多年的战友和兄弟了,相互知根知底。

看过这两个结构,想必大

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值