linux include/linux/cleanup.h 功能与用途分析

include/linux/cleanup.h 功能与用途分析

一、总体定位

这是 Linux 内核提供的**基于作用域的自动资源清理(scope-based cleanup)**基础设施头文件。它借助 GCC/Clang 的 __attribute__((cleanup)) 变量属性,让编译器在变量离开作用域时自动调用清理函数,从而系统性地消除内核代码中常见的 goto err 手动解绕(unwind)模式及其引发的资源泄漏。其设计理念类似 C++ 的 RAII(Resource Acquisition Is Initialization,资源获取即初始化),但以纯 C 宏实现。

二、解决的核心问题

传统内核错误处理依赖 goto 逐级释放资源:

  • 新增一个资源获取点,就要在所有 goto 标签间插入对应释放,容易遗漏或顺序错乱;
  • 释放顺序必须是 LIFO(后获取先释放),手工维护困难。

cleanup.h 把「释放动作」绑定到「变量作用域」,由编译器保证:变量出作用域即触发清理,多个变量按定义逆序清理(GCC 语义),天然满足 LIFO。

三、两大能力族

1. 资源自动释放(__free 族)

作用
DEFINE_FREE(name, type, free) 定义一个清理器 __free_##namefree 表达式中用 _T 引用变量
__free(name) 变量属性,绑定作用域清理
no_free_ptr(p) 类似非原子 xchg(p, NULL),取出指针并置空以抑制 清理(带 __must_check,防止意外泄漏)
return_ptr(p) return no_free_ptr(p),返回资源且不释放
retain_and_null_ptr(p) 资源已移交他人(消费成功)后置空,不再释放也不返回

关键实现细节:DEFINE_FREEfree 表达式刻意包含 NULL 判断 (如 if (_T) kfree(_T))。这样在成功路径 return_ptr 把指针置空后,编译器通过值传播 + 死代码消除,可将整段清理调用彻底优化掉,退化成一条 return p

2. 锁 / 通用析构(guard / CLASS 族)

底层是通用的构造-析构类机制:

  • DEFINE_CLASS(name, type, exit, init, args...):为某类型定义构造器与析构器;
  • CLASS(name, var)(args):声明一个受作用域管理的实例;
  • EXTEND_CLASS / EXTEND_CLASS_COND:派生出带条件的变体。

在其之上封装出锁专用接口:

用途
DEFINE_GUARD(name, type, lock, unlock) 定义一个锁 guard(lock/unlock 为语句)
DEFINE_GUARD_COND 定义条件锁变体(如 trylocklock_interruptible
guard(name)(obj) 匿名 guard,持锁至当前语句块结束
scoped_guard(name, args) { } 具名 scope,锁生命周期绑定紧随的复合语句;条件锁失败时跳过循环体
scoped_cond_guard(name, fail, args) { } 条件锁失败时执行 fail
ACQUIRE(name, var) / ACQUIRE_ERR(name, &var) 具名 guard + 错误码提取,用于需要检查加锁结果的条件锁场景

针对无原生类型(RCU、preempt)或需要「胖指针」(spin_lock_irqsave 要保存 flags)的锁,提供:

  • DEFINE_LOCK_GUARD_0(无 lock 对象)、DEFINE_LOCK_GUARD_1(单 lock 对象)、DEFINE_LOCK_GUARD_1_COND(条件变体);这些会生成一个内含 type *lock 及附加字段(如 flags)的 class_##name##_t 结构体。

四、用法示例

1. __free 自动释放内存

c 复制代码
DEFINE_FREE(kfree, void *, if (_T) kfree(_T))

struct obj *alloc_obj(...)
{
	struct obj *p __free(kfree) = kmalloc(sizeof(*p), GFP_KERNEL);
	if (!p)
		return NULL;            /* 出作用域自动 kfree(p) */

	if (!init_obj(p))
		return NULL;            /* 同样自动 kfree(p) */

	return_ptr(p);                 /* 成功:抑制清理并返回 p */
}

成功路径上 return_ptr(p) 先把 p 置空再返回,清理被抑制;配合 free 表达式里的 NULL 判断,编译器可将整段清理优化为一条 return p

2. __free 释放设备引用

c 复制代码
DEFINE_FREE(pci_dev_put, struct pci_dev *, if (_T) pci_dev_put(_T))

struct pci_dev *dev __free(pci_dev_put) =
	pci_get_slot(parent, PCI_DEVFN(0, 0));
if (!dev)
	return -ENODEV;            /* 自动 pci_dev_put(dev) */

3. guard:持锁到语句块结束

c 复制代码
DEFINE_GUARD(pci_dev, struct pci_dev *,
	     pci_dev_lock(_T), pci_dev_unlock(_T))

func(struct pci_dev *dev)
{
	if (cond) {
		guard(pci_dev)(dev);  /* 此处 pci_dev_lock() */
		...
	}                             /* 离开 if 块自动 pci_dev_unlock() */
}

注意锁只持有到 if 块结束,而非整个 func()

4. scoped_guard:显式绑定作用域

c 复制代码
scoped_guard(mutex, &lock) {
	/* 临界区,退出该复合语句时自动解锁 */
	do_something();
}

5. 条件锁:ACQUIRE + ACQUIRE_ERR

c 复制代码
DEFINE_GUARD(pci_dev, struct pci_dev *,
	     pci_dev_lock(_T), pci_dev_unlock(_T))
DEFINE_GUARD_COND(pci_dev, _try, pci_dev_trylock(_T))

int func(struct pci_dev *dev)
{
	ACQUIRE(pci_dev_try, lock)(dev);
	int rc = ACQUIRE_ERR(pci_dev_try, &lock);
	if (rc)
		return rc;             /* 加锁失败,携带错误码返回 */
	/* 此处已持锁,出作用域自动解锁 */
	return 0;
}

6. scoped_cond_guard:条件锁失败即跳错误处理

scoped_cond_guard(name, fail, args...) 也用于条件锁,但把「加锁失败」的分支交给 fail 语句处理,随后的复合语句是加锁成功后的临界区:

c 复制代码
/* mutex_lock_interruptible 失败返回 -EINTR 等错误 */
scoped_cond_guard(mutex_intr, return -EINTR, &lock) {
	/* 加锁成功的临界区,退出该块自动解锁 */
	do_something();
}
/* 若加锁失败,则执行 fail(此处 return -EINTR),不进入临界区 */

ACQUIRE + ACQUIRE_ERR 相比,scoped_cond_guard 更紧凑,适合失败时只做固定动作(如直接返回)的场景;而 ACQUIRE_ERR 能拿到具体错误码,适合需要区分错误值的场景。

7. 胖指针锁:DEFINE_LOCK_GUARD_1

针对需要保存额外状态(如 spin_lock_irqsave 的 flags)的锁,用带类型的 guard:

c 复制代码
DEFINE_LOCK_GUARD_1(spinlock_irqsave, spinlock_t,
		    spin_lock_irqsave(_T->lock, _T->flags),
		    spin_unlock_irqrestore(_T->lock, _T->flags),
		    unsigned long flags)

scoped_guard(spinlock_irqsave, &my_lock) {
	/* flags 保存在 guard 结构体中,退出时自动 restore */
}

五、条件锁的错误传递机制

条件锁通过 IS_ERR 指针编码结果:

  • __DEFINE_GUARD_LOCK_PTR 生成 lock_ptr(加锁失败返回 NULL)与 lock_err(返回 0 或负错误码);
  • scoped_guard 的循环条件 __guard_ptr(&scope) || !__is_cond_ptr(name) 保证:无条件锁必然执行循环体,条件锁则在加锁失败时跳过;
  • ACQUIRE_ERR 本质是对 guard 指针做 PTR_ERR 转换。

六、对静态分析(Context Analysis)的支持

文件末尾的 DECLARE_LOCK_GUARD_1_ATTRS / WITH_LOCK_GUARD_1_ATTRS__no_context_analysis 标记,用于解决一个矛盾:cleanup 把锁包进结构体并经辅助函数传递,遮蔽了锁别名,编译器的加锁/解锁静态检查(__acquires / __releases)无法跨函数分析。解决办法是引入一个「什么都不做、仅带 _unlock 属性」的别名变量,在作用域结束时告诉编译器此处发生了解锁,从而让 Sparse/Clang 的上下文分析仍能工作。

七、使用约束(文档中明确的注意事项)

  1. 变量定义即赋值 :使用 __free() 时应在同一语句定义并初始化,不要在函数顶部集中声明。因为清理按定义逆序执行,若把 obj __free(...) = NULL 放在 guard(mutex) 之前,会导致 obj 的清理在锁已释放后 才执行(文档中 !! 标注的 bug)。正确写法是先 guard,再 struct object *obj __free(remove_free) = alloc_add();
  2. 不与 goto 混用goto 可跨作用域跳转,与作用域清理语义冲突。同一函数要么全部转成 cleanup,要么都不用。

八、典型收益

  • 消除样板式 goto 解绕代码,减少泄漏面;
  • 由编译器强制保证 LIFO 释放顺序;
  • 成功路径经优化后零运行时开销;
  • 驱动代码(内核主体)是主要受益方,文档即以 PCI 驱动的 pci_dev_put / pci_dev_unlock 为例。

九、一句话概括

这是内核的「C 语言 RAII」头文件,用编译器的 cleanup 属性把资源释放与锁释放自动绑定到变量作用域,替代易错的 goto 手动清理。

相关推荐
j7~10 天前
【C++】智能指针的使用及其原理--详解
c++·c++11·内存泄漏·智能指针·raii·boost智能指针
Byte不洛4 个月前
深入理解C++智能指针:从RAII到shared_ptr
c++·智能指针·raii·unique_ptr·shared_ptr·auto_ptr
CoderMeijun5 个月前
C++ 智能指针:auto_ptr
c++·内存管理·智能指针·raii·auto_ptr
-许平安-5 个月前
MCP项目笔记十(客户端 MCPClient)
c++·笔记·ai·raii·mcp·pluginapi·plugin system
-许平安-6 个月前
MCP项目笔记六(PluginsLoader)
c++·笔记·raii·plugin system
知无不研6 个月前
c++垃圾回收机制
开发语言·c++·智能指针·raii·垃圾回收机制
charlee447 个月前
为什么现代 C++ 库都用 PIMPL?一场关于封装、依赖与安全的演进
c++·智能指针·raii·pimpl·编译防火墙·封装设计