NGINX内存池C代码移植项目

nginx是一个高性能的HTTP和反向代理web服务器,是由伊戈尔·塞索耶夫为俄罗斯网站开发的,是一个开源的软件,底层由C语言编写完成,但经过提炼后,是可以将其按照面向对象的思想重新组织一下的,也相当于一个小项目吧。
涉及思路是将源文件中的几个主要函数实现成为类的成员方法,成员变量则只设置一个内存池对象即可。因为nginx内存池的结构比较复杂,涉及很多网络相关的东西,我们在本地编译器上对这些变量做了简化。

一、主要结构

源码中的一些结构体我们依旧使用结构体来实现,首先减免一些不必要的变量,防止在本地编译过程中增加不必要的麻烦。

//清理函数(回调函数)的类型
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;//所有的clean_up清理操作都被穿在链表上
};

//大块内存的头部信息
struct ngx_pool_large_s
{
    
    
	ngx_pool_large_s     *next;//大块内存串起来
	void                 *alloc;//保存分配出去的大块内存的起始地址
};

//分配小块内存的数据头
typedef 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;//清理占用的外部资源
};

二、全局变量的定义

因为在源码中使用了大量的宏定义来实现数值的替换,而在C++中我们则使用const关键字来替换源码中的宏,源码中使用了typedef关键字来进行类型重命名,在C++中我们使用C++11的using关键字来实现类型重定义,这是在代码上做的改变。

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

//把buff缓冲区清零
#define ngx_memzero(buf, n)       (void) memset(buf, 0, n)

//把数值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))

//默认物理页面的大小
const int ngx_pagesize = 4096;

//需要使用NGINX内存池分配的大小,超过则不统一管理
const int NGX_MAX_ALLOC_FROM_POOL = ngx_pagesize - 1;

//nginx内存池的默认大小
const int NGX_DEFAULT_POOL_SIZE = 16 * 1024;

//内存对齐的量
const int  NGX_POOL_ALIGNMENT = 16;

//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:
	//创建指定size大小的内存池,小块内存池不超过一个页面大小
	void* ngx_create_pool(size_t size);
	//考虑内存字节对齐,从内存池申请size大小的内存
	void *ngx_palloc(size_t size);
	//不考虑内存字节对齐
	void *ngx_pnalloc(size_t size);
	//调用pnalloc,并对内存块初始化为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;//指向nginx内存池的入口指针
	//小块内存分配
	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);
};

由于本地编译不像源码的实际应用那么全面,所以在函数的返回值和参数中也做了简化,并按调用方式分了私有成员方法和用户可以调用的成员方法,由于成员函数的特性,所以在所有函数中都去掉了pool这个参数。

四、成员方法的类外定义

由于声明写在.h文件中,方法的实现写在了.cpp文件中,所以必须要将他们的声明和实现分开。

#include"ngx_mem_pool.h"
#include<cstdlib>
#include<memory>

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);//将last指针移动到申请的内存下方
	p->d.end = (u_char *)p + size;//指向内存池的末尾
	p->d.next = nullptr;//后继没有内存,指向空
	p->d.failed = 0;//申请成功,初始化为0

	size = size - sizeof(ngx_pool_s);//计算出可用空间
	p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
	//对max的值对应实际来做出改变
	p->current = p;//当前可分配的内存块
	p->large = nullptr;
	p->cleanup = nullptr;

	pool = p;
	return pool;
}

//考虑内存对齐,从内存池申请size大小的内存
void* ngx_mem_pool::ngx_palloc(size_t size)
{
    
    
	//若需要申请的内存小于max,则走small函数申请小内存
	if (size <= pool->max) {
    
    
		return ngx_palloc_small(size, 1);
	}

	//反之走大内存申请的方法
	return ngx_palloc_large(size);
}

//不考虑内存字节对齐
void *ngx_mem_pool::ngx_pnalloc(size_t size)
{
    
    
	//若需要申请的内存小于max,则走small函数申请小内存
	if (size <= pool->max) {
    
    
		return ngx_palloc_small(size, 0);
	}

	//反之走大内存申请的方法
	return ngx_palloc_large(size);
}

//调用pnalloc,并对内存块初始化为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 = pool->current;
	//从current指针指向的内存池开始分配内存

//当p不为空时执行
	do {
    
    
		m = p->d.last;

		if (align) {
    
    //若考虑内存对齐
			m = ngx_align_ptr(m, NGX_ALIGNMENT);
		}//根据开发平台调整m偏移的值

		if ((size_t)(p->d.end - m) >= size) {
    
    
			//若空余内存的大小比要申请的内存大于或等于,则分配
			p->d.last = m + size;//移动p的last
//返回分配出去的内存的首地址
			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;//使new指针指向m指向的

	newpool->d.end = m + psize;//指向该内存块的尾部
	newpool->d.next = nullptr;
	newpool->d.failed = 0;

	m += sizeof(ngx_pool_data_t);//加上数据块的大小
	m = ngx_align_ptr(m, NGX_ALIGNMENT);//移动m的指向
	newpool->d.last = m + size;//更换内存块的已分配出去的尾部


//若每个内存块的分配失败次数大于4之后则此内存块不再使用,切换下一个内存块
	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;

	p = malloc(size);
	//调用malloc开辟指定大小的内存,用p指针指向位置
	if (p == nullptr) {
    
    
		return nullptr;
	}

	n = 0;

	for (large = pool->large; large; large = large->next) {
    
    
		if (large->alloc == NULL) {
    
    
			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) {
    
    
		ngx_pfree(p);
		return nullptr;
	}

	//头信息的alloc指向新内存
	large->alloc = p;
	//给内存池的large赋值,链接大块内存
	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 = nullptr;

			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);//释放不为空的大块内存的alloc
		}
	}

	for (p = pool; p; p = p->d.next) {
    
    
		p->d.last = (u_char *)p + sizeof(ngx_pool_s);
		//移动last指针
		p->d.failed = 0;//将失败次数重置为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; /* 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;
	}

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

using namespace std;

typedef struct Data stData;
struct Data
{
    
    
	char *ptr;
	FILE *pfile;
};

void func1(void *p1)
{
    
    
	char *p = (char *)p1;
	cout << "free ptr mem!" << endl;
	free(p);
}
void func2(void *pf1)
{
    
    
	FILE *pf = (FILE *)pf1;
	cout << "close file!" << endl;
	fclose(pf);
}
int main()
{
    
    
	ngx_mem_pool mempool;//实现在构造函数中
	if (nullptr== mempool.ngx_create_pool(512))
	{
    
    
		cout << "ngx_create_pool fail..." << endl;
		return -1;
	}

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

	stData *p2 = (stData *)mempool.ngx_palloc(512); // 从大块内存池分配的
	if (p2 == NULL)
	{
    
    
		cout << "ngx_palloc 512 bytes fail..." << endl;
		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;
}
#endif

测试结果:
在这里插入图片描述
测试结果正常,代码移植项目完成。

猜你喜欢

转载自blog.csdn.net/qq_45132647/article/details/105673612