本文主要总结资源相关的编程模式,以提高大家编程质量,减少遗漏释放资源的可能。
资源
这里的资源是一个宽泛的概念。凡是需要成对操作的,都可以视为资源。比如动态内存、文件、锁、中断,等等。我们以函数为单位进行考察,编程中用到的资源大致分为三类,一类是本函数申请,无论本函数成功失败都要释放,例如不保存的动态内存、文件、锁、中断等等;第二类是本函数申请,无论本函数成功失败都不释放,例如发消息的内存;第三类是本函数申请,只有本函数失败才要释放,例如申请了表项资源,后续操作失败才需要回退。第二类从释放函数的角度,也可以看成本函数不申请,无论本函数成功失败都要释放。
遗漏释放资源,是编程中经常出现的严重问题。其根本原因是单一函数的结构过于复杂、分支过多,在多个分支中需要考虑资源的释放,非常容易遗漏,尤其是在后期维护修改代码时。所以本文总结了两大类方法用以改善代码质量。
方法一:好的设计
根本解决这类问题的方法在于降低函数的复杂度,所以对于新开发的特性在编码前设计时遵从以下原则:
资源与业务分离原则
即通过把资源申请和释放之间的业务操作代码封装成函数的方法,将申请、释放资源的函数和使用资源的业务函数分离,使得业务函数中完全不必考虑资源释放的问题。如下图所示:

单一资源原则
当一个函数中用到多个资源的时候,通过封装子函数的方法,使得一个函数中只出现一个资源,从而降低复杂度,减少出错的可能。如下图所示:

如果多个资源是并列关系,还可以把多个资源封装成一个大资源,从而在管理时视为一个资源。如下图所示:

唯一释放原则
每个函数中只能有一处释放资源的代码。
以多操作失败回退为例,通过函数封装使得每个函数中最多只有两步操作,因而只有一处失败回退的代码,从而避免遗漏。如下图所示:

释放规则统一
申请资源的函数,对资源释放的规则要统一。当本函数退出时,无论处理成功还是失败,对申请的资源要么都由自己释放,要么不释放。不要出现成功不释放失败才释放的情况。对于发包流程中的内存申请所在的函数,建议设计为成功失败都不释放。如下图所示:

方法二:好的编码
然而在实际 项目中,往往是对大量结构复杂的老代码的移植修改。在不允许遵照以上原则重写代码的场合,采用以下编程模式可以尽量减少遗漏释放资源。
1、函数只能有一个出口,使用宏DRV_SAFE_OUT安全退出,不得直接return。也可以直接用在DRV_SAFE_OUT基础上封装好的宏DRV_IF_ERR_SAFE_OUT或DRV_IF_ERR_ASSERT_SAFE_OUT(用于需要打印断言的场合)
2、函数末尾必须有名为“SAFE_OUT”的goto标签,标签后的代码对本函数所有需要释放或回退的资源集中处理。
出口宏的封装
Talk is cheap. Show you the code:
对于有返回值的函数,宏封装如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/* 安全退出当前函数,函数返回值变量的命名自定,适用于功能性返回值 */
/* 如果errCondition成立则打印信息并安全退出当前函数,函数返回值变量的命名自定,适用于功能性返回值 */
/* 如果errCondition成立则打印信息和断言并安全退出当前函数,函数返回值变量的命名自定,适用于功能性返回值 */
对于无返回值的函数,宏封装如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/* 安全退出当前函数,函数返回值变量的命名自定,适用于功能性返回值 */
/* 如果errCondition成立则打印信息并安全退出当前函数,适用于无返回值场合 */
/* 如果errCondition成立则打印信息和断言并安全退出当前函数,适用于无返回值场合 */
内存资源的释放
带返回值的函数释放
函数有返回值,无打印输出信息,如下图:

函数有返回值,有打印输出信息,如下图:

不带返回值的函数释放
函数无返回值,无打印输出信息,如下图:

函数无返回值,有打印输出信息,如下图:

锁和信号量的释放
实现分析
此处也是该处总结的重点。为了统一释放锁和信号量,需要考虑如下几个问题:
1、一个函数里可能用到不止一个锁或者信号量,如何做到在函数出口全部都能释放呢?
2、锁和信号量的种类很多,函数出口释放的时候如何知道你用哪个接口去释放呢?
首先,要想释放一个锁(或信号量),必须要知道锁(或信号量)的地址以及解锁(释放信号量)函数。另外,对于spin_lock_irqsave(lock, flags),获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中断。此时还需要一个存储寄存器值的变量。所以我们可以定义一个结构体:
1 |
|
那么接下来,我们对所有锁(或信号量)的释放函数按照DRV_LOCK_PF的函数原型进行封装,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55/* 按照DRV_LOCK_PF原型对锁释放函数进行封装 */
VOID DRV_SPIN_Unlock(const VOID *pLock, ULONG ulData)
{
(VOID)ulData;
spin_unlock((spinlock_t *)pLock);
return;
}
VOID DRV_WRITE_Unlock(const VOID *pLock, ULONG ulData)
{
(VOID)ulData;
write_unlock((rwlock_t *)pLock);
return;
}
VOID DRV_READ_Unlock(const VOID *pLock, ULONG ulData)
{
(VOID)ulData;
read_unlock((rwlock_t *)pLock);
return;
}
VOID DRV_READ_UnlockBh(const VOID *pLock, ULONG ulData)
{
(VOID)ulData;
read_unlock_bh((rwlock_t *)pLock);
return;
}
VOID DRV_WRITE_UnlockBh(const VOID *pLock, ULONG ulData)
{
(VOID)ulData;
write_unlock_bh((rwlock_t *)pLock);
return;
}
VOID DRV_SPIN_UnlockIrqRestore(const VOID *pLock, ULONG ulData)
{
spin_unlock_irqrestore((spinlock_t *)pLock, ulData);
return;
}
VOID DRV_SPIN_UnlockBh(const VOID *pLock, ULONG ulData)
{
(VOID)ulData;
spin_unlock_bh((spinlock_t *)pLock);
return;
}
VOID DRV_Up(const VOID *pLock, ULONG ulData)
{
(VOID)ulData;
up((struct semaphore *)pSem);
return;
}
接下来,就是确认函数使用锁(或信号量)的数量,为每个锁分配一个DRV_LOCK_S结构,用以释放。
在函数内部实现定义变量时调用,如下:1
2
3
4/* 声明本函数用到的锁的数量,包括各种锁和信号量 */
最后,我们就是要将锁(或信号量)和DRV_LOCK_S关联起来,加锁时入栈,解锁时出栈:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156/* 已获取的锁(信号量)入栈 */
/* 指定的锁(信号量)出栈,如果不是栈顶要搬移 */
static inline VOID DRV_LOCK_Pop(IN const VOID *pLock, INOUT DRV_LOCK_S *pstStack, INOUT UINT *puiCnt)
{
UINT uiTop = *puiCnt - 1;
if (pLock != pstStack[uiTop].pLock)
{
ULONG ulLoop;
for (ulLoop = 0; ulLoop < uiTop; ulLoop++)
{
if (pLock == pstStack[uiLoop].pLock)
{
memcpy(&(pstStack[uiLoop]), &(pstStack[ulLoop + 1]), (*puiCnt - (ulLoop + 1)) * sizeof(DRV_LOCK_S));
break;
}
}
DBGASSERT(ulLoop < uiTop);
}
(*puiCnt)--;
return;
}
/* 和spin_lock用法一样 */
/* 和spin_unlock用法一样 */
/* 和spin_lock_bh用法一样 */
DRV_LOCK_PUSH(astLocks_, uiLockCnt_, pLock, DRV_SPIN_UnlockBh, 0); \
}
/* 和spin_unlock_bh用法一样 */
/* 和spin_lock_irqsave用法一样 */
DRV_LOCK_PUSH(astLocks_, uiLockCnt_, pLock, DRV_SPIN_UnlockIrqRestore, flag); \
}
/* 和read_lock用法一样 */
/* 和read_unlock用法一样 */
/* 和read_lock_bh用法一样 */
/* 和read_unlock_bh用法一样 */
/* 和write_lock用法一样 */
/* 和write_unlock用法一样 */
/* 和write_lock_bh用法一样 */
/* 和write_unlock_bh用法一样 */
/* 和down用法一样 */
/* 和up用法一样 */
/* 和down_timeout用法不同,返回值作为出参 */
if (0 == ret) \
{ \
DRV_LOCK_PUSH(astLocks_, uiLockCnt_, pSem, DRV_Up, 0); \
}
/* 释放本函数所有已获取的锁(信号量),后进先出 */
{ \
uiTop = (uiLockCnt_ - 1) - uiLoop; \
astLocks_[uiTop].pfUnlock(astLocks_[uiTop].pLock, astLocks_[uiTop].ulData); \
} \
uiLockCnt_ = 0; \
}