Nginx内存池移植为C++版本

介绍

这个项目是在阅读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内存都在同一个位置开始,产生了覆盖

猜你喜欢

转载自blog.csdn.net/qq_42120843/article/details/130726520