MAGIC_NO,好方法!
调试无锁线程池代码过程中,采用了一种很有意思的技术,和大家share一下。她的名字叫:MAGIC NUMBER。
假设我们有了一大块缓冲区(以下称buffer),say,1M,这一块缓冲区将用来满足很多小的顺序到来的内存请求,并且,其释放先后次序和申请次序一致。我们可以考虑采用环形队列来解决这个问题。为了记录分配出去的内存块(以下称buflet)的大小、状态等信息,需要对内存区进行管理,采用如下策略:
(1)每一小块内存头部划分出一小片来记录这块内存的大小、状态,剩余部分供给用户使用。
(2)对于新的请求,首先查看当前空闲池是否能够满足需求,如果不能满足,就往前查看,合并可回收的内存区到空闲池(适用于回环了一周的内存使用状况,首次分配不存在该问题),然后进行分配。分配时,新区域可能比用户需要的区域大,此时需要执行split操作,仅仅将用户需要的内存切割出去,剩余的放入空闲池。
(3) 用户归还内存时将内存块标记为free
如下图所示:
为了实现上述管理,我们需要编写较为复杂的代码(以上是简化模型,实际应用中有更复杂的需求),这里面有个很麻烦的问题就是:指针偏移!为了通过当前块计算出下一个块的首地址,通过 next_addr = current_addr + current_size计算即可,在复杂应用中,情况往往比较复杂,计算可能出错,此时得到的next_addr是个错误的值,用这个错误的next_addr去得到下一块的大小,显然也是错的,就这样,一步算错了,将一路错下去,无日无夜……
如何及时发现这种错误?不要说仔细地写代码,因为,在程序员心中,有个概念必须牢记:软件错误是无法避免的。在一块内存区上,任意位置都可以是一个buflet的起始,当我们得到一个错误的指针时,它表面上看也可以是一个buflet地址,实际上确实错误的。这个错误无法直接检测!这个问题麻烦了我三四个小时后,终于不堪忍受了,想出使用MAGIC_NO的方法:每生成一个buflet的时候,在其头结构中加入一个magic number。每次通过现有指针计算分配一个buflet时,我们就检查magic number域,如果magic number等于某个确定值MAGIC_NO,则表明指针偏移正确,否则报错,让我们知道指针又错了,反过来及时修改代码。
#define MAGIC_NO 0x4D544C /**< ascii for LTM */
typedef struct
{
int magic_no; //Magic Number, 0x4D544C
//缓冲区状态:RECEIVED,
//标记这个packet是否是有效packet
//所属快号
//packet大小,包括本结构和后面的缓冲区
char buf[]; //存储UDP包的缓冲区
}
dfs_api_res_pkt_t;
1 #include <stdio.h>
2
3 #define MAGIC_NO 0x4D544C /**< some ascii, ^.^ */
4 union
5 int
6 char
7 }x;
8 int
9
10 {
11 int
12 x.i = i;
13 // I will show you the magic!
14 printf("%s/n",x.buf);
15 return
16 }
~