您的位置:首页 > 运维架构 > 反向代理

[源码]SQUID的简单内存调试方法

2016-04-09 15:01 489 查看
一、简介:

        SQUID自身使用了一种简单的内存调试方式,可以在程序运行的时候对内存的使用情况进行监控调试(指开发层面的监控,如泄漏,内存合法性,不是运维层面的内存监控)。本文简单介绍其内部实现。不过就其内存泄露检查部分,仍有部分疑问。。

二、主要数据结构:

        xmalloc开辟了几个简单的哈希桶,用于记录每次分配的内存的大小,分配的时机(所在函数,所在函数所在行号,所在文件名),用分配的指针进行一定的移位作为key。

static void *(*malloc_ptrs)[DBG_ARRY_SZ];
static int malloc_size[DBG_ARRY_BKTS][DBG_ARRY_SZ];
static char *malloc_file[DBG_ARRY_BKTS][DBG_ARRY_SZ];
static short malloc_line[DBG_ARRY_BKTS][DBG_ARRY_SZ];
static int malloc_count[DBG_ARRY_BKTS][DBG_ARRY_SZ];


       同时有几个临时变量,用来记录当前函数名、文件名、行号。

char *xmalloc_file = "";
int xmalloc_line = 0;
char *xmalloc_func = "";


三、主要流程:
3.1 分配与释放:

        内存分配时,通过宏将函数名、文件名、行号赋值给全局变量,后续的记录都依赖于这个全局变量(其实还可以通过传参的方式,如SQUID中对cbdata_debug的管理那样)。

#define xmalloc(size) (xmalloc_func="xmalloc",xmalloc_line=__LINE__,xmalloc_file=__FILE__,xmalloc(size))


        从底层获取完内存后,会对分配的内存和大小进行检查。根据指针的23位、45位、67位的值的和取两位作为key(0x12345670则(23+45+67) & 0xff)值找到所在的桶,在该桶内对所有已有记录的指针进行判断,判断与当前内存是否有重叠。

B = DBG_HASH_BUCKET(p);
for (I = 0; I < DBG_ARRY_SZ; I++) {
if (!(P = malloc_ptrs[B][I]))
continue;
/* 获取P的范围.  */
Q = P + malloc_size[B][I];
if (P <= p && p < Q) {
do_something();
}
}

        然后在桶中选择一个位置,把当前分配的内存的信息记录在案。

for (I = 0; I < DBG_ARRY_SZ; I++) {
if (malloc_ptrs[B][I])
continue;
malloc_ptrs[B][I] = p;
malloc_size[B][I] = (int) sz;
malloc_file[B][I] = xmalloc_file;
malloc_line[B][I] = xmalloc_line;
malloc_count[B][I] = xmalloc_count;
break;
}

        在函数xmalloc_show_trace中,会把相应的分配进行统计,统计总共分配的内存大小和总共分配的内存次数。malloc时候sign为1,free时候sign为-1(realloc的时候先释放后分配)。

sz = xmallocblksize(p) * sign;
xmalloc_total += sz;
xmalloc_count += sign > 0;


        内存释放时候则相反,直接把桶中对应的元素删除,相应位置置位。

3.2 内存泄露检查:

        这一部分有较大疑问,在打开相应选项以后,使用xmalloc_find_leaks会导致程序core。对其其中几个细节有部分疑问。

        先简单介绍下内存泄露代码的理解。

        内存泄露的检查调用点在SQUID shutdown的末尾,可以对本次服务运行的整个过程进行一个分析。

        xmalloc_find_leaks是内存泄露检查的入口,这个函数由两个部分组成。

xmalloc_scan_region(&_etext, (void *) sbrk(0) - (void *) &_etext, 0);

for (B = 0; B < DBG_ARRY_BKTS; B++) {
for (I = 0; I < DBG_ARRY_SZ; I++) {
if (malloc_ptrs[B][I] && malloc_refs[B][I] == 0) {
/* Found a leak... */
fprintf(stderr, "Leak found: %p", malloc_ptrs[B][I]);
fprintf(stderr, " %s", malloc_file[B][I]);
fprintf(stderr, ":%d", malloc_line[B][I]);
fprintf(stderr, " size %d", malloc_size[B][I]);
fprintf(stderr, " allocation %d\n", malloc_count[B][I]);
leak_sum += malloc_size[B][I];
}
}
}


        第一部分是xmalloc_scan_region,起始地址为&_etext,即代码段的结尾,扫描的范围为&_etext到sbrk(0),即整个代码段到数据段的范围,但是注意,内存分配的时候是有两种分配方式,一种是brk分配,这种分配方式会扩大数据段,另一种分配方式是直接映射,在数据段之上,栈段之下,这一部分应该是在sbrk(0)之外,因此个人理解,这次的扫描扫描的是整个brk分配的小内存。

while (ptr <= end) {
<span style="white-space:pre">	</span>void *p = *(void **) ptr;
if (p && p != start) {
B = DBG_HASH_BUCKET(p);
for (I = 0; I < DBG_ARRY_SZ; I++) {
if (malloc_ptrs[B][I] == p) {
if (!malloc_refs[B][I]++) {
/* A new reference */
fprintf(stderr, "%*s%p %s:%d size %d allocation %d\n",
<span style="white-space:pre">	</span>depth, "",
<span style="white-space:pre">	</span>		malloc_ptrs[B][I], malloc_file[B][I],
malloc_line[B][I], malloc_size[B][I],
malloc_count[B][I]);
sum += malloc_size[B][I];
xmalloc_scan_region(malloc_ptrs[B][I], malloc_size[B][I], depth + 1);
if (depth == 0) {
if (sum != malloc_size[B][I])
fprintf(stderr, "=== %d bytes\n", sum);
<span style="white-space:pre">	</span>sum = 0;
}
}
}
}
}
ptr += XMALLOC_LEAK_ALIGN;
}
        这部分的遍历有些不理解,水平有限,还请懂的人指教:

        1. void *p = *(void **) ptr; 我理解应该是搜索整个数据段的所有地址,查看这个地址是否在桶中有记录,如果有记录该点是泄露。但是这个取ptr地址中的内容不太明白是为什么。

        2. 其实从text段结束并不是马上就是数据段,直接取地址会导致有不少数据是无法访问的。

        3. 每次搜索到就标记,但是没有统计到函数find_leak中的leak_sum中,是否数据段中的内容不计算在泄露中?

        4. 其实直接判断桶中仍然有记录的值,就是malloc和free不成对出现的指针,就已经是泄露的内容,为什么用这么复杂的方式来统计?

        5. 偏移大小4,好像和实际地址对齐不太一致?

        第二部分是检查没有扫描到的,但是没有正常释放的内存,个人理解这次遍历这个桶,是为了找出因为mmap而泄露的内存。

4. 总结:

        虽然对内存泄漏检查仍有疑惑,但是对于其对内存调试的方法是值得学习的,在SQUID中的cbdata管理采用的也是类似方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  squid 源码 内存