资讯专栏INFORMATION COLUMN

Nginx 源码分析:ngx_pool_t

codergarden / 707人阅读

摘要:源代码路径版本主要作用分析提供了一种机制,帮助进行资源管理内存文件。用来标记该使用时分配失败次数。根据以上思路,可以很容易明白源码里关于创建链表的代码函数声明说明输入要分配的节点大小,返回一个的指针。

源代码路径

版本:1.8.0

srccoreNgx_palloc.h
srccoreNgx_palloc.c
主要作用分析

提供了一种机制,帮助进行资源管理(内存、文件)。可以类比C++中的RAII机制。

以内存管理为例,通常是手工进行malloc/free,这种做法的优点是灵活、高效,缺点是容易出错,造成内存泄漏以及各种莫名其妙的BUG。

因此,在C/C++中正确的管理内存一直是最棘手的问题。

C++中提供了RAII机制和智能指针来解决这个问题。Nginx采用C语言开发,实现了一套用来管理资源的机制。这就是nginx pool

数据结构

ngx_pool_data_t

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

首先看一个示意图:

last指针表示ngx_pool_data_t所管理的内存block中已使用的内存的末尾地址,这也是下次分配的起始地址。

end指针表示ngx_pool_data_t所管理的内存block的尾地址,该指针在分配此block时确定,用来标记last的边界值。

next用来指向下一个ngx_pool_data_t的内存block地址,用于形成block链表。
failed用来标记该block使用时分配失败次数。

ngx_pool_data_t的管理和使用

围绕核心数据结构ngx_pool_data_t, Nginx是如何管理和使用的呢?

本篇主要从以下几个方面说明:
1) 如何初始化ngx_pool_data_t链表;
2) 如何向链表增加ngx_pool_data_t节点。
3) 如何回收/销毁ngx_pool_data_t节点及链表

初始化ngx_pool_data_t链表

基本思路:

所谓初始化ngx_pool_data_t链表就是申请一段内存block,然后将该block指针void* p转成ngx_pool_data_t*类型的指针,这样,就可以利用ngx_pool_data_t*指针来管理这段block,而这个block就是ngx_pool_data_t链表的节点

当然要能够正确管理block还需要初始化ngx_pool_data_t的成员变量lastendnextfailed。这样,通过ngx_pool_data_t指针,可以获取管理ngx_pool_data_t链表节点的能力

但是现在我们还没有管理整个ngx_pool_data_t链表的能力,那么如何做呢?通过在内存block的起始部分添加lastendnextfailed等信息可以管理一段内存。相应地,通过在ngx_pool_data_t链表第一个节点添加管理链表的信息,就可以管理整个链表。

同时,由于链表第一个节点只是一个特殊的节点所以,负责管理节点的结构体ngx_pool_data_t应该是的负责管理链表结构体节点的子集

Nginx源码中这个管理链表的结构体就是:ngx_pool_s,其结构体定义为:

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

其中,dngx_pool_data_t用来管理节点本身,而其他变量则用来管理链表。

current用来指示链表中当前正在使用的节点;

chain用来在节点上挂接filter链表;

cleanup用来注册资源回收handler等;

large是与节点大内存小内存有关,这里暂不分析。

通过以上分析,可知,ngx_pool_s是管理整个ngx_pool_data_t链表,同时也能够管理所在的链表节点。

Nginx源码里,申请的block指针为void*统一转成ngx_pool_s*,这样,在链表第一个节点,通过ngx_pool_s*管理整个链表及节点本身,在其他节点,通过ngx_pool_s*管理节点本身。

根据以上思路,可以很容易明白Nginx源码里关于创建ngx_pool_data_t链表的代码

函数声明: Ngx_palloc.h

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

说明:

输入要分配的ngx_pool_t节点block大小size,返回一个ngx_pool_t*的指针。该指针既用来管理链表,也用来管理节点block本身。
因此,后续链表的插入,删除都是通过操作该指针来完成,而使用链表第一个节点的block也是通过该指针来完成。

函数定义: Ngx_palloc.c

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

    // 申请内存block,为提高效率,进行了对齐
    // 转成ngx_pool_t*指针
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  
    if (p == NULL) {
        return NULL;
    }

    // 初始化ngx_pool_data_t用来管理节点block本身
    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;

    // 为提高效率能使用的block最大值为NGX_MAX_ALLOC_FROM_POOL
    // 输入size最小值为sizeof(ngx_pool_t),输入小于该值会造成nginx崩溃
    size = size - sizeof(ngx_pool_t);
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    // 初始时,链表current指向自身
    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}
向链表增加ngx_pool_data_t节点

基本思路:

上一小节中关于如何初始化ngx_pool_data_t链表的思路,可以基本套用到增加ngx_pool_data_t节点中来,只是在结构体ngx_pool_t成员变量初始化上有所区别。

直接上源码:

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    // 输入变量pool用来表示整个链表
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    // 计算链表第一个节点的block大小
    psize = (size_t) (pool->d.end - (u_char *) pool);
    // 申请与链表第一个节点大小相同的内存block
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }
    // 转为ngx_pool_t*类型
    new = (ngx_pool_t *) m;
    // 初始化节点管理结构体ngx_pool_data_t
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;
    // 为了效率,对ngx_pool_data_t的last变量进行对齐操作
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;
    // 将第一个链表节点用于操作链表的pool指针的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 = new;

    return m;
}

当然,在使用时,Nginx并不是直接使用ngx_palloc_block来操作链表,而是使用ngx_palloc
原因在于,使用ngx_create_poolngx_palloc_block构造链表之后,我们得到的是多个连续的内存块组成的列表,这些内存块大小相同,其大小为调用ngx_create_pool传入的size值或NGX_MAX_ALLOC_FROM_POOL

而在实际使用时malloc的大小不一定等于这些内存块的大小,所以,需要提供一个接口,能够从ngx_pool_t链表中获取需要大小的内存。

这个函数就是ngx_palloc

基本思路:

如果要获取的内存大小大于ngx_pool_t节点的大小,那么属于创建大内存,调用ngx_palloc_large(pool, size)。这里我们暂不分析大内存的问题。

如果要获取的内存小于ngx_pool_t节点的大小,那么尝试从当前节点分配。

这里分两种情况:

当前节点剩余空间足够的情况,直接分配;

当前节点剩余空间不足的情况,创建一个新节点,并将新节点分配

P.S 这里的分配就是返回指针

函数声明:

void *ngx_palloc(ngx_pool_t *pool, size_t size);

函数定义:

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;
    // 获取内存小于链表节点内存的情况
    if (size <= pool->max) {

        p = pool->current;
        // 尝试从当前节点分配内存
        do {
            m = ngx_align_ptr(p->d.last, 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);
    }
    // 获取内存小于链表节点内存的情况
    return ngx_palloc_large(pool, size);
}

今天先到这里,后续再补充或另起一篇写。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/39133.html

相关文章

  • Nginx 源码分析:ngx_list_t

    摘要:源码路径版本主要作用分析是对通常的这种数据结构重复的造轮子。链表使用的内存池。在堆上创建调用函数与的分析类似,调用该函数会自动向申请内存空间。 源码路径 版本:1.8.0 srccoreNgx_list.h srccoreNgx_list.c 主要作用分析 ngx_list_t是Nginx对通常的list这种数据结构重复的造轮子。 在本篇中,我们先来分析Nginx是如何造这...

    Kahn 评论0 收藏0
  • Nginx 源码分析:ngx_array_t

    摘要:源文件路径版本主要作用分析是内部使用的数组型数据结构,与语言内置的数组概念上类似,但是有两点主要区别使用内存池来管理内存虽然有预设数组大小的概念,但是在数组元素超出预设值大小时,会在内存池中发生重分配。 源文件路径 版本:1.8.0 srccoreNgx_array.h srccoreNgx_array.c 主要作用分析 ngx_array_t是Nginx内部使用的数组型数据...

    zhonghanwen 评论0 收藏0
  • Nginx源码分析Nginx的内存管理

    摘要:而对于堆内存,通常需要程序员进行管理。我们通常说的内存管理亦是只堆空间内存管理。内存管理整体可以分为个部分,第一部分是常规的内存池,用于进程平时所需的内存管理第二部分是共享内存的管理。将内存块按照的整数次幂进行划分最小为最大为。 施洪宝 一. 概述 应用程序的内存可以简单分为堆内存,栈内存。对于栈内存而言,在函数编译时,编译器会插入移动栈当前指针位置的代码,实现栈空间的自管理。而对于...

    raise_yang 评论0 收藏0

发表评论

0条评论

codergarden

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<