基于作用域的清理辅助工具
概述
清理辅助 API 提供在变量离开作用域时自动清理资源的机制,类似于 C++ 的 RAII 或 Go 的 defer 语句。该 API 利用编译器对 __cleanup 属性的支持,确保清理代码自动执行,从而防止资源泄漏并简化错误处理。
必须设置 CONFIG_SCOPE_CLEANUP_HELPERS 才能启用此功能。它尤其适用于:
自动释放互斥量和信号量
自动释放动态分配的内存
确保所有代码路径(包括提前返回)都会执行清理操作
减少重复的清理代码
警告
清理机制使用 __cleanup 属性实现。如果工具链不支持此属性,则该 API 不可用。
因此,该 API 仅供用户应用程序使用,不供内核本身或其他子系统使用。
核心概念
清理 API 提供三种主要抽象:
作用域变量
作用域变量定义一种具备自动初始化和退出行为的类型。以作用域变量类型声明的变量由初始化函数初始化,并在离开作用域时由退出函数自动清理。
作用域守卫
作用域守卫是一种专用的作用域变量,在初始化时自动获取锁或资源,并在离开作用域时释放。这种模式常用于互斥量、信号量及其他同步原语。
作用域延迟操作
作用域延迟操作在变量离开作用域时执行指定函数,初始化时不获取任何资源。这类似于 Go 等语言中的 defer 语句。
定义作用域类型
自定义作用域变量
使用 SCOPE_VAR_DEFINE 定义带有初始化函数和退出函数的自定义作用域变量类型:
static inline struct flash_area *flash_area_init(int area_id)
{
struct flash_area *fa;
if (flash_area_open(area_id, &fa) < 0) {
return NULL;
}
return fa;
}
static inline void flash_area_exit(struct flash_area *fa)
{
if (fa != NULL) {
flash_area_close(fa);
}
}
// Define the scoped variable type
SCOPE_VAR_DEFINE(flash_area, struct flash_area *, flash_area_exit(_T),
flash_area_init(area_id), int area_id);
static int some_function(void)
{
// Declare 'fa' with automatic cleanup
scope_var(flash_area, fa)(PARTITION_ID(storage_partition));
if (fa == NULL) {
return -EINVAL; // Exit function is still called
}
// Use fa normally
printk("Has driver: %d\n", flash_area_has_driver(fa));
// No need to manually close - exit function is called automatically
return 0;
}
退出函数表达式中的 _T 变量包含正在清理的变量的值。
Scoped Guards
使用 SCOPE_GUARD_DEFINE 定义一个守卫,在初始化时获取锁,在退出作用域时释放锁:
// Example guard definition (already provided by <zephyr/cleanup/kernel.h>)
SCOPE_GUARD_DEFINE(k_mutex, struct k_mutex *,
(void)k_mutex_lock(_T, K_FOREVER),
(void)k_mutex_unlock(_T));
static K_MUTEX_DEFINE(lock);
void critical_section(void)
{
scope_guard(k_mutex)(&lock);
// Lock is held here
// Perform critical operations
// Lock is automatically released when guard goes out of scope
}
块作用域守卫
scope_guard 将守卫保持到 外围 作用域结束,而 scoped_guard 将守卫绑定到紧随其后的花括号块,并在退出该块时立即释放。这样可以明确标出短小的临界区,并将锁对象放在其保护的代码旁边:
static K_MUTEX_DEFINE(lock);
void worker(void)
{
// ... work that does not need the lock ...
scoped_guard(k_mutex, &lock) {
// lock held only inside these braces
}
// lock released here
// ... more work without the lock held ...
}
该块恰好执行一次。无论以何种方式退出该块,包括 break、return 和 goto,都会释放锁。注意,continue 会离开该块(行为与 break 相同),而不会重新执行该块。
条件守卫
使用 SCOPE_GUARD_DEFINE 定义的守卫总会获取锁(以 K_FOREVER 阻塞)。要表示可能获取失败的守卫(例如使用非阻塞的 K_NO_WAIT 尝试加锁),请将 SCOPE_COND_GUARD_DEFINE 与 scoped_cond_guard 配合使用。获取表达式会被求值以判定是否成功:失败时,守卫保存 NULL,执行提供的失败语句,并跳过该块。
// Example guard definition (already provided by <zephyr/cleanup/kernel.h>)
SCOPE_COND_GUARD_DEFINE(k_mutex_try, struct k_mutex *,
k_mutex_lock(_T, K_NO_WAIT) == 0,
(void)k_mutex_unlock(_T));
static K_MUTEX_DEFINE(lock);
int try_critical_section(void)
{
scoped_cond_guard(k_mutex_try, return -EBUSY, &lock) {
// runs only if the lock was acquired
// released automatically when the block is exited
}
return 0;
}
失败语句可以是任意语句,例如 break、return -EBUSY,或用于静默跳过该块的 {}。
Scoped Defers
使用 SCOPE_DEFER_DEFINE 定义执行清理函数的延迟操作:
// Define a defer for a custom cleanup function
static void cleanup_resources(void)
{
// Cleanup code here
}
SCOPE_DEFER_DEFINE(cleanup_resources);
void some_function(void)
{
scope_defer(cleanup_resources)();
// Do work...
// cleanup_resources() is called automatically
}
对于带参数的函数:
// Example deferred k_free (already provided by <zephyr/cleanup/kernel.h>)
SCOPE_DEFER_DEFINE(k_free, void *);
void allocate_and_use(void)
{
void *ptr = k_malloc(100);
scope_defer(k_free)(ptr);
// Use ptr...
// k_free(ptr) is called automatically
}
使用说明
清理顺序
清理函数按照声明的逆序调用(LIFO,后进先出),与资源自然嵌套的顺序一致:
{
scope_guard(k_mutex)(&lock); // Acquired first
void *ptr = k_malloc(100);
scope_defer(k_free)(ptr); // Registered second
// Do work...
} // ptr is freed first, then mutex is unlocked
作用域规则
变量离开作用域时会执行清理,包括:
到达块的末尾
提前返回语句
循环中的 break 或 continue
跳出作用域的 goto 语句
void example_with_early_exit(struct k_mutex *lock)
{
scope_guard(k_mutex)(lock);
if (error_condition) {
return; // Guard cleanup happens here
}
// Normal path
} // Guard cleanup also happens here