Nginx 源码阅读笔记1 内存池

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/scnu20142005027/article/details/69831333

在大体看完 unp 后本来想练手写一个小型 http 服务器的,但是感觉少了点什么,所以打算先学习一下 nginx 源码,看看大牛们是怎么设计的。然而突然发现自己好像不怎么了解 nginx,所以花了一个多星期翻完了《深入理解 Nginx》感兴趣的部分,加强了对 nginx 的了解。计划是以 linux 为系统环境,看完 http 核心模块,事件模块就看 epoll 模块,然后模仿着写一个小的(砍掉大部分功能的) http 服务器出来。

内存池

Nginx 中的内存池分为大块内存和小块内存,分别由链表连接起来,此外还可以设置销毁内存池时需要执行的函数,结构如下图所示。创建与销毁内存池一般由各内置模块执行,我们在开发第三方模块时一般只需要用预先构造好的内存池对象分配内存,不用手动去释放,这样简化了内存管理,降低了犯错的可能性。
pool

结构体定义

struct ngx_pool_s {
    ngx_pool_data_t       d;        // 内存池链表中各内存池的数据
    size_t                max;      // 小块内存的界限
    ngx_pool_t           *current;  // 小块内存池链表的首个可分配的内存池对象
    ngx_chain_t          *chain;    // 空闲的 chain 可以拿来复用 详见 ngx_alloc_chain_link
    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;

typedef void (*ngx_pool_cleanup_pt)(void *data);

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler; // 处理函数
    void                 *data;    // 用户数据 作为 handler 的参数
    ngx_pool_cleanup_t   *next;    // 链表中下一个清理对象
};

typedef struct {
    ngx_fd_t              fd;   // 关联的文件描述符
    u_char               *name; // 关联的文件路径
    ngx_log_t            *log;  // 日志对象
} ngx_pool_cleanup_file_t;

创建内存池

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;

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

    return p;
}
  • 向系统申请一块内存,地址是需要对齐的,这里用的是ngx_memalign函数,这个函数简单地包装了posix_memalign
  • 在申请到的内存中,由于起始位置用于存放ngx_pool_t,所以需要计算可用内存起始位置
  • 计算小块内存的界限,如果内存池中可用内存小于NGX_MAX_ALLOC_FROM_POOL则界限为可用内存大小,否则为NGX_MAX_ALLOC_FROM_POOL,也就是一个页面的大小

销毁内存池

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) {   // 执行清理对象函数
        if (c->handler) {
            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;
        }
    }
}
  • 遍历cleanup链表,执行handler指定的函数
  • 遍历large链表,根据alloc成员判断是否有分配内存,有则释放
  • 遍历内存池链表,逐个调用ngx_free释放内存,ngx_free就是free函数的宏定义

重置内存池

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;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL; // 由于存放节点的空间都是在当前各个内存池中分配的,所以不用 free
}
  • 遍历large链表,根据alloc成员判断是否有分配内存,有则释放
  • 遍历内存池链表,调整各个内存池的last到初始位置,并清零failed成员
  • 由于chainlarge的节点都是在内存池中分配的,所以可以不用释放,直接设置为NULL

分配内存

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    if (size <= pool->max) {    // 根据大小判断是否为大内存
        return ngx_palloc_small(pool, size, 1);
    }

    return ngx_palloc_large(pool, size);
}
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 0);
    }

    return ngx_palloc_large(pool, size);
}

sizemax比较,以决定是否为大块内存,ngx_palloc分配的内存是根据NGX_ALIGNMENT对齐的,ngx_pnalloc不一定是对齐的,这两个函数的区别是调用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;

    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);    // 现有内存池的剩余空间都不足则分配新的内存池
}

current开始遍历内存池,若找到大小足够的内存池则更新last成员,返回地址,否则调用ngx_palloc_block分配新的内存池

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

    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;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);   // 计算 last
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    for (p = pool->current; p->d.next; p = p->d.next) { // 调整 current 指向的内存池
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;    // 链表末尾接上新分配的内存池

    return m;
}
  • 根据原有的内存池大小分配一个新的内存池
  • 设置新内存池的各个成员,由于d之后的成员都使用第一个内存池的,所以计算last时与之前不同
  • 递增各个内存池的failed的值,并根据其调整current指向的内存池
  • 链表末尾接上新分配的内存池
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);
    if (p == NULL) {
        return NULL;
    }

    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_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_alloc分配内存,这个函数简单地包装了malloc函数
  • large链表中寻找没有分配内存的节点,考虑到效率问题,最多找 3 次,若找到则赋值并返回
  • 若没找到则从内存池中分配存放ngx_pool_large_t的空间,设置各成员并接到large链表头部

其他函数

剩余的函数逻辑较为直观简单,简单地记录下作用

函数 说明
ngx_pmemalign 分配对齐的内存,按指定的 alignment 参数对齐,分配的内存被当作大块内存挂在 large 链表
ngx_pfree 释放指定地址的大块内存
ngx_pcalloc 调用 ngx_palloc 分配内存并清零
ngx_pool_cleanup_add 添加清理对象
ngx_pool_run_cleanup_file 执行 handler 为 ngx_pool_cleanup_file 的清理对象

下面的是提供的一些ngx_pool_cleanup_pt函数

函数 说明
ngx_pool_cleanup_file 关闭指定的文件描述符
ngx_pool_delete_file 删除并关闭指定的文件描述符

猜你喜欢

转载自blog.csdn.net/scnu20142005027/article/details/69831333