手写Nginx内存池移植项目

注意点

1.所有需要传入内存池入口指针(pool)的地方都不用传入了,因为私有变量pool_存储的就是内存池入口指针

2.在C++中,void* 指针是一个通用指针类型,它可以指向任何类型的数据。然而,由于C++是一种类型安全的语言,直接将 void* 转换为其他类型的指针(或反之)而不进行显式转换(即“强转”)通常是不被允许的,或者至少是不被推荐的。

0.预编译文件

1
2
3
4
5
6
//pch.h
#pragma once
#ifndef PCH_H
#define PCH_H

#endif
1
2
//pch.cpp
#include"pch.h"

1.ngx_mem_pool.h 变量和接口函数定义

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
#pragma once
#include<stdlib.h>
#include<memory.h>
/*
移植nginx内存池的代码,用oop来实现
*/

//类型重定义
using u_char = unsigned char;
using ngx_uint_t = unsigned int;

//类型前置声明
struct ngx_pool_s;

//清理函数类型
typedef void (*ngx_pool_cleanup_pt)(void* data);
// 清理操作的类型定义,包括一个清理回调函数,传给回调函数的数据和下一个清理操作的地址
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 定义一个函数指针,保存清理回调函数
void* data; // 传递给回调函数的参数
ngx_pool_cleanup_s* next; // 指向下一个清理操作
};

//大块内存的头部信息
struct ngx_pool_large_s {
ngx_pool_large_s* next;//所有的大块内存分配被串在一条链表上
void* alloc;//保存分配出去的大块内存的起始地址
};

//分配小块内存的内存池的头部数据信息
struct ngx_pool_data_t {
u_char* last; // 小块内存池可分配内存开始位置
u_char* end; // 可分配内存末尾位置
ngx_pool_s* next; // 保存下一个小块内存池的地址
ngx_uint_t failed; // 记录当前内存池分配失败的次数
} ;

// nginx内存池头部信息和管理成员信息
struct ngx_pool_s {
ngx_pool_data_t d; // 存储的是当前小块内存池的使用情况
size_t max; // 小块内存分配的最大值 小块和大块内存的分界线
ngx_pool_s* current; // 小块内存池入口指针 指向第一个可以分配的小块内存池,每次分配都是从这个指针所指的内存池开始分配的
ngx_pool_large_s* large; // 大块内存的入口地址(大块也在一条链表上)
ngx_pool_cleanup_s* cleanup;
/*指向所有预置的清理函数handler回调函数的入口(串在一条链表上) 用户可以设置回调函数,
在内存池释放之前,可以对内存池上的数据进行一个释放操作。相当于析构函数的角色。
*/
};
//把d调整到邻近的a的倍数上
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
//小块内存分配考虑字节对齐时的单位
#define NGX_ALIGNMENT sizeof(unsigned long)
//把指针p调整到a邻近的倍数
#define ngx_align_ptr(p, a) \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
//buf缓冲区清0(初始化为0)
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)

const int ngx_pagesize = 4096;
/*能从内存池分配到的最大内存 4096-1=4095(4k) 就是32位操作系统一个页面的大小
这是nginx内存池区分大小块内存的一个分界线,比SGI STL内存池的128字节要大得多,在SGI STL中,只要比128字节大就去调用malloc而不是内存池
*/
const int NGX_MAX_ALLOC_FROM_POOL = ngx_pagesize - 1;
//默认的内存池的大小最大为 16k
const int NGX_DEFAULT_POOL_SIZE = 16 * 1024;
//小块内存池大小按照16字节进行对齐
const int NGX_POOL_ALIGNMENT = 16;
//小块内存池最小的大小 gnx_align是把数字调整到邻近的16的倍数上
const int NGX_MIN_POOL_SIZE =
ngx_align((sizeof(ngx_pool_s) + 2 * sizeof(ngx_pool_large_s)),
NGX_POOL_ALIGNMENT);

class ngx_mem_pool
{
public:
//创建指定size大小的内存池,但小块内存池不超过一个页面的大小
void* ngx_create_pool(size_t size);
//考虑内存字节对齐,从内存池申请size大小的内存
void* ngx_palloc(size_t size);
//和上面的函数一样,不考虑内存字节对齐
void* ngx_pnalloc(size_t size);
//调用的是ngx_palloc实现内存分配,但是会初始化0
void* ngx_pcalloc(size_t size);
//释放大块内存
void ngx_pfree(void* p);
//内存重置函数
void ngx_reset_pool();
//内存池的销毁函数
void ngx_destroy_pool();
//添加回调清理操作函数
ngx_pool_cleanup_s* ngx_pool_cleanup_add(size_t size);
private:
//指向ngx内存池的入口指针
ngx_pool_s* pool;
//小块内存分配
void* ngx_palloc_small(size_t size, ngx_uint_t align);
//分配新的小块内存池
void* ngx_palloc_block(size_t size);
//分配大块内存池
void* ngx_palloc_large(size_t size);
};

2.ngx_mem_pool.cpp 接口具体实现

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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include"pch.h"
#include"ngx_mem_pool.h"


//创建指定size大小的内存池,但小块内存池不超过一个页面的大小
void* ngx_mem_pool::ngx_create_pool(size_t size)
{
ngx_pool_s* p;
//根据用户指定的大小开辟内存 根据不同的平台调用不同的API 如果没有指定平台,那调用的就是malloc函数
p = (ngx_pool_s*)malloc(size);
//申请失败 退出
if (p == nullptr) {
return nullptr;
}
//last指向的是用户可以使用的内存空间的首地址(除了内存池的头结构体部分的其他内容) end指向的是这块的末尾地址 size是传入的要申请的大小 在图中表示为log末尾到内存池末尾这一块
p->d.last = (u_char*)p + sizeof(ngx_pool_s);
p->d.end = (u_char*)p + size;
//初始化
p->d.next = nullptr;
p->d.failed = 0;
//这个size是我们用户实际上可以使用的大小 申请的大小减去内存池记录信息所用的数据头的大小
size = size - sizeof(ngx_pool_s);
//如果比一个页面小,那就用size,否则就用一个页面的大小
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
//初始化
p->current = p;//指向本内存池的起始地址
p->large = nullptr;
p->cleanup = nullptr;

pool = p;//封装后对私有变量要赋值的

return p;
}


//考虑内存字节对齐,从内存池申请size大小的内存
void* ngx_mem_pool::ngx_palloc(size_t size)
{
if (size <= pool->max) {
return ngx_palloc_small(size, 1);
}
return ngx_palloc_large(size);
}
//和上面的函数一样,不考虑内存字节对齐
void* ngx_mem_pool::ngx_pnalloc(size_t size)
{
if (size <= pool->max) {
return ngx_palloc_small(size, 0);
}
return ngx_palloc_large(size);
}
//调用的是ngx_palloc实现内存分配,但是会初始化0
void* ngx_mem_pool::ngx_pcalloc(size_t size)
{
void* p;

p = ngx_palloc(size);
if (p) {
ngx_memzero(p, size);
}

return p;
}


//小块内存分配
void* ngx_mem_pool::ngx_palloc_small(size_t size, ngx_uint_t align)
{
u_char* m;
ngx_pool_s* p;

//p指向传入内存池的起始地址
p = pool->current;

do {
//指向可分配内存的起始地址
m = p->d.last;
//考虑内存对齐
if (align) {
//根据平台调整字节对齐,调整到4或8字节的倍数上去
m = ngx_align_ptr(m, NGX_ALIGNMENT);
}
//目前可分配内存(空闲内存)比要申请的内存要大 就可以分配 然后更新last即空闲内存起始地址
if ((size_t)(p->d.end - m) >= size) {
p->d.last = m + size;

return m;//m是已经分配好的内存空间的首地址 返回这个指针给用户
}

p = p->d.next;//这个内存池大小不够就去下一个,没有下一个内存池的话就会退出循环

} while (p);
//没有分配成功就会进入这个函数
return ngx_palloc_block(size);
}
//分配新的小块内存池
void* ngx_mem_pool::ngx_palloc_block(size_t size)
{
u_char* m;
size_t psize;
ngx_pool_s* p, * newpool;
//计算当前整个内存池的大小
psize = (size_t)(pool->d.end - (u_char*)pool);
//再开辟另一块大小一模一样的内存池
m =(u_char*)malloc(psize);
if (m == nullptr) {
return nullptr;
}
//记录内存池起始地址
newpool = (ngx_pool_s*)m;
/*初始化内存池结构体
从第二块内存池开始 只需要ngx_pool_data_t里面的四个指针来管理,从max到log全都不需要了,存在第一块内存池里面就行了,剩下的只要这四个必要的指针就行
*/
newpool->d.end = m + psize;
newpool->d.next = nullptr;//这是新创建的,就相当于链表最后一个结点,next域就是nullptr
newpool->d.failed = 0;

m += sizeof(ngx_pool_data_t);
//字节对齐 调整到4或8的倍数
m = ngx_align_ptr(m, NGX_ALIGNMENT);
newpool->d.last = m + size;//更新内存池的last指针,指向可用内存空间(空闲内存)的首地址

for (p = pool->current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
pool->current = p->d.next;
}
}

p->d.next = newpool;

return m;
}
//分配大块内存池
void* ngx_mem_pool::ngx_palloc_large(size_t size)
{
void* p;
ngx_uint_t n;
ngx_pool_large_s* large;
//直接调用malloc开辟内存
p = malloc(size);
if (p == nullptr) {
return nullptr;
}

n = 0;
//先看下面的pfree函数再看这个,遍历大块内存池链表,找到alloc是空的,把新分配的这块给安置到这里
for (large = pool->large; large; large = large->next) {
if (large->alloc == nullptr) {
large->alloc = p;
return p;
}
//找了三次还没找到空的,那就算了,太浪费时间了,直接在小块内存池里面把内存头结构体放进去
if (n++ > 3) {
break;
}
}
//把大块内存的内存头 上面那个结构体给放到小块内存的内存池里面去了
large = (ngx_pool_large_s*)ngx_palloc_small(sizeof(ngx_pool_large_s), 1);
//开辟失败直接把大的内释放掉
if (large == nullptr) {
free(p);
return nullptr;
}
//记录大块内存的起始地址
large->alloc = p;
large->next = pool->large;
pool->large = large;

return p;
}
//大块内存释放
void ngx_mem_pool::ngx_pfree(void* p)
{
ngx_pool_large_s* l;

for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
free(l->alloc);
l->alloc = NULL;
return ;
}
}
}


//内存重置函数
void ngx_mem_pool::ngx_reset_pool()
{
ngx_pool_s* p;
ngx_pool_large_s* l;
//遍历大块内存
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
/*遍历小块内存 这个处理的不太好,因为除了第一块结构体是完整的(从开始到log),
剩下的内存池结构体那块都是只有四个指针 这样释放内存相当于多释放了除开四个指针的多余部分。表现就是从第二块开始的内存池有一小部分不能使用,但是可以使用,不会报错。

for (p = pool; p; p = p->d.next) {
p->d.last = (u_char*)p + sizeof(ngx_pool_t);
p->d.failed = 0;
}*/

//修改后的版本
p = pool;
//处理第一个内存池
p->d.last = (u_char*)p + sizeof(ngx_pool_s);
p->d.failed = 0;

//从第二个开始循环到最后一个 只需要释放那四个指针,即ngx_pool_data_t就行
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char*)p + sizeof(ngx_pool_data_t);
p->d.failed = 0;
}

pool->current = pool;
pool->large = nullptr;
}
//内存池的销毁函数
void ngx_mem_pool::ngx_destroy_pool()
{
ngx_pool_s* p, * n;
ngx_pool_large_s* l;
ngx_pool_cleanup_s* c;
//1.回调函数不为空先调回调函数 这样就把外部资源释放掉了
for (c = pool->cleanup; c; c = c->next) {
if (c->handler) {
c->handler(c->data);
}
}
//2.大块
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
//3.小块
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
free(p);

if (n == nullptr) {
break;
}
}
}
//添加回调清理操作函数
ngx_pool_cleanup_s* ngx_mem_pool::ngx_pool_cleanup_add(size_t size)
{
ngx_pool_cleanup_s* c;
//把结构体存储在小块内存内存池
c = (ngx_pool_cleanup_s*)ngx_palloc(sizeof(ngx_pool_cleanup_s));
if (c == nullptr) {
return nullptr;
}
//如果预置的回调函数不需要参数,那就写0,如果有参数就写需要的内存大小,然后再开辟相应的内存,然后data指向它,如果不需要参数那就是nullptr
if (size) {
c->data = ngx_palloc(size);
if (c->data == nullptr) {
return nullptr;
}

}
else {
c->data = nullptr;
}
//暂时置为空,还没有具体的值
c->handler = nullptr;
//链表的连接动作,把新创建的cleanup连接到链表上(next指向的是下一个清理操作)
c->next = pool->cleanup;

pool->cleanup = c;
return c;
}


3.textNginxPool.cpp 测试文件

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
#include"pch.h"
#include"ngx_mem_pool.h"
#include<stdio.h>
#include<string.h>

//大块内存池里面放了两个指针将要指向外部资源
typedef struct Data stData;
struct Data
{
char* ptr;
FILE* pfile;
};
//两个回调函数 堆内存资源和文件资源
void func1(void* p1)
{
char* p = (char*)p1;
printf("free ptr mem!");
free(p);
}
void func2(FILE* pf1)
{
FILE *pf=(FILE*) pf1;
printf("close file!");
fclose(pf);
}
int main()
{
// 最大值设置为512 create_pool可以直接实现在mempool的构造函数
ngx_mem_pool mempool;

if (mempool.ngx_create_pool(512) == nullptr)
{
printf("ngx_create_pool fail...");
return -1;
}

void* p1 = mempool.ngx_palloc(128); // 从小块内存池分配的
if (p1 == nullptr)
{
printf("ngx_palloc 128 bytes fail...");
return -1;
}

stData* p2 =(stData*) mempool.ngx_palloc(512); // 从大块内存池分配的
if (p2 == nullptr)
{
printf("ngx_palloc 512 bytes fail...");
return -1;
}
p2->ptr = (char*)malloc(12);
strcpy(p2->ptr, "hello world");
p2->pfile = fopen("data.txt", "w");

ngx_pool_cleanup_s* c1 = mempool.ngx_pool_cleanup_add(sizeof(char*));
c1->handler = func1;
c1->data = p2->ptr;

ngx_pool_cleanup_s* c2 = mempool.ngx_pool_cleanup_add(sizeof(FILE*));
c2->handler = (ngx_pool_cleanup_pt)func2;
c2->data = p2->pfile;

//可以直接实现在mempool的析构函数
mempool.ngx_destroy_pool(); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存

return 0;
}

4.改进

1.可以将create_pool放在构造函数中,将destroy_pool放在析构函数中,进一步进行封装

2.回调函数部分可以用bind绑定器或者function函数对象来实现

笔者只实现了第一点,实现第二点时,老是报错(读取访问权限冲突。 std::_Func_class<void,void * __ptr64>::_Getimpl(…) 返回 0xFFFFFFFFFFFFFFFF。完了写好之后再更新此文章)

pool.h

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
//pool.h
#pragma once
#include<stdlib.h>
#include<memory.h>
#include<iostream>
using namespace std;
/*
移植nginx内存池的代码,用oop来实现
*/

//类型重定义
using u_char = unsigned char;
using ngx_uint_t = unsigned int;

//类型前置声明
struct ngx_pool_s;

//清理函数类型
typedef void (*ngx_pool_cleanup_pt)(void* data);
// 清理操作的类型定义,包括一个清理回调函数,传给回调函数的数据和下一个清理操作的地址
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 定义一个函数指针,保存清理回调函数
void* data; // 传递给回调函数的参数
ngx_pool_cleanup_s* next; // 指向下一个清理操作
};

//大块内存的头部信息
struct ngx_pool_large_s {
ngx_pool_large_s* next;//所有的大块内存分配被串在一条链表上
void* alloc;//保存分配出去的大块内存的起始地址
};

//分配小块内存的内存池的头部数据信息
struct ngx_pool_data_t {
u_char* last; // 小块内存池可分配内存开始位置
u_char* end; // 可分配内存末尾位置
ngx_pool_s* next; // 保存下一个小块内存池的地址
ngx_uint_t failed; // 记录当前内存池分配失败的次数
} ;

// nginx内存池头部信息和管理成员信息
struct ngx_pool_s {
ngx_pool_data_t d; // 存储的是当前小块内存池的使用情况
size_t max; // 小块内存分配的最大值 小块和大块内存的分界线
ngx_pool_s* current; // 小块内存池入口指针 指向第一个可以分配的小块内存池,每次分配都是从这个指针所指的内存池开始分配的
ngx_pool_large_s* large; // 大块内存的入口地址(大块也在一条链表上)
ngx_pool_cleanup_s* cleanup;
/*指向所有预置的清理函数handler回调函数的入口(串在一条链表上) 用户可以设置回调函数,
在内存池释放之前,可以对内存池上的数据进行一个释放操作。相当于析构函数的角色。
*/
};
//把d调整到邻近的a的倍数上
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
//小块内存分配考虑字节对齐时的单位
#define NGX_ALIGNMENT sizeof(unsigned long)
//把指针p调整到a邻近的倍数
#define ngx_align_ptr(p, a) \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
//buf缓冲区清0(初始化为0)
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)

const int ngx_pagesize = 4096;
/*能从内存池分配到的最大内存 4096-1=4095(4k) 就是32位操作系统一个页面的大小
这是nginx内存池区分大小块内存的一个分界线,比SGI STL内存池的128字节要大得多,在SGI STL中,只要比128字节大就去调用malloc而不是内存池
*/
const int NGX_MAX_ALLOC_FROM_POOL = ngx_pagesize - 1;
//默认的内存池的大小最大为 16k
const int NGX_DEFAULT_POOL_SIZE = 16 * 1024;
//小块内存池大小按照16字节进行对齐
const int NGX_POOL_ALIGNMENT = 16;
//小块内存池最小的大小 gnx_align是把数字调整到邻近的16的倍数上
const int NGX_MIN_POOL_SIZE =
ngx_align((sizeof(ngx_pool_s) + 2 * sizeof(ngx_pool_large_s)),
NGX_POOL_ALIGNMENT);

class ngx_mem_pool
{
public:
ngx_mem_pool(size_t size)
{

if (this->ngx_create_pool(size) == nullptr)
{
cout << "ngx_create_pool fail..." << endl;
exit(0);
}
}
//考虑内存字节对齐,从内存池申请size大小的内存
void* ngx_palloc(size_t size);
//和上面的函数一样,不考虑内存字节对齐
void* ngx_pnalloc(size_t size);
//调用的是ngx_palloc实现内存分配,但是会初始化0
void* ngx_pcalloc(size_t size);
//释放大块内存
void ngx_pfree(void* p);
//内存重置函数
void ngx_reset_pool();

//添加回调清理操作函数
ngx_pool_cleanup_s* ngx_pool_cleanup_add(size_t size);

~ngx_mem_pool()
{
this->ngx_destroy_pool();
}
private:
//指向ngx内存池的入口指针
ngx_pool_s* pool;
//小块内存分配
void* ngx_palloc_small(size_t size, ngx_uint_t align);
//分配新的小块内存池
void* ngx_palloc_block(size_t size);
//分配大块内存池
void* ngx_palloc_large(size_t size);
//创建指定size大小的内存池,但小块内存池不超过一个页面的大小
void* ngx_create_pool(size_t size);
//内存池的销毁函数
void ngx_destroy_pool();
};

pool.cpp

这个没改动

text.cpp

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
#include"pch.h"
#include"ngx_mem_pool.h"
#include<stdio.h>
#include<string.h>

//大块内存池里面放了两个指针将要指向外部资源
typedef struct Data stData;
struct Data
{
char* ptr;
FILE* pfile;
};
//两个回调函数 堆内存资源和文件资源
void func1(void* p1)
{
char* p = (char*)p1;
printf("free ptr mem!");
free(p);
}
void func2(FILE* pf1)
{
FILE* pf = (FILE*)pf1;
printf("close file!");
fclose(pf);
}
int main()
{
// 最大值设置为512 create_pool可以直接实现在mempool的构造函数
ngx_mem_pool mempool(512);

void* p1 = mempool.ngx_palloc(128); // 从小块内存池分配的
if (p1 == nullptr)
{
printf("ngx_palloc 128 bytes fail...");
return -1;
}

stData* p2 = (stData*)mempool.ngx_palloc(512); // 从大块内存池分配的
if (p2 == nullptr)
{
printf("ngx_palloc 512 bytes fail...");
return -1;
}
p2->ptr = (char*)malloc(12);
strcpy(p2->ptr, "hello world");
p2->pfile = fopen("data.txt", "w");

ngx_pool_cleanup_s* c1 = mempool.ngx_pool_cleanup_add(sizeof(char*));
c1->handler = func1;
c1->data = p2->ptr;

ngx_pool_cleanup_s* c2 = mempool.ngx_pool_cleanup_add(sizeof(FILE*));
c2->handler = (ngx_pool_cleanup_pt)func2;
c2->data = p2->pfile;

return 0;
}



bind和function的改进还没完成(笔者标注一下不要在意)