2.5 加桩调试
如果我们对代码做过单元测试,那么肯定知道加桩的概念,简单点说就是为了让一个模块执行起来,额外添加的一些支撑代码。比如,我要简单测试一个实现某种排序算法的子函数的功能是否正常,那么我也许需要写一个main()函数,设置一个数组,提供一些乱序的数据,然后利用这些数据调用排序子函数(假设它提供的接口就是对数组的排序),然后printf打印排序后的结果,看是否排序正常,所有写的这些额外代码(main()函数、数组、printf打印)就是桩代码。
上面提到的这种用于单元测试的方法,同样也可以用来深度调试Nginx内部逻辑,而且Nginx很多的基础实现(比如slab机制、红黑树、chain链、array数组等)都比较独立,要调试它们只需提供少量的桩代码即可。
以Nginx的slab机制为例,我们通过下面所提供的这些桩代码即可调试该功能的具体实现。Nginx 的 slab 机制用于对多进程共享内存的管理,不过单进程也是一样的执行逻辑,除了加/解锁直通以外(即加锁时必定成功),所以我们采取最简单的办法,直接在 Nginx 本身的main()函数内插入我们的桩代码。当然,必须根据具体情况把桩代码放在合适的调用位置,比如这里的slab机制就依赖一些全局变量(像ngx_pagesize等),所以需要把桩代码的调用位置放在这些全局变量的初始化之后。
197: 代码片段2.5-1,文件名: nginx.c
198: void ngx_slab_test()
199: {
200: ngx_shm_t shm;
201: ngx_slab_pool_t *sp;
202: u_char *file;
203: void *one_page;
204: void *two_page;
205:
206: ngx_memzero(&shm, sizeof(shm));
207: shm.size = 4 * 1024 * 1024;
208: if (ngx_shm_alloc(&shm) != NGX_OK) {
209: goto failed;
210: }
211:
212: sp = (ngx_slab_pool_t *) shm.addr;
213: sp->end = shm.addr + shm.size;
214: sp->min_shift = 3;
215: sp->addr = shm.addr;
216:
217: #if (NGX_HAVE_ATOMIC_OPS)
218: file = NULL;
219: #else
220: #error must support NGX_HAVE_ATOMIC_OPS.
221: #endif
222: if (ngx_shmtx_create(&sp->mutex, &sp->lock, file) != NGX_OK) {
223: goto failed;
224: }
225:
226: ngx_slab_init(sp);
227:
228: one_page = ngx_slab_alloc(sp, ngx_pagesize);
229: two_page = ngx_slab_alloc(sp, 2 * ngx_pagesize);
230:
231: ngx_slab_free(sp, one_page);
232: ngx_slab_free(sp, two_page);
233:
234: ngx_shm_free(&shm);
235:
236: exit(0);
237: failed:
238: printf("failed.\n");
239: exit(-1);
240: }
241: …
353: if (ngx_os_init(log) != NGX_OK) {
354: return 1;
355: }
356:
357: ngx_slab_test();
358: …
上面是修改之后的nginx.c源文件,直接make后生成新的Nginx,不过这个可执行文件不再是一个Web服务器,而是一个简单的调试slab机制的辅助程序。可以看到,程序在进入main()函数后先做一些初始化工作,然后通过ngx_slab_test()函数调入到桩代码内执行调试逻辑,完成既定目标后便直接exit()退出整个程序。
正常运行时,Nginx本身对内存的申请与释放是不可控的,所以直接去调试Nginx内存管理的slab机制的相关代码逻辑非常困难,利用这种加桩的办法,ngx_slab_alloc()申请内存和ngx_slab_free()释放内存都能精确控制,对每一次内存的申请或释放后,slab机制的内部结构发生了怎样的变化都能准确地掌握,对其相关逻辑的理解也就没有那么困难了。