zoukankan      html  css  js  c++  java
  • Nginx数据结构之内存池

    基本结构

    1. ngx_pool_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;
        ngx_log_t            *log;
    };
    

    提供的函数

    1. ngx_create_pool(): 分配并初始化一块 size 大小的内存池

    ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)
    {
        ngx_pool_t  *p;
    
        /* 进行16字节的内存对齐分配 */
        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的最大值为4095,假设一页大小为4KB.
         * 问:为什么要将pool->max字段的最大值限制在一页内存?
         * 这个字段是区分小块内存和大块内存的临界,所以这里的原因也就在于只有当
         * 分配的内存空间小于一页时才有缓存的必要(即向Nginx内存池申请),否则的
         * 话,还不如直接利用系统接口malloc()向操作系统申请。
         */
        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_pool_t 占去开头一部分(即额外的开销 overhead),Nginx实际是从该内存池里 p->d.last 指向的
    起始位置开始分配给用户的。

    2. ngx_palloc(): 从内存池pool中分配size大小的内存

    void *ngx_palloc(ngx_pool_t *pool, size_t size)
    {
    #if !(NGX_DEBUG_PALLOC)
        if (size <= pool->max)
        {
            /* 第 3 个参数为 1 表示需要进行内存对齐 */
            return ngx_palloc_small(pool, size, 1);
        }    
    #endif
    
        return ngx_palloc_large(pool, size);
    }
    

    该函数尝试从 pool 内存池里分配 size 大小的内存空间。有两种情况:

    1. 如果 size 小于等于 pool->max (称之为小块内存分配),即小于等于内存池总大小或 1 页内存(4K - 1),则调用
      ngx_palloc_small() 进行分配;
    2. 否则为大块内存分配, 即调用 ngx_palloc_large() 进行分配。

    2.1 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 指向当前正在使用的 ngx_pool_t 内存池首地址 */
        p = pool->current;
    
        do 
        {
            m = p->d.last;
    
            /* 进行内存对齐 */
            if (align) 
            {
                m = ngx_align_ptr(m, NGX_ALIGNMENT);
            }
    
            /* 若当前内存池的可用空间充足,则直接从当前内存池中分配 size 大小空间 */
            if ((size_t) (p->d.end - m) >= size)
            {
                p->d.last = m + size;
    
                return m;
            }
    
            /* 若当前内存池的可用空间不足 size,则指向下一个内存池节点 */
            p = p->d.next;
    
        } while (p);
    
        /* 若遍历完 p->d.next 所链接的内存池链表都没有足够的内存空间,则调用 ngx_palloc_block()
         * 再分配一个等同大小的内存池,并将其链接到 p->d.next 内存池链表中 */
        return ngx_palloc_block(pool, size);
    }
    

    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;
    
        /* 获取 pool 指向的内存池总大小,其中包括内存池开头占用的 sizeof(ngx_pool_t) 大小的 overhead  */
        psize = (size_t) (pool->d.end - (u_char *) pool);
    
        /* 分配 psize 大小并 16 字节对齐的内存空间 */
        m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
        if (m == NULL)
        {
            return NULL;
        }
    
        /* ngx_pool_t 类型的结构体指针 new 指向该新分配的内存起始地址 */
        new = (ngx_pool_t *) m;
    
        new->d.end    = m + psize;
        new->d.next   = NULL;
        new->d.failed = 0;
    
        /* 该新分配的内存池的起始为 sizeof(ngx_pool_data_t) 大小的 overhead */
        m += sizeof(ngx_pool_data_t);
        m = ngx_align_ptr(m, NGX_ALIGNMENT);
        /* 表示从新内存池中分配 size 大小的内存空间 */
        new->d.last = m + size;
    
        /* 遍历 pool->current 指向的内存池链表,根据 p->d.failed 值更新 pool->current */
        for (p = pool->current; p->d.next; p = p->d.next) 
        {
            /* current 字段的变动是根据统计来做的,如果从当前内存池节点分配内存总失败次数(记录在
             * 字段 p->d.failed 内) 大于等于 6 次(这是一个经验值,具体判断是 "if (p->d.failed++ > 4)"
             * 由于 p->d.failed 初始值为 0,所以当这个判断为真时,至少已经分配失败 6 次了),就将
             * current 字段移到下一个内存池节点,如果下一个内存池节点的 failed 统计数也大于等于 6 次,
             * 再下一个,依次类推,如果直到最后仍然是 failed 统计数大于等于 6 次,那么 current 字段
             * 指向刚新分配的内存池节点 */
            if (p->d.failed++ > 4) 
            {
                pool->current = p->d.next;
            }
        }
    
        /* 将新分配的内存池节点添加到内存池链表的尾部 */
        p->d.next = new;
    
        return m;
    }
    
    申请并链接新的内存池节点图

    2.2 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;
    
        /* 直接malloc size 大小的内存 */
        p = ngx_alloc(size, pool->log);
        if (p == NULL) 
        {
            return NULL;
        }
    
        n = 0;
    
        /* 遍历 large 指向的大块内存链表 */
        for (large = pool->large; large; large = large->next) 
        {
            /* 在内存池的使用过程中,由于大块内存可能会被释放(通过函数 ngx_pfree()),
             * 此时将空出其对应的头结构体变量 ngx_pool_large_t,所以在进行实际的链
             * 头插入操作前,会去搜索当前是否有这种情况存在。如果有,则直接将新分配的
             * 内存块设置在其 alloc 指针字段下。又如下可知,这种搜索也只是对前面 5 个
             * 链表节点进行 */
            if (large->alloc == NULL) 
            {
                large->alloc = p;
                return p;
            }
    
            /* 这里表示仅搜索大块内存链表中前面的 5 个节点 */
            if (n++ > 3)
            {
                break;
            }
        }
    
        /* 若 ngx_pool_large_t 类型的指针 large 开始时为 NULL,则从内存池 pool 中
         * 分配一块 sizeof(ngx_pool_large_t) 大小的内存,并使 large 指向该内存起始 */
        large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
        if (large == NULL) 
        {
            ngx_free(p);
            return NULL;
        }
    
        /* alloc 指向上面直接 malloc 的大块内存 */
        large->alloc = p;
        /* 将该新分配的大块内存节点 large 插入到 pool->large 指向的大块内存链表头部 */
        large->next  = pool->large;
        pool->large  = large;
    
        return p;
    }
    
    申请第一个大块内存

    继续申请大块内存

    释放大块内存

    3. ngx_pnalloc()

    该函数与 ngx_palloc() 类似,只不过是没有进行内存对齐.

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

    4. ngx_pmemalign()

    void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
    {
        void              *p;
        ngx_pool_large_t  *large;
    
        /* 分配一块 size 大小并进行 alignment 字节对齐的内存空间 */
        p = ngx_memalign(alignment, size, pool->log);
        if (p == NULL) 
        {
            return NULL;
        }
    
        /* 从内存池 pool 中分配一块 sizeof(ngx_pool_large_t) 大小的内存空间 */
        large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
        if (large == NULL)
        {
            ngx_free(p);
            return NULL;
        }
    
        /* 将 p 指向的新分配的大块内存挂到 large->alloc 下 */
        large->alloc = p;
        /* 然后将 large 添加到 pool->large 指向的大块内存链表头 */
        large->next  = pool->large;
        pool->large  = large;
    
        return p;
    }
    

    5. ngx_pfree()

    ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
    {
        ngx_pool_large_t  *l;
    
        /* 遍历 pool->large 指向的大块内存链表,在该链表中找到 p 指向的大块内存,
         * 然后释放 p 指向的内存空间,并将 alloc 置为 NULL*/
        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;
            }
        }
    
        /* 没有找到则返回 NGX_DECLINED */
        return NGX_DECLINED;
    }
    

    6. 其他函数

    Nginx 此外提供的一套函数接口 ngx_pool_cleanup_add()、ngx_pool_run_cleanup_file()、ngx_pool_cleanup_file()、
    ngx_pool_delete_file() 用于对内存与其他资源的关联管理,也就是说,从内存池里申请一块内存时,可能外部会
    附加一些其他资源(比如打开的文件),这些资源的使用和申请的内存是绑定在一起的,那么在进行资源释放的时候,
    希望这些资源的释放能和内存池释放一起进行(通过 handler() 回调函数),既能避免无意的资源泄漏,又省得单独
    执行资源释放的麻烦。

  • 相关阅读:
    解决死锁四大方式
    Windows内存管理简介:
    排序算法优劣
    排序
    HTTPs
    http和https的异同
    HTTP协议
    FTP与TFTP
    tomcat热部署
    开发心得体会
  • 原文地址:https://www.cnblogs.com/jimodetiantang/p/8910514.html
Copyright © 2011-2022 走看看