不落辰

知不可乎骤得,托遗响于悲风

0%

轮子-nginx内存池移植代码

nginx 内存池移植

OOP移植内存池

导图

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
103
104
105
106
107
108
109
110
111
#include<cstring>
#include<cstdlib>

#ifndef nginx_memory_pool
#define nginx_memory_pool


using u_char = unsigned char;
using ngx_uint_t = unsigned int;
struct ngx_pool_t;

// 小内存池(Block)头信息
typedef struct {
u_char* last; // 可用内存起始
u_char* end; // 内存末尾
ngx_pool_t* next; // 小内存Block链表
ngx_uint_t failed; // 分配内存是否成功
} ngx_pool_data_t;

// 大内存池(Block)头信息
struct ngx_pool_large_t {
ngx_pool_large_t* next; // 大内存Block的头信息链表
void* alloc; // 指向申请的大内存Block
};

typedef void(*ngx_pool_cleanup_pt)(void* data); // 回调函数;负责清理外部资源
// 外部资源的头信息
struct ngx_pool_cleanup_t {
ngx_pool_cleanup_pt handler; // 处理外部资源的回调函数
void* data; // 传给handler的参数
ngx_pool_cleanup_t* next; // 外部资源头信息链表。
};


// 管理整个内存池的头信息
struct ngx_pool_t {
ngx_pool_data_t d; // 小内存Block的头信息
size_t max; // 大块内存和小块内存的分界线。p->max:一个小块Block块内最多能分配多大的内存。其大小受制于程序本身ngx_memalign开辟的大小,也受制于小内存定义的上限4095
ngx_pool_t* current; // 指向第一块提供小块内存分配的小块内存Block地址
ngx_pool_large_t* large; // 大内存Block头信息的链表入口
ngx_pool_cleanup_t* cleanup; // 外部资源的头信息链表
};

// 32位 4 64位 8
// 小块内存考虑字节对齐时的单位
const int NGX_ALIGNMENT = sizeof(unsigned long); /* platform word */

// 默认一个页面大小:4KB
const int ngx_pagesize = 4096; // 1024B = 1KB
// ngx小块内存block里可分配的最大空间。(也即不能超过一个页)
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;

// 将d上调至a的倍数
#define ngx_align(d,a) ( ((d)+(a-1)) & ~(a-1) )
// 把指针p调整到a的临近倍数
#define ngx_align_ptr(p, a) \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
#define ngx_memset(buf, c, n) (void) memset(buf, c, n)

// ngx小块内存池最小的size调整成 NGX_POOL_ALIGNMENT 的倍数
// 保证至少能有一个ngx_pool_t头信息的大小 如 sizeof(ngx_pool_t)【15】 + 2*8 = 31 调整至32
const int NGX_MIN_POOL_SIZE = ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), NGX_POOL_ALIGNMENT);



/*
* OOP 移植nginx内存池
*/
class ngx_mem_pool
{
public:
// 构造函数,创建内存池。
ngx_mem_pool(size_t size = NGX_DEFAULT_POOL_SIZE);
// 析构函数,释放内存池
~ngx_mem_pool();
// 考虑字节对齐,从内存池申请size大小的内存
void* ngx_palloc(size_t size);
// 不考虑字节对齐,从内存池申请size大小的内存
void* ngx_pnalloc(size_t size);
// 调用ngx_palloc,并初始化为0.
void* ngx_pcalloc(size_t size);
// 释放大块内存block。ngx不提供释放小块内存的接口。原因见博客
void ngx_pfree(void* p);
// 内存重置函数
void ngx_reset_pool();
// 添加回调清理操作函数
ngx_pool_cleanup_t* ngx_pool_cleanup_add(size_t size);
private:
// 指向nginx内存池的入口指针,一个内存池只有一个pool。即第一个创建的内存block里的ngx_pool_t。pool_的指向不会改变,始终是第一个,因为只有第一个有。会发生改变的是它指向的current,next之类的东西。
ngx_pool_t* pool_;
// 尝试从内存池中拿出size大小内存。内存池不够则从操作系统开辟。align=1意味着需要内存对齐
void* ngx_palloc_small(size_t size, ngx_uint_t align);
// 从操作系统开辟新的小块内存池。ngx_palloc_small调用ngx_palloc_block。ngx_palloc_block底层调用ngx_memalign。在unix平台下ngx_memalign就是ngx_alloc
void* ngx_palloc_block(size_t size);
// 从操作系统开辟大块内存,挂载到某个已有的大块头信息下。(或再从内存池申请一块用作大内存block头信息)
void* ngx_palloc_large(size_t size);
// 销毁内存池
void ngx_destroy_pool();
// 创建size大小的内存池 (每个小内存block的大小均为size)
ngx_pool_t* ngx_create_pool(size_t size);
// void* ngx_alloc(size_t size); // 从操统malloc大块内存。ngx_palloc_large调用ngx_alloc。ngx_alloc调用malloc
};



#endif

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#include"ngx_mem_pool.h"
#include <new>
#include<iostream>
// 创建size大小的内存池 (每个小内存block的大小均为size)
ngx_pool_t* ngx_mem_pool::ngx_create_pool(size_t size)
{
pool_ = static_cast<ngx_pool_t*>(malloc(size));
if (pool_ == nullptr) {
return nullptr;
}

pool_->d.last = (u_char*)pool_ + sizeof(ngx_pool_t);
pool_->d.end = (u_char*)pool_ + size;
pool_->d.next = nullptr;
pool_->d.failed = 0;

size = size - sizeof(ngx_pool_t);
pool_->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

pool_->current = pool_;
pool_->large = nullptr;
pool_->cleanup = nullptr;
return pool_;
}


// 考虑字节对齐,从内存池申请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);
}


// 尝试从内存池中拿出size大小内存。内存池不够则从操作系统开辟。align=1意味着需要内存对齐
void* ngx_mem_pool::ngx_palloc_small(size_t size, ngx_uint_t align)
{
u_char* m;
ngx_pool_t* p;
p = pool_->current;

do {
m = p->d.last;

if (align) {
m = static_cast<u_char*>(ngx_align_ptr(m, NGX_ALIGNMENT)); // ??
}

if ((size_t)(p->d.end - m) >= size) {
p->d.last = m + size;

return m;
}

p = p->d.next;

} while (p);

return ngx_palloc_block(size);
}


// 从操作系统malloc开辟新的小块内存池。ngx_palloc_small调用ngx_palloc_block。ngx_palloc_block底层调用ngx_memalign。在unix平台下ngx_memalign就是ngx_alloc。(就是对malloc的浅封装)
void* ngx_mem_pool :: ngx_palloc_block(size_t size)
{
u_char* m;
size_t psize;
ngx_pool_t* p, * new_m;

psize = (size_t)(pool_->d.end - (u_char*)pool_);

m = static_cast<u_char*>(malloc(psize)); // ngx_alloc底层就是malloc
if (m == nullptr) {
return nullptr;
}

new_m = (ngx_pool_t*)m;

new_m->d.end = m + psize;
new_m->d.next = NULL;
new_m->d.failed = 0;

m += sizeof(ngx_pool_data_t);
m = static_cast<u_char*>(ngx_align_ptr(m, NGX_ALIGNMENT));
new_m->d.last = m + size;

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

p->d.next = new_m;

return m;
}


// 从操作系统malloc开辟大块内存,挂载到某个已有的大块头信息下。(或再从内存池申请一块用作大内存block头信息)
void* ngx_mem_pool::ngx_palloc_large(size_t size)
{
void* p;
ngx_uint_t n;
ngx_pool_large_t* large;

p = malloc(size);
if (p == nullptr) {
return nullptr;
}

n = 0;

for (large = pool_->large; large; large = large->next) {
if (large->alloc == nullptr) {
large->alloc = p;
return p;
}

if (n++ > 3) {
break;
}
}

large = static_cast<ngx_pool_large_t*>(ngx_palloc_small(sizeof(ngx_pool_large_t), 1));
if (large == nullptr) {
free(p);
return nullptr;
}

large->alloc = p;
large->next = pool_->large;
pool_->large = large;

return p;
}



// 从操统malloc大块内存。ngx_palloc_large调用ngx_alloc。ngx_alloc调用malloc
// void* ngx_mem_pool::ngx_alloc(size_t size)
// {
// void* p = malloc(size);
// return p;
// }


// 释放大块内存block。ngx不提供释放小块内存的接口。原因见博客
void ngx_mem_pool::ngx_pfree(void* p)
{
ngx_pool_large_t* l;
for (l = pool_->large; l; l = l->next) {
if (p == l->alloc) {
free(l->alloc);
l->alloc = nullptr;
return ;
}
}
}




// 考虑字节对齐,从内存池申请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 = ngx_palloc(size);
if (p) { // 清0操作
ngx_memzero(p, size);
}
return p;
}


// 内存重置函数
void ngx_mem_pool::ngx_reset_pool()
{
// 自己加的:释放外部资源
for (ngx_pool_cleanup_t* c = pool_->cleanup; c; c = c->next)
{
if (c->handler&&c->data) {
c->handler(c->data);
c->handler = nullptr;
c->data = nullptr;
}
}

// 释放大块内存
for (ngx_pool_large_t* l = pool_->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}

// 重置小块内存
ngx_pool_t* p = pool_;
p->d.last = (u_char*)p + sizeof(ngx_pool_t);
p->d.failed = 0;
for (p = p->d.next; p; p = p->d.next)
{
p->d.last = (u_char*)p + sizeof(ngx_pool_data_t);
p->d.failed = 0;
}

//for (p = pool_; p; p = p->d.next) {
// p->d.last = (u_char*)p + sizeof(ngx_pool_t);
// p->d.failed = 0;
//}
// 重置pool_成员
pool_->current = pool_;
pool_->large = nullptr;
}


// 销毁内存池
void ngx_mem_pool::ngx_destroy_pool()
{
ngx_pool_t* p, * n;
ngx_pool_large_t* l;
ngx_pool_cleanup_t* c;

// 释放外部资源
for (c = pool_->cleanup; c; c = c->next) {
if (c->handler) {
c->handler(c->data);
}
}

// 释放大块内存
for (l = pool_->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}

// 释放小块内村
for (p = pool_, n = pool_->d.next; /* void */; p = n, n = n->d.next) {
free(p);
if (n == nullptr) {
break;
}
}
}

// 申请外部资源信息头、添加回调清理操作函数
ngx_pool_cleanup_t* ngx_mem_pool::ngx_pool_cleanup_add(size_t size)
{
ngx_pool_cleanup_t* c;

c = static_cast<ngx_pool_cleanup_t*>(ngx_palloc(sizeof(ngx_pool_cleanup_t)));
if (c == nullptr) {
return nullptr;
}

if (size) {
c->data = ngx_palloc(size);
if (c->data == nullptr) {
return nullptr;
}
}
else {
c->data = nullptr;
}

c->handler = nullptr;
c->next = pool_->cleanup;

pool_->cleanup = c;

return c;
}


ngx_mem_pool::ngx_mem_pool(size_t size)
{
pool_ = ngx_create_pool(size);
if (pool_ == nullptr) {
throw std::bad_alloc();
}
}

ngx_mem_pool::~ngx_mem_pool()
{
std::cout << "~ngx_mem_pool" << std::endl;
ngx_destroy_pool();
}

test_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

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include"ngx_mem_pool.h"
#include <new>

typedef struct Data stData;
struct Data
{
// 多个指针
char* ptr;
FILE* pfile;
char* ptr2;
};


typedef struct yData yData;
struct yData
{
char* ptr;
int x;
int y;
};



void self_handler(void* p)
{
printf("self_handler\n");
stData* q = (stData*)p;
printf("free ptr mem!\n");
free(q->ptr);
printf("free ptr mem!\n");
free(q->ptr2);
printf("close file!\n");
fclose(q->pfile);
}


void self_handler_02(void* p)
{
char* q = (char*)p;
printf("self_handler_02\n");
printf("free ptr mem!\n");
free(q);
}



int main()
{
// 1. ngx_create_pool 造内存池
// 第一块内存Block。里面有完整的ngx_pool_t
// ngx_pool_t::max = min(512 - sizeof(ngx_pool_t) , 4095)
ngx_mem_pool mem_pool(512);
// 2. 小块内存以及外部资源
// 向内存池申请小块内存
stData* p1 = static_cast<stData*>(mem_pool.ngx_palloc(sizeof(yData))); // 从小块内存池分配的
if (p1 == nullptr)
{
printf("ngx_palloc %d bytes fail\n", sizeof(yData));
return -1;
}
// 小块内存保存的指针管理的外部资源
p1->ptr = static_cast<char*>(malloc(12));
strcpy(p1->ptr, "hello world");
p1->pfile = fopen("data.txt", "w");
p1->ptr2 = static_cast<char*>(malloc(12));
strcpy(p1->ptr2, "goodbye world");
// 预置回调函数用于释放外部资源
ngx_pool_cleanup_t* c1 = mem_pool.ngx_pool_cleanup_add(sizeof(yData)); // 开辟内存,用于handler传参
c1->handler = self_handler;
memcpy(c1->data, p1, sizeof(yData)); // 用户只负责拷贝!!将外部资源拷贝一下到c1->data。将p1指针指向的内容逐字节拷贝到c->data指向的内存


// 3. 大块内存以及外部资源
// 向内存池申请大块内存
yData* p2 = static_cast<yData*>(mem_pool.ngx_palloc(512)); // 从大块内存池分配的
if (p2 == nullptr)
{
printf("ngx_palloc 512 bytes fail...");
return -1;
}
// 外部资源
p2->ptr = static_cast<char*>(malloc(12));
strcpy(p2->ptr, "hello world");

// 预置回调函数用于释放外部资源
ngx_pool_cleanup_t* c2 = mem_pool.ngx_pool_cleanup_add(0); // 不开辟内存,直接让c->data指向要释放的内存
c2->handler = self_handler_02;
c2->data = p2->ptr;

// 重置内存池
// mem_pool.ngx_reset_pool();
// std::cout << "reset over" << std::endl;

// 释放内存池
// ~mem_pool() 调用mem_pool.ngx_destroy_pool(); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存

return 0;
}

运行

  • 环境 ubuntu / VS均可
    1
    2
    3
    4
    5
    6
    7
    8
    shc@shc-virtual-machine:~/code/nginx/my_nginx$ ./test_mem_pool.out 
    ~ngx_mem_pool
    self_handler_02
    free ptr mem!
    self_handler
    free ptr mem!
    free ptr mem!
    close file!

Bug

  • 出现heap corruption detected
    • 测试代码问题:添加外部资源时的大小不对。已解决

性能测试