klist链表

klist链表

| 💡本文基于linux-v4.14.7

1. 概述

klist 是 Linux 内核中基于 list_head 扩展的链表结构,提供了引用计数和线程安全的遍历机制。

1.1 设计目的

klist 的设计目标是解决以下问题:

  1. 安全遍历:允许在遍历过程中安全地修改链表(插入、删除节点)
  2. 引用计数 :通过 kref 机制管理节点的生命周期
  3. 延迟删除:节点被标记删除后,只有在引用计数为 0 时才真正移除
  4. 线程安全:使用自旋锁保护链表操作

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容器,getput是获取和释放节点时的回调函数。

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_DEADklist_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);
}
相关推荐
一个平凡而乐于分享的小比特1 小时前
Linux动态库与静态库技术详解
linux·动态库·静态库
XiaoHu02071 小时前
Linux网络编程(第三弹)
linux·运维·网络
Remember_9932 小时前
【数据结构】Java集合核心:线性表、List接口、ArrayList与LinkedList深度解析
java·开发语言·数据结构·算法·leetcode·list
袁袁袁袁满2 小时前
Docker服务彻底清空的所有相关资源(容器、镜像、网络、数据卷等)
linux·运维·ubuntu·docker·容器·docker清空资源·docker停掉资源
Run_Teenage2 小时前
Linux:匿名管道(实现个进程池)和命名管道
linux·运维·服务器
warton882 小时前
proxysql配置mysql mgr代理,实现读写分离
linux·运维·数据库·mysql
skywalk81632 小时前
Ubuntu22.04安装docker并启动 dnote服务
linux·ubuntu·docker·dnote
上天_去_做颗惺星 EVE_BLUE2 小时前
Android设备与Mac/Docker全连接指南:有线到无线的完整方案
android·linux·macos·adb·docker·容器·安卓
BingoXXZ2 小时前
20260114Linux学习笔记
linux·服务器·笔记·学习