nginx内存池是nginx中的一个重要功能模块,用于管理内存的分配和释放,以提高nginx服务器的性能和稳定性。
文章目录
nginx内存池特点
- nginx内存池是一种灵活、线程安全的内存分配器。与其他内存池不同的是,每个进程或线程都可以创建自己的内存池,因此不需要考虑多线程竞争的问题,无需加锁,从而节省了开销。
- nginx内存池可以高效地分配小块内存。它通过指针的偏移来管理和分配内存,减少了malloc调用的次数,避免了用户态和内核态频繁切换的开销。每个小块内存池都是一块连续内存,大小控制在一页大小以内,既避免了内存碎片问题造成的内存浪费,页内寻址还保证了申请小内存时的速度效率。
- 小块内存不提供单个回收接口,只在重置内存池时回收小内存,销毁内存池时内存归还给系统。这是因为nginx多用于http服务器,处理完客户端请求后,等待60秒的keepalive时间后,如果没有收到该客户端链接的后续请求,服务器会主动断开链接,此时nginx可以调用ngx_reset_pool重置内存池,所以小内存无需单个回收。
- 对于大块内存,nginx内存池使用malloc申请,并将所有申请的大内存地址记录在一条链表中。此外,nginx允许用户挂载自己的数据区域及对应的数据清理操作,用户可以使用ngx_pool_cleanup_add接口注册清理函数,在内存池销毁时释放自己申请的动态内存,避免内存泄漏。
nginx内存池重要的数据结构
在nginx内存池中,最重要的数据结构是ngx_pool_s。它作为整个内存池的头部,管理着内存池节点链表、大内存链表、cleanup链表等信息,并存放在第一个小内存池里。后续其他的小内存池中只有ngx_pool_data_t数据头。
struct ngx_pool_s {
ngx_pool_data_t d; // 小内存池的数据头
size_t max; // 小块内存分配的最大值
ngx_pool_t *current; // 小块内存池入口指针
ngx_chain_t *chain;
ngx_pool_large_t *large; // 大块内存分配入口指针
ngx_pool_cleanup_t *cleanup; // 清理函数handler的入口指针
ngx_log_t *log;
};
ngx_pool_data_t是小内存池的数据头,通过next指针将小内存池串成链表(尾插)。它记录了可分配内存的开始位置和末尾位置,保存了下一个内存池的地址,还记录了当前内存池分配失败的次数。
// 小块内存数据头信息
typedef struct {
u_char *last; // 可分配内存开始位置
u_char *end; // 可分配内存末尾位置
ngx_pool_t *next; // 保存下一个内存池的地址
ngx_uint_t failed; // 记录当前内存池分配失败的次数
} ngx_pool_data_t;
ngx_pool_large_s则是用来记录大内存地址的,通过next指针将大内存串成链表(头插),它记录了分配的大块内存的起始地址。
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 下一个大块内存
void *alloc; // 记录分配的大块内存的起始地址
};
ngx_pool_cleanup_s是用于用户自定义清理操作的,当内存池销毁时,可以用它来清理用户自己申请的动态内存。它包括了一个清理回调函数、传给回调函数的数据和下一个清理操作的地址。
// 清理操作的类型定义,包括一个清理回调函数,传给回调函数的数据和下一个清理操作的地址
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 清理回调函数
void *data; // 传递给回调函数的指针
ngx_pool_cleanup_t *next; // 指向下一个清理操作
};
nginx内存池结构图
nginx内存池的分配机制和重要接口
nginx内存池提供了高效的内存管理机制,其设计旨在避免内存碎片和频繁的系统调用。以下是nginx内存池的分配机制和重要接口的概述:
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log); // 创建内存池
void ngx_destroy_pool(ngx_pool_t *pool); // 销毁内存池
void ngx_reset_pool(ngx_pool_t *pool); // 重置内存池
void *ngx_palloc(ngx_pool_t *pool, size_t size); // 内存分配函数,支持内存对齐
void *ngx_pnalloc(ngx_pool_t *pool, size_t size); // 内存分配函数,不支持内存对齐
void *ngx_pcalloc(ngx_pool_t *pool, size_t size); // 内存分配函数,支持内存初始化0
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p // 内存释放(大块内存)
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size); // 添加清理
handler
- 用户通过调用ngx_create_pool函数来创建指定大小的内存池。这个函数会在内存中分配一块大小为size的内存池,并返回一个指向ngx_pool_t类型的结构体的指针。
- 用户可以使用ngx_palloc函数从内存池中申请内存。如果所申请的内存大小小于内存池的最大大小,ngx_palloc会遍历小块内存池链表,并从第一个满足要求的内存池中分配内存。分配完成后,当前内存池的last指针会向后偏移size个字节,更新可用内存的起始地址。
- 如果所申请的内存大小大于内存池的最大大小,ngx_palloc会调用ngx_pool_large使用malloc的方式从系统中再申请一块大小为size的内存,并将其记录到大内存链表中。
- 如果链表上所有内存池的可用空间都不足以提供用户申请的size空间,ngx_palloc会调用ngx_pool_block再申请一个内存池,从其中分配内存并将其返回给用户,并将新的内存池尾插到内存池链表上。
- 用户可以通过调用ngx_reset_pool函数来重置内存池。这个函数会释放大内存链表中记录的所有大块内存,并将所有小块内存池的last指针重置回收小块内存。
- 用户可以通过调用ngx_destroy_pool函数来销毁内存池。这个函数会遍历cleanup列表,调用用户注册的清理函数回收外部资源。然后,它会释放大内存链表中的所有大内存,并释放所有小块内存池。
用C++ OOP的方式封装重写nginx内存池
减少内存浪费
在第一个小内存池中,除了记录头部信息之外,还记录整个内存池的管理成员信息。其他小内存池只记录头部信息。因此,第一个小内存池的可用内存要比其他小内存池小。在重置内存池时,需要将这两种内存池分别处理,以避免内存浪费。
所以在ngx_reset_pool重置函数中,做了优化
ngx_mem_pool.h
#pragma once
#include <stdlib.h>
#include <memory.h>
// 重定义类型
using u_char = unsigned char;
using ngx_uint_t = unsigned int;
// 向上对齐到a的倍数
#define ngx_align(d,a) (((d) + (a -1)) & ~(a - 1)
// 将地址值向上对齐到a(指针大小)的倍数
#define ngx_align_ptr(p, a) \
(u_char*) (((uintptr_t)(p) + ((uintptr_t)a - 1)) & ~((uintptr_t)a - 1))
// 将给定内存初始化为0
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
// 前置声明
struct ngx_pool_s;
// 一个页面大小为4K
const int ngx_pagesize = 4096;
// 大块内存和小块内存的分解
const int NGX_MAX_ALLOC_FROM_POOL = (ngx_pagesize - 1);
// 如果用户不提供内存池size参数,则默认16K
const int NGX_DEFAULT_POOL_SIZE = (16 * 1024);
// 内存池大小字节对齐16byte
const int NGX_POOL_ALIGNMENT = 16;
const int NGX_ALIGNMENT = sizeof(unsigned long);
// 清理外部资源回调函数指针的类型定义
typedef void (*ngx_pool_cleanup_pt)(void* data);
// 小块内存池的头部信息
struct ngx_pool_data_t {
u_char* last; // 小块内存池中可分配内存起始位置
u_char* end; // 小块内存池中可分配内存末尾位置
ngx_pool_s* next; // 所有小块内存池都被串在一个链表上
ngx_uint_t failed; // 当前小块内存池内存分配失败次数
};
// 管理大块内存池,用于将所有大块内存池串成链表
struct ngx_pool_large_s {
ngx_pool_large_s* next; // 将所有大块内存池串成链表
void* alloc; // 从堆上malloc申请的大块内存
};
// 管理清理外部资源回调函数指针的类
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 清理外部资源回调函数指针, 由用户提供
void* data;
ngx_pool_cleanup_s* next; // 串成一个链表
};
// 内存池的管理成员
struct ngx_pool_s {
ngx_pool_data_t d; // 小块内存池的头部信息
size_t max; // 申请的内存大于max,则分配大块内存
ngx_pool_s* current; // 指向当前可分配的小块内存池
ngx_pool_large_s* large; // 指向大块内存池的管理头
ngx_pool_cleanup_s* cleanup; // 指向清理外部资源的回调函数管理结构
};
// 最小内存池size
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_pool_s* 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_palloc_block(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;
void* ngx_palloc_small(size_t size, ngx_uint_t align);
void* ngx_palloc_large(size_t size);
};
ngx_mem_pool.cpp
#include "ngx_mem_pool.h"
#include "stdio.h"
// 创建第一个内存池,并初始化内存池管理成员
ngx_pool_s* ngx_mem_pool::ngx_create_pool(size_t size)
{
// malloc申请内存池空间
pool = (ngx_pool_s*)malloc(size);
if (nullptr == pool)
{
printf("ngx create pool failed!\n");
return nullptr;
}
// 初始化小块内存池头部信息
pool->d.last = (u_char*)pool + sizeof(ngx_pool_s);
pool->d.end = (u_char*)pool + size;
pool->d.next = nullptr;
pool->d.failed = 0;
// 小块内存可分配大小(第一块小块内存池减去内存池管理成员大小)
size = size - sizeof(ngx_pool_s);
pool->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
// 初始化内存池管理成员
pool->current = pool;
pool->large = nullptr;
pool->cleanup = nullptr;
printf("ngx create pool, first small pool created!\n");
return pool;
}
// 从内存池中分配内存,字节对齐
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_palloc_small(size_t size, ngx_uint_t align)
{
u_char* m;
ngx_pool_s* p;
p = pool->current;
do {
m = p->d.last;
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_large(size_t size)
{
void* p; // 保存申请大块内存的地址
ngx_uint_t n;
ngx_pool_large_s* large; // 用于管理大内存的节点
p = malloc(size);
if (nullptr == p)
{
return nullptr;
}
n = 0;
// 遍历当前大块内存管理链表的前3个节点,如果有空闲的管理节点,,则将申请的大内存地址给它
// 否则就用申请小块内存的方式,新申请一个大块内存管理节点,头插法插入到大内存管理链表中
// 因为申请小块内存时非常快捷,所以只遍历前3个现有节点,没有空闲节点就去申请小内存,避免浪费时间
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 (nullptr == large)
{ // 如果管理节点的空间申请失败,则释放申请的大内存
free(p);
return nullptr;
}
// 头插法,将大内存管理节点large插入大内存管理链表
large->alloc = p;
large->next = pool->large;
pool->large = large;
printf("alloc large mem %p, size %d\n", p, size);
return p;
}
// 用于新申请一个小块内存池
void* ngx_mem_pool::ngx_palloc_block(size_t size)
{
u_char* m;
size_t psize;
ngx_pool_s* p, * newpool;
// size和第一个小块内存池相同
psize = (size_t)(pool->d.end - (u_char*)pool);
m = (u_char*)malloc(psize);
if (nullptr == m)
{
return nullptr;
}
// 初始化小块内存池头部信息
newpool = (ngx_pool_s*)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);
newpool->d.last = m + size;
// 遍历链表上现有的小块内存池,更新current指针(记录链表第一个可以分配内存的小块内存池)
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_pfree(void* p)
{
ngx_pool_large_s* l;
for (l = pool->large; l; l = l->next)
{
// 释放大内存,并将其对应的管理节点指针置为nullptr
if (p == l->alloc)
{
printf("free: %p\n", 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);
}
}
/****************************************
* 这里逻辑与nginx源码不同,因为第一块小内存池不仅有头部信息,还记录整个内存池的管理成员信息,
* 但其他小内存池只有头部信息,所以可用内存大于第一块小内存池,重置时需区分开,避免内存浪费
****************************************/
// 重置所有小内存池
// 重置第一块小内存池
pool->d.last = (u_char*)pool + sizeof(ngx_pool_s);
pool->d.failed = 0;
// 重置后面的小内存池
for (p = pool->d.next; p; p = p->d.next)
{
p->d.last = (u_char*)p + sizeof(ngx_pool_s);
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) {
printf("run cleanup: %p\n", c);
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 (nullptr == n)
{
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 (nullptr == c)
{
return nullptr;
}
if (size)
{
c->data = ngx_palloc(size);
if (nullptr == c->data)
{
return nullptr;
}
}
else
{
c->data = nullptr;
}
c->handler = nullptr;
c->next = pool->cleanup;
pool->cleanup = c;
printf("add cleanup\n");
return c;
}
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;
}
test.cpp
#include "ngx_mem_pool.h"
#include <cstring>
#include <cstdio>
struct Data
{
char* ptr;
FILE* pfile;
};
void func1(char* p)
{
printf("free ptr mem!\n");
free(p);
}
void func2(FILE* pf)
{
printf("close file!\n");
fclose(pf);
}
int main()
{
ngx_mem_pool mempool;
// 512 - sizeof(ngx_pool_s) - 4095 => max
if (nullptr == mempool.ngx_create_pool(512))
{
printf("ngx_create_pool fail...");
return -1;
}
void* p1 = mempool.ngx_palloc(128); // 从小块内存池分配的
if (p1 == nullptr)
{
printf("ngx_palloc 128 bytes fail...");
return -2;
}
Data* p2 = (Data*)mempool.ngx_palloc(512); // 从大块内存池分配的
if (p2 == nullptr)
{
printf("ngx_palloc 512 bytes fail...");
return -3;
}
p2->ptr = (char*)malloc(12);
if (nullptr == p2->ptr)
{
return -4;
}
strcpy_s(p2->ptr, 12, "hello world");
fopen_s(&p2->pfile, "data.txt", "w");
ngx_pool_cleanup_s* c1 = mempool.ngx_pool_cleanup_add(sizeof(char*));
c1->handler = (ngx_pool_cleanup_pt)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;
// 也可以把ngx_destroy_pool放在ngx_mem_pool的析构函数里执行.
mempool.ngx_destroy_pool(); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存
return 0;
}