nginx源码解析之内存池

nginx自身实现了内存池,所有内存分配都是基于内存池来操作。基本思想是预申请一段内存空间,低于指定大小的内存(小段内存)直接从内存池中申请,超过指定大小的内存(大段内存)直接调用malloc申请。相关代码在os/unix/ngx_alloc.{c,h}和core/ngx_palloc.{c,h}。

os/unix/ngx_alloc.{c,h}文件封装了内存分配的系统调用,其中:

  • ngx_alloc调用malloc申请一段内存空间
  • ngx_calloc调用malloc申请一段内存空间,再调用memset将这段空间初始化为0
  • ngx_memalign调用memalign/posix_memalign申请内存地址对齐的一段空间

core/ngx_palloc.{c,h}包含了nginx内存池实现的主要代码,先看看内存池的相关数据结构:

// 内存池数据区
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; // 大段内存空间,调用malloc申请
};

struct ngx_pool_s {
    ngx_pool_data_t       d;
    size_t                max; // 内存池分配空间(不超过page_size-1)
    ngx_pool_t           *current;  // 当前内存池
    ngx_chain_t          *chain;
    ngx_pool_large_t     *large;    // 大段内存链表
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
};

转换成为数据结构图可能更加清晰一些:

注意图中current指针指向的并不是所在的内存池,实际上current指针的指向是会变化的。

依次查看ngx_palloc.c文件中的代码:

  • ngx_create_poll函数用来创建size大小的内存池
ngx_pool_t  *p;

// 申请NGX_POLL_ALIGNMENT对齐的内存空间
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);

// 实际可分配的空间为size-sizeof(ngx_poll_t)
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.end = (u_char *) p + size;
size = size - sizeof(ngx_pool_t);
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
...
  • ngx_destroy_poll函数用来销毁内存池,销毁流程是先销毁cleanup链表中的内存(数据结构定义如下,即调用handler函数销毁data空间),销毁大段内存(large链表),最后销毁ngx_poll_t链表。
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_reset_pool函数用来重置内存池(销毁大段内存空间、重置内存池字段,如last字段设为p+sizeof(ngx_poll_t))
  • ngx_palloc函数从内存池中申请一段空间并返回地址
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 {
            // 计算last地址对齐后的地址
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);

            // 可用空间超过size则直接返回
            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);
}
  • ngx_pnalloc函数与ngx_palloc函数功能相同,唯一不同的是缺少了计算last对齐后的地址,即ngx_palloc返回的一定是内存地址对齐的地址,而ngx_pnalloc则不一定。
        do {
            // 唯一与ngx_palloc不同的地方
            m = p->d.last;

            if ((size_t) (p->d.end - m) >= size) {
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        } while (p);
  • ngx_palloc_block函数在ngx_palloc和ngx_pnalloc中均有被调用,用来重新申请内存池并返回对齐的地址空间
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new, *current;

    // 计算当前内存池分配大小
    psize = (size_t) (pool->d.end - (u_char *) pool);

    // 申请内存地址对齐的空间
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    // 初始化工作,与ngx_create_poll中类似
    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 = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    // 重新设置current指针,将刚申请的内存池放到链表末尾
    current = pool->current;

    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            current = p->d.next;
        }
    }

    p->d.next = new;

    pool->current = current ? current : new;

    return m;
}
  • 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申请空间
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    // 尝试将申请的空间放入large链表中(仅尝试4次?)
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }

    // 申请ngx_pool_large_t数据结构,初始化alloc指向申请的大段内存,放到large链表头
    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}
  • ngx_pmemalign函数用来申请内存地址对齐的大段空间
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
    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;

    // 遍历large链表
    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;
}
  • ngx_pcalloc函数在ngx_palloc的基础上,将这段空间初始化为0

ngx_pool_cleanup_add、ngx_pool_run_cleanup_file、ngx_pool_cleanup_file和ngx_pool_delete_file函数暂时不对其进行解析。以上便是nginx内存池实现的全部,本代码基于nginx1.7.0,在CentOS6.0下完成编译。

时间: 2024-10-10 08:11:14

nginx源码解析之内存池的相关文章

redis源码解析之内存管理

zmalloc.h的内容如下: 1 void *zmalloc(size_t size); 2 void *zcalloc(size_t size); 3 void *zrealloc(void *ptr, size_t size); 4 void zfree(void *ptr); 5 char *zstrdup(const char *s); 6 size_t zmalloc_used_memory(void); 7 void zmalloc_enable_thread_safeness(v

Java源码解析 - ThreadPoolExecutor 线程池

1 线程池的好处 线程使应用能够更加充分合理地协调利用CPU.内存.网络.I/O等系统资源.线程的创建需要开辟虚拟机栈.本地方法栈.程序计数器等线程私有的内存空间;在线程销毁时需要回收这些系统资源.频繁地创建和销毁线程会浪费大量的系统资源,增加并发编程风险. 在服务器负载过大的时候,如何让新的线程等待或者友好地拒绝服务? 这些都是线程自身无法解决的;所以需要通过线程池协调多个线程,并实现类似主次线程隔离.定时执行.周期执行等任务. 线程池的作用包括:●利用线程池管理并复用线程.控制最大并发数等●

Samba 源码解析之内存管理

由于工作需要想研究下Samba的源码,下载后发现目录结构还是很清晰的.一般大家可能会对source3和source4文件夹比较疑惑.这两个文件夹针对的是Samba主版本号,所以你可以暂时先看一个.这里我选择Source3. 阅读源码最好要动手编译并安装,但这里我偷个懒直接在ubuntu上安装跳过了编译步骤.首先从client开始看起.SMBclient的所有命令的对应code都在source3/client/client.c中,我们由浅入深,挑一个比较简单的命令来看下它的执行流程,将简单的命令分

nginx源码解析之数据结构篇

在上一篇分析nginx内存池的基础上,回过头来看看nginx中一些常见的数据结构,如字符串.数组.队列.链表.hash表等. 字符串 nginx字符串的实现代码在core/ngx_string.{h,c},里面除了字符串定义之外,还包括很多辅助函数,有字符串拷贝.格式化.转换及编码等.以下是字符串结构的定义,实际上就是在字符指针的基础上加了个长度字段,意味着可以用来表示二进制的数据. typedef struct { size_t len; u_char *data; } ngx_str_t;

leveldb源码分析之内存池Arena

转自:http://luodw.cc/2015/10/15/leveldb-04/ 这篇博客主要讲解下leveldb内存池,内存池很多地方都有用到,像linux内核也有个内存池.内存池的存在主要就是减少malloc或者new调用的次数,较少内存分配所带来的系统开销. Arena类采用vector来存储每次分配内存的指针,每一次分配的内存,我们称为一个块block.block默认大小为4096kb.我们可以先看下Arena的模型: 我们来看看源码: 首先看下这个类的几个成员变量: 1 2 3 4

STL源码分析之内存池

前言 上一节只分析了第二级配置器是由多个链表来存放相同内存大小, 当没有空间的时候就向内存池索取就行了, 却没有具体分析内存池是怎么保存空间的, 是不是内存池真的有用不完的内存, 本节我们就具体来分析一下 内存池 static data template的初始化 template <bool threads, int inst> char *__default_alloc_template<threads, inst>::start_free = 0; // 内存池的首地址 tem

Netty源码解析(4)-内存分配

ByteBuf直接与底层IO打交道 1.内存类别有哪些 2.如何减少多线程内存分配竞争 3.不同大小内存是如何分配的 内存与内存管理器的抽象 不同规格大小和不同内存类别的分配策略 内存回收 ByteBuf结构 readerIndex,表示要读数据从当前指针开始读,从0到readerIndex这段空间表示是无效的 writerIndex,必须大于readerIndex,表示要写数据从当前指针开始写,从readerIndex到writerIndex这段空间表示可以读的 capacity,必须,从wr

[nginx源码解析]配置解析(main作用域)

下面我们就以一个简单配置(nginx默认配置文件)来进行分析整个配置解析过程,同时会附图 配置文件如下(nginx默认生成配置文件) worker_processes 1; daemon off; events { worker_connections 1024 ; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { list

[nginx源码解析]配置解析(event作用域)

处理events 其中符合NGX_EVENT_MODULE有两个模块分别是ngx_event_core_module.ngx_epoll_module 核心代码 ngx_modules[i]->ctx_index = ngx_event_max_module++;//设置模块内部索引 } ctx = ngx_pcalloc(cf->pool, sizeof(void *)); if (ctx == NULL) { return NGX_CONF_ERROR; } *ctx = ngx_pcal