欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

__attribute__ (( unused,__cleanup__)),FRR的互斥锁

程序员文章站 2024-02-13 20:46:22
...

        今天看了下FRR中常见的一个锁实现,当然离开不了其中强大的宏frr_with_mutex。以一个示例慢慢揭开它的面纱吧。

    frr_with_mutex(&m->mtx) {
		if (XXX)
			break;

		/*
        *对m的操作
        */

		AWAKEN(m);
	}

       为了知道frr_with_mutex是什么,接下来是一层层扒开宏,接下来的宏可能比较枯燥,不愿细看推倒可以直接看后面宏结果

/* mutex auto-lock/unlock */

/* variant 1:
 * (for short blocks, multiple mutexes supported)
 * break & return can be used for aborting the block
 *
 * frr_with_mutex(&mtx, &mtx2) {
 *    if (error)
 *       break;
 *    ...
 * }
 */
#define _frr_with_mutex(mutex)                                                 \
	*NAMECTR(_mtx_) __attribute__((                                        \
		unused, cleanup(_frr_mtx_unlock))) = _frr_mtx_lock(mutex),     \
	/* end */

#define frr_with_mutex(...)                                                    \
	for (pthread_mutex_t MACRO_REPEAT(_frr_with_mutex, ##__VA_ARGS__)      \
	     *_once = NULL; _once == NULL; _once = (void *)1)                  \
	/* end */



/* variadic macros, use like:
 * #define V_0()  ...
 * #define V_1(x) ...
 * #define V(...) MACRO_VARIANT(V, ##__VA_ARGS__)(__VA_ARGS__)
 */
#define _MACRO_VARIANT(A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10, N, ...) N

#define _CONCAT2(a, b) a ## b
#define _CONCAT(a, b) _CONCAT2(a,b)

#define MACRO_VARIANT(NAME, ...) \
	_CONCAT(NAME, _MACRO_VARIANT(0, ##__VA_ARGS__, \
			_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0))

#define NAMECTR(name) _CONCAT(name, __COUNTER__)

/* per-arg repeat macros, use like:
 * #define PERARG(n) ...n...
 * #define FOO(...) MACRO_REPEAT(PERARG, ##__VA_ARGS__)
 */

#define _MACRO_REPEAT_0(NAME)
#define _MACRO_REPEAT_1(NAME, A1) \
	NAME(A1)
#define _MACRO_REPEAT_2(NAME, A1, A2) \
	NAME(A1) NAME(A2)
#define _MACRO_REPEAT_3(NAME, A1, A2, A3) \
	NAME(A1) NAME(A2) NAME(A3)
#define _MACRO_REPEAT_4(NAME, A1, A2, A3, A4) \
	NAME(A1) NAME(A2) NAME(A3) NAME(A4)
#define _MACRO_REPEAT_5(NAME, A1, A2, A3, A4, A5) \
	NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5)
#define _MACRO_REPEAT_6(NAME, A1, A2, A3, A4, A5, A6) \
	NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6)
#define _MACRO_REPEAT_7(NAME, A1, A2, A3, A4, A5, A6, A7) \
	NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7)
#define _MACRO_REPEAT_8(NAME, A1, A2, A3, A4, A5, A6, A7, A8) \
	NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7) NAME(A8)

#define MACRO_REPEAT(NAME, ...) \
	MACRO_VARIANT(_MACRO_REPEAT, ##__VA_ARGS__)(NAME, ##__VA_ARGS__)

相关函数有:

static inline pthread_mutex_t *_frr_mtx_lock(pthread_mutex_t *mutex)
{
	pthread_mutex_lock(mutex);
	return mutex;
}

static inline void _frr_mtx_unlock(pthread_mutex_t **mutex)
{
	if (!*mutex)
		return;
	pthread_mutex_unlock(*mutex);
	*mutex = NULL;
}

    其中__COUNTER__是一个从 0 开始计数,然后每次调用加 1的计数器。

    经过一番推敲,得到了一下代码:

//frr_with_mutex(mutex)即
for (pthread_mutex_t *NAMECTR(_mtx_)                                           /* 1 */
    __attribute__((unused, cleanup(_frr_mtx_unlock))) = _frr_mtx_lock(mutex),  /* 2 */
    *_once = NULL;_once == NULL; _once = (void *)1)                            /* 3 */

1:NAMECTR(_mtx_) 是一个名字变的变量名,__COUNTER__每次调用则加1,所以每次用这个宏,这个临时指针变量名就是_mtx_0,_mtx_1,_mtx_2...,后记_mtx_X

2:编译器属性__attribute__用于向编译器描述特殊的标识、检查或优化,这里用到了unused和cleanup可选属性值。

  • 如果定义了一个变量而不使用,有的编译器会有告警。__attribute__((unused))可以告诉编译器忽略此告警。
  • __attribute__ ((cleanup(function))) 的作用是  当其修饰的变量离开了其生命周期,那么会自动调用所指定的销毁函数function。这里是先调用_frr_mtx_lock(mutex),将其返回值mutex传给了临时指针变量_mtx_X,然后在for循环结束时,调用_frr_mtx_unlock销毁这个锁。

    __attribute__((unused, cleanup(_frr_mtx_unlock)))在这里是一个整体,放在NAMECTR(_mtx_) 变量后面用于修饰该变量,pthread_mutex_t *NAMECTR(_mtx_) = _frr_mtx_lock(mutex)是一个函数调用。

     注意一个细节,临时变量(一级指针)再传给销毁函数时,传的是二级指针。

3:这个_once的定义、判断和赋值,没有实际的意义,就是给/*2*/中的临时变量声明周期的提供了一个的区块。让它在这个地方出在后再死去,因为加锁调用而出生,死去时它会自己销毁自己。

     frr_with_mutex宏实际控制的区块就是这个for循环的循环体,也就是这个锁的整个生命周期。不得不说,这里选中for而不是while的一个巧妙之处,可以自行控制锁的影响范围。


    综上,不得不说,这些个复杂的宏背后干了太多事,最大的意图就是自动干程序员可能会忘的一件事:unlock锁。同时还可以*控制锁影响的区块大小。学习啦。

 

    此外提一下最开始示例代码中的AWAKEN(m),后期学习用:

#define AWAKEN(m)                                                              \
	do {                                                                   \
		static unsigned char wakebyte = 0x01;                          \
		write(m->io_pipe[1], &wakebyte, 1);                            \
	} while (0);