介绍
这个项目是在阅读nginx内存池源码后,将其代码重新用C++面向对象的方式实现,大体与源码相同,旨在加深对nginx内存池的理解。
运行环境
-
VS2019
-
win10
内存池类成员声明
// ngx_mem_pool.h
#pragma once
#include <string.h>
using u_char = unsigned char;
using ngx_uint_t = unsigned int;
struct ngx_pool_s;
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
#define NGX_ALIGNMENT sizeof(unsigned long)
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
#define ngx_align_ptr(p, a) \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
// 移植 nginx内存池代码,用C++ oop来实现
// _s代表struct, _t代表typedef之后的类型
// 清理资源的类型,每个对象绑定一个函数
typedef void (*ngx_pool_cleanup_pt)(void* data);
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 自定义的资源释放函数
void* data; // handler的参数
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;
};
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;
};
// 从池中可获得的最大内存数为4095,即一个页面的大小
const int ngx_pagesize = 4096;
// 小块内存池可分配的最大空间
const int NGX_MAX_ALLOC_FROM_POOL = ngx_pagesize - 1;
// 表示一个默认内存池开辟的大小
const int NGX_DEFAULT_POOL_SIZE = 16 * 1024; // 16K
// 按16B对齐
const int NGX_POOL_ALIGNMENT = 16;
// ngx_align就是和SGI STL二级空间配置器的 _S_round_up函数相同,对齐到NGX_POOL_ALIGNMENT
// ngx小块内存池的最小size调整为 NGX_POOL_ALIGNMENT 的倍数
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:
void* ngx_create_pool(size_t size);
// 内存分配,支持内存对齐
void* ngx_palloc(size_t size);
// 内存分配,不支持内存对齐
void* ngx_pnalloc(size_t size);
// 内存分配,支持内存初始化为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_pool_s* pool; // 管理内存池的指针
// 小块内存分配
inline void* ngx_palloc_small(size_t size, ngx_uint_t align);
// 大块内存分配
void* ngx_palloc_large(size_t size);
// 分配的新的小块内存池
void* ngx_palloc_block(size_t size);
};
成员函数的定义实现
// ngx_mem_pool.cpp
#include "ngx_mem_pool.h"
#include <stdlib.h>
void* ngx_mem_pool::ngx_create_pool(size_t size)
{
ngx_pool_s* p;
p = (ngx_pool_s*)malloc(size);
if (p == nullptr)
{
return nullptr;
}
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 - sizeof(ngx_pool_s); // 内存池实际的可分配内存空间
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;
}
inline void* ngx_mem_pool::ngx_palloc_small(size_t size, ngx_uint_t align)
{
u_char* m;
ngx_pool_s* p;
p = pool->current;
do {
m = (u_char*)p->d.last;
// m = (u_char*)p->current;
if (align)
{
m = 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);
}
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 == NULL) {
return NULL;
}
newpool = (ngx_pool_s*)m;
newpool->d.end = m + psize;
newpool->d.next = NULL;
newpool->d.failed = 0;
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
newpool->d.last = m + size;
for (p = pool->current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) { // 分配内存失败次数大于4就将current设置为下一个内存池块
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;
p = malloc(size); // malloc,实际分配的内存空间
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 = (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;
// 查找 alloc 与 p相同的内存将其释放掉
for (l = pool->large; l; l = l->next)
{
if (p == l->alloc)
{
free(l->alloc);
l->alloc = nullptr;
return;
}
}
}
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);
}
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_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);
}
}
// 特殊处理第一块内存池
p = pool;
p->d.last = (u_char*)p + sizeof(ngx_pool_s);
p->d.failed = 0;
// 第二个到结尾内存池没有多余的头部信息,如current、cleanup
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;
}
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;
// 释放外部资源,通过调用自己实现的释放资源函数
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; ; p = n, n = n->d.next)
{
free(p);
if (n == nullptr)
{
break;
}
}
}
// size表示清理函数参数的大小
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;
}
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;
}
测试
#include "ngx_mem_pool.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct Data stData;
struct Data
{
char* ptr;
FILE* pfile;
};
void func1(void* p)
{
char* pc = (char*)p;
printf("free ptr mem!");
free(p);
}
void func2(void* p)
{
FILE* pf = (FILE*)p;
printf("close file!");
fclose(pf);
}
int main()
{
// 512 - sizeof(ngx_pool_t) - 4095 => max
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 = func2;
c2->data = p2->pfile;
mempool.ngx_destroy_pool(); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存
return 0;
}
执行结果
遇到的问题
背景
死循环执行自定义内存清理函数 ngx_pool_cleanup_s::handler
分析:
发现设置的两个清理对象的地址相同
可能是分配对象空间时没有在新位置吗, 猜测可能是ngx_palloc
的问题,跟踪之后发现在 ngx_palloc_small
函数中内存池起始可用空间设置为p->current
m = (u_char*)p->d.last; // 正确代码
// m = (u_char*)p->current; // 错误的代码
导致了每次分配清理操作对象ngx_pool_cleanup_s
内存都在同一个位置开始,产生了覆盖