klist链表
| 💡本文基于linux-v4.14.7
1. 概述
klist 是 Linux 内核中基于 list_head 扩展的链表结构,提供了引用计数和线程安全的遍历机制。

1.1 设计目的
klist 的设计目标是解决以下问题:
- 安全遍历:允许在遍历过程中安全地修改链表(插入、删除节点)
- 引用计数 :通过
kref机制管理节点的生命周期 - 延迟删除:节点被标记删除后,只有在引用计数为 0 时才真正移除
- 线程安全:使用自旋锁保护链表操作
1.2 核心特性
- 基于
list_head的双向循环链表 - 每个节点包含引用计数(
kref) - 支持在遍历过程中安全删除节点
- 提供
get/put回调函数用于管理嵌入对象的引用计数
2. 数据结构定义
klist它表示的是一个对象的集合,本质上是一个带锁,带引用保护的双向链表。
c
struct klist {
spinlock_t k_lock; // 保护链表的自旋锁
struct list_head k_list; // 实际的链表头
void (*get)(struct klist_node *); // 获取节点时的回调
void (*put)(struct klist_node *); // 释放节点时的回调
} __attribute__ ((aligned (sizeof(void *))));
klist_node被挂到klist中的"某个对象",是链表里的一个成员。
c
struct klist_node {
void *n_klist; // 指向所属的 klist(低位用作删除标记)
struct list_head n_node; // 链表节点,用于链入链表
struct kref n_ref; // 引用计数,当计数为0时节点被真正的删除
};
klist_iter用来表示一次klist遍历过程的状态上下文
c
struct klist_iter {
struct klist *i_klist; // 正在遍历的 klist
struct klist_node *i_cur; // 当前所在的节点
};
klist_waiter 用来等待某个节点引用计数归0
c
struct klist_waiter {
struct list_head list; /* 链表节点,用于插入到klist_remove_waiters全局链表 */
struct klist_node *node; /* 等待的节点 */
struct task_struct *process; /* 保存当前运行的进程,用于后续被唤醒 */
int woken;
};
3. 核心操作函数源码分析
3.1 初始化
klist_init初始化整个klist容器,get和put是获取和释放节点时的回调函数。
c
void klist_init(struct klist *k, void (*get)(struct klist_node *),
void (*put)(struct klist_node *))
{
INIT_LIST_HEAD(&k->k_list);
spin_lock_init(&k->k_lock);
k->get = get;
k->put = put;
}
初始化klist某个节点,它会初始化该对象的kref->refcount引用计数为1,然后将klist_node绑定到指定的klist,并且如果get 存在的话则触发回调函数。
c
static void klist_node_init(struct klist *k, struct klist_node *n)
{
INIT_LIST_HEAD(&n->n_node);
kref_init(&n->n_ref);
knode_set_klist(n, k);
if (k->get)
k->get(n);
}
3.2 添加节点
klist_add_head在链表头部添加节点,实现调用klist_node_init 初始化改节点,然后调用add_head将其添加到链表头。
c
void klist_add_head(struct klist_node *n, struct klist *k)
{
klist_node_init(k, n);
add_head(k, n);
}
klist_add_tail在链表尾部添加节点,逻辑和klist_add_head 一样,只是节点插入的位置不一样。
c
void klist_add_tail(struct klist_node *n, struct klist *k)
{
klist_node_init(k, n);
add_tail(k, n);
}
klist_add_behind / klist_add_before在指定节点后/前添加节点,逻辑也和上面一样,只是节点插入的位置不一样。
c
void klist_add_behind(struct klist_node *n, struct klist_node *pos)
{
struct klist *k = knode_klist(pos);
klist_node_init(k, n);
spin_lock(&k->k_lock);
list_add(&n->n_node, &pos->n_node);
spin_unlock(&k->k_lock);
}
void klist_add_before(struct klist_node *n, struct klist_node *pos)
{
struct klist *k = knode_klist(pos);
klist_node_init(k, n);
spin_lock(&k->k_lock);
list_add_tail(&n->n_node, &pos->n_node);
spin_unlock(&k->k_lock);
}
3.3 删除节点

klist_del标记节点为删除状态并递减引用计数,他不会立即从链表中移除节点,只是标记节点为删除状态(设置 KNODE_DEAD 标志),递减引用计数,当计数为 0 时才真正删除,knode_kill 函数核心作用是将knode 所绑定n_klist 指针设置为KNODE_DEAD。klist_dec_and_del 函数内部会递减链表节点的引用计数,当计数到0时会触发klist_release 函数的调用。
c
void klist_del(struct klist_node *n)
{
klist_put(n, true);
}
static void klist_put(struct klist_node *n, bool kill)
{
struct klist *k = knode_klist(n); /* 获取所属的klist */
void (*put)(struct klist_node *) = k->put;
spin_lock(&k->k_lock);
if (kill)
knode_kill(n);
if (!klist_dec_and_del(n))
put = NULL;
spin_unlock(&k->k_lock);
if (put)
put(n); /* 如果有put函数,则触发 */
}
klist_release函数从klist中删除指定的klist_node节点,然后唤醒对应的等待进程,klist_remove_waiters 是一个全局链表,保存所有正在等待某个klist_node彻底释放的klist_waiter ,当对接节点被释放时就换唤醒对应klist_waiter 保存的等待进程。
c
static void klist_release(struct kref *kref)
{
struct klist_waiter *waiter, *tmp;
/* 找到对应的klist_node节点 */
struct klist_node *n = container_of(kref, struct klist_node, n_ref);
WARN_ON(!knode_dead(n));
list_del(&n->n_node); /* 删除链表节点 */
spin_lock(&klist_remove_lock);
/* 找到等待当前klist_node的waiter*/
list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {
if (waiter->node != n)
continue;
/* 将该waiter节点从waiter链表中删除,然后唤醒对应的等待进程 */
list_del(&waiter->list);
waiter->woken = 1;
mb();
wake_up_process(waiter->process);
}
spin_unlock(&klist_remove_lock);
/* 将已删除的kist_node的绑定的n_klist设置为NULL */
knode_set_klist(n, NULL);
}
klist_remove删除节点并等待其真正被移除,调用 klist_del 标记删除,阻塞等待直到节点引用计数为0并被真正移除,适用于需要确保节点完全移除的场景。
c
void klist_remove(struct klist_node *n)
{
struct klist_waiter waiter;
waiter.node = n; /* 保存当前要删除的节点 */
waiter.process = current; /* 保存当前运行的进程 */
waiter.woken = 0;
/* 将klist_waiter节点插入到全局等待链表 */
spin_lock(&klist_remove_lock);
list_add(&waiter.list, &klist_remove_waiters);
spin_unlock(&klist_remove_lock);
klist_del(n); /* 引用计数-1 */
/* 让出CPU,当节点被删除时会被唤醒 */
for (;;) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (waiter.woken)
break;
schedule();
}
/* 恢复运行态 */
__set_current_state(TASK_RUNNING);
}
3.4 遍历操作
klist_iter_init用于初始化遍历器,将i_klist指向k ,因为还未开始遍历,c_cur设置为NULL 。
c
void klist_iter_init(struct klist *k, struct klist_iter *i)
{
klist_iter_init_node(k, i, NULL);
}
klist_iter_init_node初始化遍历器,从指定节点开始遍历
c
void klist_iter_init_node(struct klist *k, struct klist_iter *i,
struct klist_node *n)
{
i->i_klist = k;
i->i_cur = NULL;
/* 引用计数不为0,则增加节点的引用计数,遍历器的当前遍历节点指向n */
if (n && kref_get_unless_zero(&n->n_ref))
i->i_cur = n;
}
klist_next获取下一个节点,to_klist_node 函数通过链表成员找到对应的klist_node 节点。通过调用knode_dead 来检查节点是否被标记为了KNODE_DEAD ,如果是则遍历器跳过该节点,如果不是则返回该节点。klist_prev获取前一个节点(实现机制类似 klist_next)。
c
struct klist_node *klist_next(struct klist_iter *i)
{
void (*put)(struct klist_node *) = i->i_klist->put;
struct klist_node *last = i->i_cur;
struct klist_node *next;
spin_lock(&i->i_klist->k_lock);
if (last) {
/* 找到下一个klist_node节点 */
next = to_klist_node(last->n_node.next);
if (!klist_dec_and_del(last)) /* 引用计数-1 */
put = NULL; /* 对象仍在使用 */
} else
/* 当遍历器没有当前节点last时,从链表头获取第一个节点 */
next = to_klist_node(i->i_klist->k_list.next);
i->i_cur = NULL;
/* 遍历到链表头则循环终止*/
while (next != to_klist_node(&i->i_klist->k_list)) {
/* 节点被标记了KNODE_DEAD时条件不成立 */
if (likely(!knode_dead(next))) {
/* 引用计数+1,返回该节点 */
kref_get(&next->n_ref);
i->i_cur = next;
break;
}
/* 找到下一个klist_node节点 */
next = to_klist_node(next->n_node.next);
}
spin_unlock(&i->i_klist->k_lock);
/* 如果put存在则对上一个节点调用put */
if (put && last)
put(last);
return i->i_cur;
}
klist_iter_exit结束遍历,释放当前节点的引用计数,必须调用此函数来正确释放引用计数。
c
void klist_iter_exit(struct klist_iter *i)
{
if (i->i_cur) {
/* 内部会递减该节点的引用计数 */
klist_put(i->i_cur, false);
i->i_cur = NULL;
}
}
3.5 查询操作
klist_node_attached检查节点是否已附加到某个 klist
c
int klist_node_attached(struct klist_node *n)
{
return (n->n_klist != NULL);
}