千家信息网

nginx内存池源码分析

发表于:2025-11-08 作者:千家信息网编辑
千家信息网最后更新 2025年11月08日,本篇内容主要讲解"nginx内存池源码分析",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"nginx内存池源码分析"吧!内存池概述内存池是在真正使用内存之前
千家信息网最后更新 2025年11月08日nginx内存池源码分析

本篇内容主要讲解"nginx内存池源码分析",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"nginx内存池源码分析"吧!

内存池概述

内存池是在真正使用内存之前,预先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存。

内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片,提示程序性能,减少程序员在编写代码中对内存的关注等

目前一些常见的内存池实现方案有STL中的内存分配区,boost中的object_pool,nginx中的ngx_pool_t,google的开源项目TCMalloc等。

一、nginx数据结构

// SGI STL小块和大块内存的分界点:128B// nginx(给HTTP服务器所有的模块分配内存)小块和大块内存的分界点:4096B#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1) // 内存池默认大小#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)// 内存池字节对齐,SGI STL对其是8B#define NGX_POOL_ALIGNMENT       16#define NGX_MIN_POOL_SIZE        ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), \                                         NGX_POOL_ALIGNMENT)// 将开辟的内存调整到16的整数倍#define ngx_align(d, a)          (((d) + (a - 1)) & ~(a - 1))
typedef struct ngx_pool_s ngx_pool_t;typedef struct {    u_char               *last;   // 指向可用内存的起始地址    u_char               *end;    // 指向可用内存的末尾地址    ngx_pool_t           *next;   // 指向下一个内存块      ngx_uint_t            failed; // 当前内存块分配空间失败的次数} ngx_pool_data_t;// 内存池块的类型struct ngx_pool_s {    ngx_pool_data_t       d;          // 内存池块头信息    size_t                max;          ngx_pool_t           *current;    // 指向可用于分配空间的内存块(failed < 4)的起始地址    ngx_chain_t          *chain;      // 连接所有的内存池块    ngx_pool_large_t     *large;     // 大块内存的入口指针    ngx_pool_cleanup_t   *cleanup;    // 内存池块的清理操作,用户可设置回调函数,在内存池块释放之前执行清理操作    ngx_log_t            *log;        // 日志};

二、nginx向OS申请空间ngx_create_pool

// 根据size进行内存开辟ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log){    ngx_pool_t  *p;        // 根据系统平台定义的宏以及用户执行的size,调用不同平台的API开辟内存池    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);    if (p == NULL) {        return NULL;    }    p->d.last = (u_char *) p + sizeof(ngx_pool_t);  // 指向可用内存的起始地址    p->d.end = (u_char *) p + size;                 // 指向可用内存的末尾地址    p->d.next = NULL;                               // 指向下一个内存块,当前刚申请内存块,所以置空                  p->d.failed = 0;                                // 内存块是否开辟成功    size = size - sizeof(ngx_pool_t);              // 能使用的空间 = 总空间 - 头信息    // 指定的大小若大于一个页面就用一个页面,否则用指定的大小    // max = min(size, 4096),max指的是除开头信息以外的内存块的大小    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;    p->current = p;         // 指向可用于分配空间的内存块的起始地址    p->chain = NULL;    p->large = NULL;        // 小块内存直接在内存块开辟,大块内存在large指向的内存开辟    p->cleanup = NULL;    p->log = log;    return p;}

三、nginx向内存池申请空间

void *ngx_palloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC)    if (size <= pool->max) {            // 当前分配的空间小于max,小块内存的分配        return ngx_palloc_small(pool, size, 1);   // 考虑内存对齐    }#endif    return ngx_palloc_large(pool, size);}void *ngx_pnalloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC)    if (size <= pool->max) {        return ngx_palloc_small(pool, size, 0);  // 不考虑内存对齐    }#endif    return ngx_palloc_large(pool, size);}void* ngx_pcalloc(ngx_pool_t *pool, size_t size){    void *p;    p = ngx_palloc(pool, size); // 考虑内存对齐    if (p) {        ngx_memzero(p, size);   // 可以初始化内存为0    }    return p;}

ngx_palloc_small 分配效率高,只做了指针的偏移

static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align){    u_char      *m;    ngx_pool_t  *p;        // 从第一个内存块的current指针指向的内存池进行分配    p = pool->current;    do {        m = p->d.last;  // m指向可分配内存的起始地址        if (align) {                // 把m调整为NGX_ALIGNMENT整数倍            m = ngx_align_ptr(m, NGX_ALIGNMENT);        }                // 内存池分配内存的核心代码        if ((size_t) (p->d.end - m) >= size) {                // 若可分配空间 >= 申请的空间                // 偏移d.last指针,记录空闲空间的首地址            p->d.last = m + size;            return m;        }        // 当前内存块的空闲空间不够分配,若有下一个内存块则转向下一个内存块        // 若没有,p会被置空,退出while        p = p->d.next;    } while (p);            return ngx_palloc_block(pool, size);}

当前内存池的块足够分配:

当前内存池的块不够分配:

  1. 开辟新的内存块,修改新内存块头信息的last、end、next、failed

  2. 前面所有内存块的failed++

  3. 连接新的内存块以及前面的内存块

static void * ngx_palloc_block(ngx_pool_t *pool, size_t size){    u_char      *m;    size_t       psize;    ngx_pool_t  *p, *new;        // 开辟与上一个内存块大小相同的内存块    psize = (size_t) (pool->d.end - (u_char *) pool);                // 将psize对齐为NGX_POOL_ALIGNMENT的整数倍后,向OS申请空间    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);    if (m == NULL) {        return NULL;    }    new = (ngx_pool_t *) m;    // 指向新开辟内存块的起始地址    new->d.end = m + psize;    // 指向新开辟内存块的末尾地址    new->d.next = NULL;                   // 下一块内存的地址为NULL     new->d.failed = 0;            // 当前内存块分配空间失败的次数            // 指向头信息的尾部,而max,current、chain等只在第一个内存块有    m += sizeof(ngx_pool_data_t);      m = ngx_align_ptr(m, NGX_ALIGNMENT);    new->d.last = m + size;                // last指向当前块空闲空间的起始地址                // 由于每次都是从pool->current开始分配空间        // 若执行到这里,除了new这个内存块分配成功,其他的内存块全部分配失败    for (p = pool->current; p->d.next != NULL; p = p->d.next) {            // 对所有的内存块的failed都++,直到该内存块分配失败的次数大于4了            // 就表示该内存块的剩余空间很小了,不能再分配空间了            // 就修改current指针,下次从current开始分配空间,再次分配的时候可以不用遍历前面的内存块        if (p->d.failed++ > 4) {            pool->current = p->d.next;        }    }            p->d.next = new;   // 连接可分配空间的首个内存块 和 新开辟的内存块    return m;}

四、大块内存的分配与释放

typedef struct ngx_pool_large_s  ngx_pool_large_t;struct ngx_pool_large_s {    ngx_pool_large_t     *next;   // 下一个大块内存的起始地址    void                 *alloc;  // 大块内存的起始地址};static void * ngx_palloc_large(ngx_pool_t *pool, size_t size){    void              *p;    ngx_uint_t         n;    ngx_pool_large_t  *large;                // 调用的就是malloc    p = ngx_alloc(size, pool->log);    if (p == NULL) {        return NULL;    }    n = 0;        // for循环遍历存储大块内存信息的链表    for (large = pool->large; large; large = large->next) {        if (large->alloc == NULL) {                // 当大块内存被ngx_pfree时,alloc为NULL                // 遍历链表,若大块内存的首地址为空,则把当前malloc的内存地址写入alloc            large->alloc = p;            return p;        }                // 遍历4次后,若还没有找到被释放过的大块内存对应的信息                // 为了提高效率,直接在小块内存中申请空间保存大块内存的信息        if (n++ > 3) {            break;        }    }        // 通过指针偏移在小块内存池上分配存放大块内存*next和*alloc的空间    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);    if (large == NULL) {            // 如果在小块内存上分配存储*next和*alloc空间失败,则无法记录大块内存            // 释放大块内存p        ngx_free(p);        return NULL;    }            large->alloc = p;                     // alloc指向大块内存的首地址    large->next = pool->large;         // 这两句采用头插法,将新内存块的记录信息存放于以large为头结点的链表中    pool->large = large;    return p;}

大块内存的释放

// 释放p指向的大块内存ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p){    ngx_pool_large_t  *l;    for (l = pool->large; l; l = l->next) {            // 遍历存储大块内存信息的链表,找到p对应的大块内存        if (p == l->alloc) {            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,                           "free: %p", l->alloc);            // 释放大块内存,但不释放存储信息的内存空间            ngx_free(l->alloc);  // free            l->alloc = NULL;     // alloc置空            return NGX_OK;        }    }    return NGX_DECLINED;}

五、关于小块内存不释放

就用了last和end两个指着标识空闲的空间,是无法将已经使用的空间合理归还到内存池的,只是会重置内存池。同时还存储了指向大内存块large和清理函数cleanup的头信息

考虑到nginx的效率,小块内存分配高效,同时也不回收内存

void ngx_reset_pool(ngx_pool_t *pool){    ngx_pool_t        *p;    ngx_pool_large_t  *l;                // 由于需要重置小块内存,而大块内存的控制信息在小块内存中保存        // 所以需要先释放大块内存,在重置小块内存    for (l = pool->large; l; l = l->next) {        if (l->alloc) {            ngx_free(l->alloc);        }    }                // 遍历小块内存的链表,重置last、failed、current、chain、large等管理信息    for (p = pool; p; p = p->d.next) {            // 由于只有第一个内存块有除了ngx_pool_data_t以外的管理信息,别的内存块只有ngx_pool_data_t的信息            // 不会出错,但是会浪费空间        p->d.last = (u_char *) p + sizeof(ngx_pool_t);        p->d.failed = 0;    }                // current指向可用于分配内存的内存块    pool->current = pool;    pool->chain = NULL;    pool->large = NULL;}

nginx本质是http服务器,通常处理的是短链接,间接性提供服务,需要的内存不大,所以不回收内存,重置即可。

客户端发起一个requests请求后,nginx服务器收到请求会返回response响应,若在keep-alive时间内没有收到客户的再次请求,nginx服务器会主动断开连接,此时会reset内存池。下一次客户端请求再到来时,可以复用内存池。

如果是处理长链接,只要客户端还在线,服务器的资源就无法释放,直到系统资源耗尽。长链接一般使用SGI STL内存池的方式进行内存的开辟和释放,而这种方式分配和回收空间的效率就比nginx低

六、销毁和清空内存池

假设如下情况:

// 假设内存对齐为4Btypedef struct{        char* p;        char data[508];}stData;ngx_pool_t *pool = ngx_create_pool(512, log);  // 创建一个总空间为512B的nginx内存块stData* data_ptr = ngx_alloc(512);            // 因为可用的实际内存大小为:512-sizeof(ngx_pool_t),所以属于大内存开辟data_ptr->p = malloc(10);                   // p指向外界堆内存,类似于C++对象中对用占用了外部资源

当回收大块内存的时候,调用ngx_free,就会导致内存泄漏

以上内存泄漏的问题,可以通过回调函数进行内存释放(通过函数指针实现)

typedef void (*ngx_pool_cleanup_pt)(void *data);typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;// 以下结构体由ngx_pool_s.cleanup指向,也是存放在内存池的小块内存struct ngx_pool_cleanup_s {    ngx_pool_cleanup_pt   handler;    void                 *data;     // 指向需要释放的资源    ngx_pool_cleanup_t   *next;     // 释放资源的函数都放在一个链表,用next指向这个链表};

nginx提供的函数接口:

// p表示内存池的入口地址,size表示p->cleanup->data指针的大小// p->cleanup指向含有清理函数信息的结构体// ngx_pool_cleanup_add返回 含有清理函数信息的结构体 的指针ngx_pool_cleanup_t* ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){    ngx_pool_cleanup_t  *c;                // 开辟清理函数的结构体,实际上也是存放在内存池的小块内存    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));    if (c == NULL) {        return NULL;    }            if (size) {            // 为c->data申请size的空间        c->data = ngx_palloc(p, size);        if (c->data == NULL) {            return NULL;        }    } else {        c->data = NULL;    }    c->handler = NULL;    // 采用头插法,将当前结构体串在pool->cleanup后    c->next = p->cleanup;    p->cleanup = c;    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);    return c;}

使用方式:

void release(void* p){        free(p);}ngx_pool_cleanup_t* clean_ptr = ngx_clean_cleanup_add(pool, sizeof(char*));clean_ptr->handler = &release;   // 用户设置销毁内存池前需要调用的函数clean_ptr->data = data_ptr->p;   // 用户设置销毁内存池前需要释放的内存的地址ngx_destroy_pool(pool);          // 用户销毁内存池

七、编译测试内存池接口功能

void ngx_destroy_pool(ngx_pool_t *pool){    ngx_pool_t          *p, *n;    ngx_pool_large_t    *l;    ngx_pool_cleanup_t  *c;                // 遍历cleanup链表(存放的时释放前需要调用的函数),可释放外部占用的资源    for (c = pool->cleanup; c; c = c->next) {        if (c->handler) {            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,                           "run cleanup: %p", c);            c->handler(c->data);        }    }        // 释放大块内存    for (l = pool->large; l; l = l->next) {        if (l->alloc) {            ngx_free(l->alloc);        }    }                // 释放小块内存池    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {        ngx_free(p);                if (n == NULL) {            break;        }    }}

执行configure生成Makefile文件(若报错则表示需要apt安装软件)

Makefile如下:

执行make命令使用Makefile编译源码,在相应目录下生成 .o文件

#include #include #include #include #include #include #include void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,            const char *fmt, ...){}typedef struct Data stData;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);}void main(){        // max = 512 - sizeof(ngx_pool_t)        // 创建总空间为512字节的nginx内存块    ngx_pool_t *pool = ngx_create_pool(512, NULL);    if(pool == NULL){        printf("ngx_create_pool fail...");        return;    }            // 从小块内存池分配的    void *p1 = ngx_palloc(pool, 128);     if(p1 == NULL){        printf("ngx_palloc 128 bytes fail...");        return;    }                // 从大块内存池分配的    stData *p2 = ngx_palloc(pool, 512);     if(p2 == NULL){        printf("ngx_palloc 512 bytes fail...");        return;    }        // 占用外部堆内存    p2->ptr = malloc(12);    strcpy(p2->ptr, "hello world");    // 文件描述符    p2->pfile = fopen("data.txt", "w");        ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add(pool, sizeof(char*));    c1->handler = func1;   // 设置回调函数    c1->data = p2->ptr;    // 设置资源地址    ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add(pool, sizeof(FILE*));    c2->handler = func2;    c2->data = p2->pfile;                // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存    ngx_destroy_pool(pool);     return;}

由于ngx_pool_cleanup_add中用头插法将创建的清理块链入pool->cleanup,所以ngx_destroy_pool的时候先清理文件后清理堆内存。

到此,相信大家对"nginx内存池源码分析"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

内存 分配 空间 大块 指向 地址 信息 函数 指针 起始 大小 资源 结构 服务 服务器 用户 存储 源码 客户 效率 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 excel图表嵌入数据库 梦幻西游服务器和商人比例 dhcp 服务器的组成 餐饮后台系统软件开发公司 怎么将自己的电脑当服务器 秀屿区网络安全宣传 vue中可以直接查数据库吗 迅麦风互联网科技有限公司大连 网络数据库技术课后答案 字节跳动企业网络服务器 河北文档软件开发技术 美国高科技互联网上市公司 网络安全宣传青少年日 病句以网络技术为重要支撑 华为服务器错误请稍后 不是数据库的对象类型的 数据库关键字详解 网络安全工程师好不好学 宇视软件开发工程师笔试真题 电脑打网页显示找不到服务器 江苏极光网络技术有限公司 人工智能在网络技术应用中难题 管理游戏服务器怎么办 河北联想服务器虚拟化 图片路径 数据库中 数据库的安全特性是指 温州网络技术赛重播 市网络安全应急管理中心怎么样 网络安全评论留言大全 数据库环境应如何写
0