linux-6.9中scoped_guard浅析

前言

最近在看 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 的特性充分运用起来了,不尽想如果自己去实现又得写多么冗长的代码呢?能做到这样的通用性吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值