手写Nginx内存池移植项目
可配合这篇博客一起学习
剖析Nginx的内存池源码 | 施磊 | C++项目实战-优快云博客
注意点
1.所有需要传入内存池入口指针(pool)的地方都不用传入了,因为私有变量pool_存储的就是内存池入口指针
2.在C++中,void* 指针是一个通用指针类型,它可以指向任何类型的数据。然而,由于C++是一种类型安全的语言,直接将 void* 转换为其他类型的指针(或反之)而不进行显式转换(即“强转”)通常是不被允许的,或者至少是不被推荐的。
文章目录
0.预编译文件
//pch.h
#pragma once
#ifndef PCH_H
#define PCH_H
#endif
//pch.cpp
#include"pch.h"
1.ngx_mem_pool.h 变量和接口函数定义
#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 接口具体实现
#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 测试文件
#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
//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
#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的改进还没完成(笔者标注一下不要在意)

15万+

被折叠的 条评论
为什么被折叠?



