NGINX 内存池 源码剖析

NGINX 内存池 源码刨析

剖析nginx的内存池源码,讲解原理实现以及该内存池设计的应用场景

总览图

image-20211203211259152 ## 重要类型定义
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1) // 4K

#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)

#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)

#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))  // 向上调正到邻接 a 的倍数上
// nginx内存池的主结构体类型
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;
};
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;
typedef struct ngx_pool_large_s ngx_pool_large_t;
// 大块内存类型定义
struct ngx_pool_large_s {
	ngx_pool_large_t *next; // 下一个大块内存
	void *alloc; // 记录分配的大块内存的起始地址
};
typedef void (*ngx_pool_cleanup_pt)(void *data); // 清理回调函数的类型定义
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;

// 清理操作的类型定义,包括一个清理回调函数,传给回调函数的数据和下一个清理操作的地址
struct ngx_pool_cleanup_s {
	ngx_pool_cleanup_pt handler; // 清理回调函数
	void *data; // 传递给回调函数的指针
	ngx_pool_cleanup_t *next; // 指向下一个清理操作
};

函数接口

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);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
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);
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
void ngx_pool_cleanup_file(void *data);
void ngx_pool_delete_file(void *data);

ngx_create_pool

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    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); // 可以用的空间
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; // 最大能开辟一个页面,max记录 内存池 分配的最大字节数

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

ngx_palloc

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) { // 小块内存分配   pool->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);
}

内存池数据

image-20211203191943495
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;
    ngx_log_t            *log;
};

typedef struct {
    u_char               *last; 
    u_char               *end; 
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

小块内存分配

小块内存池无回收函数

ngx_palloc_smal

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;

    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(pool, size);
}

ngx_palloc_block

image-20211203211511951

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);  // 计算 pool size

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); // 分配内存,m 指向起始地址
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t); // m指向了 data_t后面,保留了data_t,没保留了除了data_t外的其他头部信息
    m = ngx_align_ptr(m, NGX_ALIGNMENT); // 调整指针,到每个整数倍(sizeof(unsigned long) ),与平台相关
    new->d.last = m + size; // 分配内存

    for (p = pool->current; p->d.next; p = p->d.next) { // 遍历所有内存块
        if (p->d.failed++ > 4) { // 分配次数超过四次,就不再使用这块内存
            pool->current = p->d.next;
        }
    }

    p->d.next = new; // 接到原来的内存链表当中

    return m;
}

大块内存分配

存在一些不准确 小块内存 与 原内存池一样大

image-20211203211259152
typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};

ngx_palloc_large

static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    p = ngx_alloc(size, pool->log); // 直接调用malloc
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) { // 遍历large链表
        if (large->alloc == NULL) { // 如果alloc为空(在free的时候设为空)
            large->alloc = p; // 直接放在这里
            return p;
        }

        if (n++ > 3) { // 只遍历3次
            break;
        }
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); // 在小块内存池上分配 大块内存池的内存头
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large; // 头插法
    pool->large = large;

    return p;
}

ngx_pfree 回收

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

内存池重置函数 和 Nginx内存池特性

小块内存池无法归还 —— 因为 分配内存池 只有 两个指针(last指针,end指针),移动last指针即可分配(分配效率块),不能回收(无法移动last部指针回收)

nginx大块内存分配 ——> 内存释放ngx_free函数
nginx小块内存分配 ——> 没有提供任何的内存释放函数,实际上,从小块内存的分配方式来看(直接通过last指针偏移来分配内存),它也没法进行小块内存的回收

  • nginx本质: http服务器
    是一个短连接的服务器,客户端(浏览器)发起一个request请求,到达nginx服务器以后,处理完成,nginx给客户端返回一个response响应,http服务器就主动断开tcp连接(http 1.1 keep-avlie: 60s )
  • http服务器(nginx)返回响应以后,需要等待60s,60s之内客户端又发来请求,重置这个时间,否则60s之内没有客户端发来的响应,Nginx就主动断开连接。

此时Nginx可以调用ngx_reset_pool重置内存池了,等待下—次该客户端的请求

分配内存池很快,不易free,有内存碎片,对于短连接很有用 —— 不适用于长连接

ngx_reset_pool

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);
        }
    }
	
    /* 原写法存在一定优化空间
    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 = (uchar *) p + sizeof(ngx_pool_t);
    p->d.failed = 0;
    
    // 处理后续内存池
    for (p = p.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->chain = NULL;
    pool->large = NULL; // 大块内存池的内存头 没有用了
}

内存池外部资源释放和内存池销毁

管理外部资源

需要预置一个回调函数 —— 析构对象

typedef void (*ngx_pool_cleanup_pt)(void *data); // 函数指针 —— ngx_pool_claenup_pt

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler; // —— 回调函数
    void                 *data;
    ngx_pool_cleanup_t   *next;
};

ngx_pool_cleanup_add

image-20211205165401043

// typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) // 插入一个 ngx_pool_cleanup_s
{
    ngx_pool_cleanup_t  *c;

    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); // 开辟cleanup_t的空间
    if (c == NULL) {
        return NULL;
    }

    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    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);
}

stData *pData = ngx_alloc(512) // stData里有一个 char*成员
pData->p = (char*)malloc(12);
strcpy(pData->p, "hello world");

ngx_pool_cleanup_t *pclean = ngx_pool_cleanup_add(pool, sizeof(char*));
pclean->handler = &release;
pclean->data = pData->p;

ngx_desteory_pool

先对大快内存操作

  1. 调用先前 放置 的 内存清理函数
  2. 释放大块内存
void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

    for (c = pool->cleanup; c; c = c->next) { // 先遍历cleanup,执行外部资源的清除操作
        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) { // free 大块内存
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { // free 小块内存
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

使用例子

编译

gcc -c -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -o ngx_testpool.o  ngx_testpool.c


gcc -o ngx_testpool ngx_testpool.o objs/src/core/ngx_palloc.o objs/src/os/unix/ngx_alloc.o  // 生成可执行文件
#include <ngx_config.h>
#include <nginx.h>
#include <ngx_core.h>
#include <ngx_palloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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!");
    free(p);
}
void func2(FILE *pf)
{
    printf("close file!");
    fclose(pf);
}
void main()
{
	// 512 - sizeof(ngx_pool_t) 、 4096   =>   max
    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;

    ngx_destroy_pool(pool); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存

    return;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
提供的码资涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些码资特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资都能为你提供宝贵的学习和实践机会。通过学习和运行这些码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些码资进行课程实践、课外项目或毕业设计。通过分析和运行码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保码资的可运行性和易用性,特别注意了以下几点:首先,每份码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些码资,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值