klist 迭代器初始化:klist_iter_init_node 与 klist_iter_init

klist 迭代器初始化:klist_iter_init_node 与 klist_iter_init

本代码片段展示了 Linux 内核中 klist(一种引用计数的链表)迭代器的初始化函数。其核心功能是为一个 klist_iter 结构体(迭代器/游标)设置其初始状态,使其准备好开始遍历一个 klist 。它通过安全地获取对指定起始节点的引用,来确保遍历的起点是有效且不会被并发释放的,从而为后续的 klist_next 操作提供了安全保障。

实现原理分析

此代码是 klist 安全遍历机制的入口点,其实现原理的关键在于利用引用计数来解决并发环境下的 use-after-free 问题。

  1. 引用计数作为安全保障:

    • klist 的设计哲学是,任何对链表节点的有效访问都必须持有 该节点的一个引用。迭代器 klist_iter 也不例外,它的 i_cur 字段指向的当前节点,必须是一个已经被迭代器持有了引用的节点。
    • 初始化过程的本质,就是为迭代器安全地获取对第一个节点(即起始节点)的引用。
  2. kref_get_unless_zero 原子操作:

    • klist_iter_init_node 函数中最核心的操作是 kref_get_unless_zero(&n->n_ref)。这是一个原子的、有条件的引用计数增加操作。
    • 原子性: 它能保证在检查和增加引用计数的过程中,不会被其他任务或中断打断。
    • 条件性 (_unless_zero) : 这是最关键的特性。它只在节点的当前引用计数不为零 的情况下,才会成功地将引用计数加一并返回 true。如果引用计数已经为零,意味着该节点正处于被销毁的过程中,此时尝试获取一个新的引用是不安全的。在这种情况下,kref_get_unless_zero 会失败并返回 false
    • 目的 : 这个函数完美地解决了在初始化迭代器时可能发生的竞态条件:当一个任务尝试以节点 n 为起点初始化迭代器时,另一个任务(或中断)可能正在释放节点 nkref_get_unless_zero 确保了只有当节点 n 仍然"存活"(引用计数 > 0)时,迭代器才能成功地将其设置为当前节点,否则 i->i_cur 将保持为 NULL,表示一个无效或空的起始点。
  3. 封装与便利性 (klist_iter_init):

    • klist_iter_init 是一个简单的内联封装函数。它调用 klist_iter_init_node 并将起始节点 n 显式地设置为 NULL
    • 这为最常见的用例------从链表的头部开始遍历------提供了一个更简洁的 API。当 nNULL 时,i_cur 会被初始化为 NULL,后续的 klist_next 调用将自动从 klist 的链表头开始查找第一个有效的节点。

特定场景分析:单核、无MMU的STM32H750平台

功能相关性

klist 是内核设备模型的基础。前文分析的 class_dev_iter_init 函数,其核心正是调用了 klist_iter_init_node。因此,对于在 STM32H750 上运行的、使用了任何标准设备驱动(块设备、输入、网络等)的系统,klist_iter_init_node 是一个频繁被调用的、基础性的函数,是实现安全设备遍历的基石。

单核环境影响
  • 原子操作的必要性 : kref_get_unless_zero 底层是基于原子操作(atomic_t)实现的。ARMv7-M 架构为这些原子操作提供了专门的指令支持(如 LDREX/STREX),确保了即使在单核系统上,操作也是原子的。
  • 并发源 : 在多核系统中,并发主要来自不同的 CPU。在单核 处理器上,并发主要来自中断可抢占的任务 。一个中断处理程序(例如,处理设备热拔除的中断)完全有可能在主线任务(例如,一个正在读取 /proc/partitions 的进程)调用 klist_iter_init_node 的过程中,尝试释放同一个 klist_node
  • 结论 : 因此,kref_get_unless_zero 的原子性和条件性在单核环境下仍然是绝对必要的 。它保证了在任务上下文和中断上下文之间对 klist 节点进行操作的安全性。
无MMU影响
  • klistkref(引用计数)是纯粹的内核数据结构和算法。它们的所有操作,包括链表指针的移动和引用计数的增减,都发生在内核的内存空间中。
  • 这些机制完全不依赖于虚拟内存地址到物理地址的翻译。
  • 因此,缺少 MMU 对 klist_iter_init_nodeklist_iter_init 函数的逻辑和正确性没有任何影响

代码分析

c 复制代码
/**
 * @brief klist_iter_init_node - 初始化一个 klist_iter 结构体。
 * @param k: 要遍历的 klist。
 * @param i: 要被初始化的 klist_iter 结构体。
 * @param n: 迭代开始的节点。
 *
 * 与 klist_iter_init() 类似,但从指定的节点 @n 开始,而不是从链表头开始。
 */
void klist_iter_init_node(struct klist *k, struct klist_iter *i,
			  struct klist_node *n)
{
	// 在迭代器中保存要遍历的 klist 的指针。
	i->i_klist = k;
	// 将迭代器的当前节点指针初始化为 NULL,这是一个安全默认值。
	i->i_cur = NULL;
	// 如果提供了一个有效的起始节点 n,并且...
	// ...成功地获取了该节点的引用(即该节点的引用计数不为0),则...
	if (n && kref_get_unless_zero(&n->n_ref))
		// ...将迭代器的当前节点设置为该起始节点。
		// 这确保了迭代器持有一个对起始节点的有效引用。
		i->i_cur = n;
}
EXPORT_SYMBOL_GPL(klist_iter_init_node);

/**
 * @brief klist_iter_init - 初始化一个 klist_iter 结构体。
 * @param k: 要遍历的 klist。
 * @param i: 要被初始化的 klist_iter 结构体。
 *
 * 与 klist_iter_init_node() 类似,但总是从链表的头部开始。
 */
void klist_iter_init(struct klist *k, struct klist_iter *i)
{
	// 调用核心实现,并将起始节点设置为 NULL,
	// 表示后续的 klist_next() 将从链表头开始查找。
	klist_iter_init_node(k, i, NULL);
}
EXPORT_SYMBOL_GPL(klist_iter_init);
相关推荐
朱昆鹏3 小时前
如何通过sessionKey 登录 Claude
前端·javascript·人工智能
cjinhuo3 小时前
标签页、书签太多找不到?AI 分组 + 拼音模糊搜索,开源插件秒解切换难题!
前端·算法·开源
小章鱼学前端3 小时前
小程序中使用 Iconfont 图标的优化指南
前端
凸头3 小时前
Collections.synchronizedList()详解
java
一心只读圣贤书3 小时前
解决jinkins的CI、CD使用非root执行sudo命令时无密码权限导致报错的问题
前端
用户0273851840263 小时前
【Android】MotionLayout详解
java·程序员
Jammingpro3 小时前
【Git版本控制】Git初识、安装、仓库初始化与仓库配置(含git init、git config与配置无法取消问题)
java·git·elasticsearch
wydaicls3 小时前
AIDL 接口的定义与生成,使用
java·开发语言
云草桑3 小时前
C#入坑JAVA 使用XXLJob
java·开发语言·c#