ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

linux include/linux/cleanup.h 功能与用途分析 include/linux/cleanup.h功能与用途分析一、总体定位这是 Linux 内核提供的**基于作用域的自动资源清理scope-based cleanup**基础设施头文件。它借助 GCC/Clang 的__attribute__((cleanup))变量属性让编译器在变量离开作用域时自动调用清理函数从而系统性地消除内核代码中常见的goto err手动解绕unwind模式及其引发的资源泄漏。其设计理念类似 C 的 RAIIResource 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_FREE的free表达式刻意包含 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)定义一个锁 guardlock/unlock为语句DEFINE_GUARD_COND定义条件锁变体如trylock、lock_interruptibleguard(name)(obj)匿名 guard持锁至当前语句块结束scoped_guard(name, args) { }具名 scope锁生命周期绑定紧随的复合语句条件锁失败时跳过循环体scoped_cond_guard(name, fail, args) { }条件锁失败时执行failACQUIRE(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自动释放内存DEFINE_FREE(kfree,void*,if(_T)kfree(_T))structobj*alloc_obj(...){structobj*p__free(kfree)kmalloc(sizeof(*p),GFP_KERNEL);if(!p)returnNULL;/* 出作用域自动 kfree(p) */if(!init_obj(p))returnNULL;/* 同样自动 kfree(p) */return_ptr(p);/* 成功抑制清理并返回 p */}成功路径上return_ptr(p)先把p置空再返回清理被抑制配合free表达式里的 NULL 判断编译器可将整段清理优化为一条return p。2.__free释放设备引用DEFINE_FREE(pci_dev_put,structpci_dev*,if(_T)pci_dev_put(_T))structpci_dev*dev__free(pci_dev_put)pci_get_slot(parent,PCI_DEVFN(0,0));if(!dev)return-ENODEV;/* 自动 pci_dev_put(dev) */3.guard持锁到语句块结束DEFINE_GUARD(pci_dev,structpci_dev*,pci_dev_lock(_T),pci_dev_unlock(_T))func(structpci_dev*dev){if(cond){guard(pci_dev)(dev);/* 此处 pci_dev_lock() */...}/* 离开 if 块自动 pci_dev_unlock() */}注意锁只持有到if块结束而非整个func()。4.scoped_guard显式绑定作用域scoped_guard(mutex,lock){/* 临界区退出该复合语句时自动解锁 */do_something();}5. 条件锁ACQUIREACQUIRE_ERRDEFINE_GUARD(pci_dev,structpci_dev*,pci_dev_lock(_T),pci_dev_unlock(_T))DEFINE_GUARD_COND(pci_dev,_try,pci_dev_trylock(_T))intfunc(structpci_dev*dev){ACQUIRE(pci_dev_try,lock)(dev);intrcACQUIRE_ERR(pci_dev_try,lock);if(rc)returnrc;/* 加锁失败携带错误码返回 *//* 此处已持锁出作用域自动解锁 */return0;}6.scoped_cond_guard条件锁失败即跳错误处理scoped_cond_guard(name, fail, args...)也用于条件锁但把「加锁失败」的分支交给fail语句处理随后的复合语句是加锁成功后的临界区/* mutex_lock_interruptible 失败返回 -EINTR 等错误 */scoped_cond_guard(mutex_intr,return-EINTR,lock){/* 加锁成功的临界区退出该块自动解锁 */do_something();}/* 若加锁失败则执行 fail此处 return -EINTR不进入临界区 */与ACQUIREACQUIRE_ERR相比scoped_cond_guard更紧凑适合失败时只做固定动作如直接返回的场景而ACQUIRE_ERR能拿到具体错误码适合需要区分错误值的场景。7. 胖指针锁DEFINE_LOCK_GUARD_1针对需要保存额外状态如spin_lock_irqsave的 flags的锁用带类型的 guardDEFINE_LOCK_GUARD_1(spinlock_irqsave,spinlock_t,spin_lock_irqsave(_T-lock,_T-flags),spin_unlock_irqrestore(_T-lock,_T-flags),unsignedlongflags)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 的上下文分析仍能工作。七、使用约束文档中明确的注意事项变量定义即赋值使用__free()时应在同一语句定义并初始化不要在函数顶部集中声明。因为清理按定义逆序执行若把obj __free(...) NULL放在guard(mutex)之前会导致obj的清理在锁已释放后才执行文档中!!标注的 bug。正确写法是先guard再struct object *obj __free(remove_free) alloc_add();。不与goto混用goto可跨作用域跳转与作用域清理语义冲突。同一函数要么全部转成 cleanup要么都不用。八、典型收益消除样板式goto解绕代码减少泄漏面由编译器强制保证 LIFO 释放顺序成功路径经优化后零运行时开销驱动代码内核主体是主要受益方文档即以 PCI 驱动的pci_dev_put/pci_dev_unlock为例。九、一句话概括这是内核的「C 语言 RAII」头文件用编译器的cleanup属性把资源释放与锁释放自动绑定到变量作用域替代易错的goto手动清理。
返回列表