前言
最近在看 alsa 框架源码时,发现了 scoped_guard 函数,准确的说是一个宏定义。关于它的介绍网上几乎没有,大多数文章都是介绍 C++ 中的ScopedGuard。惭愧的是我对 C++ 并不熟悉,大概是说 C++ 中的 ScopedGuard 是自动释放锁的机制。猜测 linux 在较新的内核中引入了 C++ ScopedGuard 的概念或机制,但是是用 C 语言实现的。所以仔细分析了这块的源码,有了一点了解,希望能够给刚接触 linux 内核的同学一些参考,水平有限,有不对的地方还希望指正。
初次见面
在了解 linux scoped_guard 内部前总要看看内核是怎么使用的吧,下面是我和她初次见面的场景:
static int snd_card_init(struct snd_card *card, struct device *parent,
int idx, const char *xid, struct module *module,
size_t extra_size)
{
int err;
... ...
scoped_guard(mutex, &snd_card_mutex) {
if (idx < 0) /* first check the matching module-name slot */
idx = get_slot_from_bitmask(idx, module_slot_match, module);
if (idx < 0) /* if not matched, assign an empty slot */
idx = get_slot_from_bitmask(idx, check_empty_slot, module);
if (idx < 0)
err = -ENODEV;
else if (idx < snd_ecards_limit) {
if (test_bit(idx, snd_cards_lock))
err = -EBUSY; /* invalid */
} else if (idx >= SNDRV_CARDS)
err = -ENODEV;
if (!err) {
set_bit(idx, snd_cards_lock); /* lock it */
if (idx >= snd_ecards_limit)
snd_ecards_limit = idx + 1; /* increase the limit */
}
}
... ...
}
可以看到向 scoped_guard 传入了如下两个参数:
- &snd_card_mutex:是一个互斥锁的取址
- mutex:应该不是函数、变量之类的(函数或变量名肯定不会起的这么烂),那他是什么有什么作用,带着疑问我们走入内部看看
初步了解
通过代码追踪,发现 scoped_guard 是在 “include/linux/cleanup.h” 文件中定义的,至少 linux-6.9.8 内核是这样。以下是宏定义的源码:
#define scoped_guard(_name, args...) \
for (CLASS(_name, scope)(args), \
*done = NULL; __guard_ptr(_name)(&scope) && !done; done = (void *)1)
可以看到是一个 for 循环,可以分解成以下指令:
- 初始化
CLASS(_name, scope)(args); *done = NULL;
- 判断
__guard_ptr(_name)(&scope) && !done;
- 执行
done = (void *)1;
虽然看上去并不复杂,但是并不能一眼看透她,有一下几点疑问:
- CLASS 是什么?有什么作用?
- CLASS 的第二个参数 scope 从何而来,有什么作用?
- done 是在呢定义的,有什么作用?
- __guard_ptr 又做了什么?
看吧,一小段代码就有这么多疑问,所以我们需要继续深入。
深入了解
CLASS 宏
以下是 CLASS 宏定义:
#define CLASS(_name, var) \
class_##_name##_t var __cleanup(class_##_name##_destructor) = \
class_##_name##_constructor
一看到宏展开“初步了解”小节中的第二个问题就解决了一半,scope 就是宏展开的 var,是一个变量名,暂且不管。
但这样还是不太容易理解我们吧宏展开试试:
CLASS(mutex, scope) = class_mutex_t scope __cleanup(class_mutex_destructor) = class_mutex_constructor
首先 class_mutex_t 肯定是一个变量类型,class_mutex_destructor 和 class_mutex_constructor 应该是两个函数。我们暂且不管“__cleanup(class_mutex_destructor)”(但这个非常重要下文会说明作用),所以这部分就是定义一个 class_mutex_t 类型的变量,变量名为 scope,并且给这个变量赋值为 class_mutex_constructor。但是 class_mutex_constructor 不是一个函数吗,难道 class_mutex_t 是函数指针类型?当然不是,你忘了这是宏吗,这只是 CLASS 的宏展开呀,CLASS 外面还有参数呢,那组合到一起就是下面这样:
CLASS(mutex, scope)(args); = class_mutex_t scope __cleanup(class_mutex_destructor) = class_mutex_constructor(args);
DEFINE_CLASS 宏
那接下来我们找找 class_mutex_t 是什么类型,class_mutex_constructor 函数有做了什么呢?直接搜索不到?那当然因为他们也是该死的宏定义的,看看下边:
#define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...) \
typedef _type class_##_name##_t; \
static inline void class_##_name##_destructor(_type *p) \
{ _type _T = *p; _exit; } \
static inline _type class_##_name##_constructor(_init_args) \
{ _type t = _init; return t; }
怎么样开始好玩起来了吧,首先看看 DEFINE_CLASS 这个宏需要什么参数:
- _name:毫无疑问是名字,也就是 class_mutex_constructor 中的 mutex
- _type:看样子是返回类型,也就是 class_mutex_t
- _exit:退出函数
- _init:初始化函数
- _init_args…:一堆初始化参数
接下来看看这个宏都做了什么:
- 给 _type 类型娶个别名 class_mutex_t
- 定义内联函数
class_mutex_t class_mutex_destructor(class_mutex_t *p)
,并且调用了 _exit 函数(这里有个小疑问, _type _T = *p; 中的 _T 应该会传给 _exit 函数,是怎么做到的? 答:还是通过宏继续看就知道了) - 定义内联函数
class_mutex_t class_mutex_constructor(_init_args)
,并且调用了 _init 函数并返回 _init 函数的返回值(这里同样有个小疑问,_init_args 参数应该会传递给 _init 函数,是怎么做到的?答:还是通过宏继续看就知道了)
揭开面纱
让我们来看看 DEFINE_CLASS 宏到底实例化了一个什么怪物吧。可是直接搜索DEFINE_CLASS(mutex
,并没有得到结果,也就是说还有“中间商”。后来我找到了下面这些:
#define DEFINE_GUARD(_name, _type, _lock, _unlock) \
DEFINE_CLASS(_name, _type, if (_T) { _unlock; }, ({ _lock; _T; }), _type _T); \
static inline void * class_##_name##_lock_ptr(class_##_name##_t *_T) \
{ return *_T; }
宏展开后:
#define DEFINE_GUARD(mutex, _type, _lock, _unlock) = \
typedef _type class_mutex_t;
static inline void class_mutex_destructor(_type *p) \
{
_type _T = *p;
if (_T) {
_unlock;
}
}
static inline _type class_mutex_constructor(_type _T)
{
_type t = { _lock; _T; };
return t;
}
static inline void * class_mutex_lock_ptr(class_mutex_t *_T)
{
return *_T;
}
还好还好,没有什么新概念,只是将 DEFINE_CLASS 封装了一层,更方便使用。
好了可以继续我们的旅程了,接下来看 _type、_lock、_unlock 到底是什么:
DEFINE_GUARD(mutex, struct mutex *, mutex_lock(_T), mutex_unlock(_T))
- _type = struct mutex *:就是互斥锁的结构体指针
struct mutex { atomic_long_t owner; raw_spinlock_t wait_lock; #ifdef CONFIG_MUTEX_SPIN_ON_OWNER struct optimistic_spin_queue osq; /* Spinner MCS lock */ #endif struct list_head wait_list; #ifdef CONFIG_DEBUG_MUTEXES void *magic; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif };
- _lock = mutex_lock:获取锁函数
void __sched mutex_lock(struct mutex *lock) { might_sleep(); if (!__mutex_trylock_fast(lock)) __mutex_lock_slowpath(lock); }
- _unlock = mutex_unlock:释放锁函数
static int mutex_unlock(unsigned long *m) { int flags = FUTEX_WAKE; if (!processes) flags |= FUTEX_PRIVATE_FLAG; if (*m == 2) *m = 0; else if (xchg(m, 0) == 1) return 0; sys_futex(m, flags, 1, NULL, NULL, 0); return 0; }
CLASS 宏的完全展开如下:
// DEFINE_GUARD 宏定义的部分
typedef struct mutex * class_mutex_t;
static inline void class_mutex_destructor(class_mutex_t *p) \
{
class_mutex_t _T = *p;
if (_T) {
mutex_unlock(_T);
}
}
static inline class_mutex_t class_mutex_constructor(class_mutex_t _T)
{
class_mutex_t t = {
mutex_unlock(_T);
_T;
};
return t;
}
static inline void * class_mutex_lock_ptr(class_mutex_t *_T)
{
return *_T;
}
// CLASS 宏展开:CLASS(mutex, scope)(&snd_card_mutex)
class_mutex_t scope __cleanup(class_mutex_destructor) = class_mutex_constructor(&snd_card_mutex);
那么 __cleanup 是什么呢?__cleanup 的宏定义如下:
#define __cleanup(func) __attribute__((__cleanup__(func)))
__attribute__
是 gcc 编译器设置对象属性的指令,__cleanup__
属性的意思是当变量离开生命周期时,会自动调用这个销毁程序,也就是 func。这里为变量 scope 设置了一个属性,当这个变量离开生命周期时会自动调用 class_mutex_destructor 函数。
春天来了
搞懂 CLASS 宏后 scoped_guard 的作用忽然明了了,下面我们把 scoped_guard 宏展开一切就清晰了。宏展开如下:
// DEFINE_GUARD 宏定义的部分
typedef struct mutex * class_mutex_t;
static inline void class_mutex_destructor(class_mutex_t *p) \
{
class_mutex_t _T = *p;
if (_T) {
mutex_unlock(_T);
}
}
static inline class_mutex_t class_mutex_constructor(class_mutex_t _T)
{
class_mutex_t t = {
mutex_unlock(_T);
_T;
};
return t;
}
static inline void * class_mutex_lock_ptr(class_mutex_t *_T)
{
return *_T;
}
#define __guard_ptr(_name) class_##_name##_lock_ptr
// scoped_guard 宏定义的部分: scoped_guard(mutex, &snd_card_mutex) { }
for (class_mutex_t scope __cleanup(class_mutex_destructor) = class_mutex_constructor(&snd_card_mutex), *done = NULL;
class_mutex_lock_ptr(&scope) && !done; done = (void *)1) {
}
接下来整体梳理一下她是如何工作的:
- 首先在 mutex.h 中使用 DEFINE_GUARD 宏定义了一个 mutex 类型变量(就是类的概念),还为 mutex 类型变量定义了三个操作函数,分别为:
- class_mutex_destructor:释放锁
- class_mutex_constructor:获取锁
- class_mutex_lock_ptr:返回锁的指针(这个函数在 mutex 类中并没有实际作用)
- 当我我们使用 CLASS 宏创建一个 mutex 类型变量时,class_mutex_constructor 会自动执行(当然是宏实现的),并且会给这个变量设置一个销毁程序(在变量销毁时执行的程序)。
- 其他的就很好理解了,for 并不是实现循环,而是实现类似与 if 的作用。但是我没有找到 done 是在哪定义的,我想也不重要了不影响我们理解代码。
那么问题来了,我调用 scoped_guard 时做了什么,其实就是一个简单的上锁操作。那么你可能会说,上个锁为啥要弄的这么麻烦。因为她能实现自动解锁,你可能会惊奇,她怎么知道我什么时候要解锁的呢?事实上他并不知道,但是他在你的函数里创建了一个局部变量 scope,当你的函数执行完毕时,scope 变量肯定就到了她的“死期”,但是你不是为她创建了一个销毁函数,这个销毁函数就会执行,也就实现了自动释放锁的功能。
终于可以回答上边的问题了:
- CLASS 是什么?有什么作用?
答:她是一个实例化一个类的宏定义,mutex 就是类名;实例化一个类,并执行类的初始化函数,当类中的变量走完生命周期时再执行反初始化函数。 - CLASS 的第二个参数 scope 从何而来,有什么作用?
答:scope 并不是一个参数,而是一个变量名没有来源;她就是上面说的类中变量,由于 scope 是个局部变量,所以当函数退出时 scope 走完生命周期,就会调用反初始化函数。 - done 是在呢定义的,有什么作用?
答:我并没有找到她是在哪定义的,但是她大概用作标记执行次数,使 for 实现类似 if 的逻辑。 - __guard_ptr 又做了什么?
答:是类中定义的返回变量指针的函数,scope 变量初始化成功就会进入循环体。
写在最后的话
这段代码不是特别复杂,但是开发人员的思想还是让我惊叹。用这么简短的代码实现了锁定的自动释放(当然不止这一个功能),而且几乎全是宏实现的,把性能损耗做的极致。把宏定义和 gcc 的特性充分运用起来了,不尽想如果自己去实现又得写多么冗长的代码呢?能做到这样的通用性吗?