当前位置 : 主页 > 网络编程 > 其它编程 >

Nginx内存管理源码剖析注解

来源:互联网 收集:自由互联 发布时间:2023-07-02
文章目录Nginx内存池总览内存池中变量类型定义创建内存池ngx_create_pool内存池分配空间ngx_palloc小块内存空间分配ngx_create_pool 内存池分配空间ngx_palloc 小块内存空间分配ngx_palloc_small 创建小
文章目录Nginx内存池总览内存池中变量类型定义创建内存池ngx_create_pool内存池分配空间ngx_palloc小块内存空间分配ngx_create_pool
  • 内存池分配空间ngx_palloc
    • 小块内存空间分配ngx_palloc_small
      • 创建小块内存池ngx_palloc_block
    • 大块内存空间分配ngx_palloc_large
  • 重置内存池ngx_reset_pool
    • 释放大块内存ngx_pfree
  • 销毁内存池ngx_destroy_pool
    • 注册内存回收函数ngx_pool_cleanup_add
  • Nginx内存池总览

    在这里插入图片描述

    内存池中变量类型定义

    /* * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86. * On Windows NT it decreases a number of locked pages in a kernel. */// 内存池中小内存和大内存分配的分界点一个页的大小4k#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)// 默认的内存池大小#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)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;};typedef struct ngx_pool_large_s ngx_pool_large_t;struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc;};typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed;} ngx_pool_data_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;};

    创建内存池ngx_create_pool

    // 创建内存池ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){ ngx_pool_t *p; // 内存池的头部信息记录内存池中可用内存的信息// 对size进行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); // 内存池中实际能够使用的内存大小 // 使用小内存块进行内存分配时内存池中能够分配的内存块的最大值最多是一个页面大小 // 也可以理解为是内存池分配内存时使用小块内存分配和大块内存分配的分界线 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_palloc

    void *ngx_palloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC)// 如果需要分配的内存小于等于pool->max那么就使用小内存分配// 否则就使用大内存块分配。注意pool->max最大也不能超过一个页面大小(4K) if (size max) { return ngx_palloc_small(pool, size, 1); }#endif return ngx_palloc_large(pool, size);}

    小块内存空间分配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; // 从pool->current指向的大内存块进行分配内存 do { m p->d.last; if (align) {// 对可用空间的起始地址进行内存对齐// 这样可以减少CPU进行IO的次数提高CPU访问数据的效率 m ngx_align_ptr(m, 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);}

    创建小块内存池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);// 分配内存对齐之后的大内存块// 注意分配的大内存块只有内存块的头信息(ngx_pool_data_t)其余的部分都没有 m ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); if (m NULL) { return NULL; }// new成为新大内存的头信息 new (ngx_pool_t *) m; new->d.end m psize; new->d.next NULL; new->d.failed 0;// m在跳过内存块的头部信息之后进行内存对齐。最后就可以被分配出去使用了 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) {// 如果内存块分配内存失败的次数超过4次那么下一次分配内存的时候// 就会默认该大内存块所剩的可用内存太小不适合进行内存分配了// 之后分配内存的时候就会直接跳过该大内存块使用后面的大内存块// 进行内存分配 if (p->d.failed > 4) { pool->current p->d.next; } }// 将新创建的大内存块串联在链表中 p->d.next 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; p ngx_alloc(size, pool->log); // malloc大块内存 if (p NULL) { return NULL; } n 0;// 遍历大块内存块的链表将其中节点中alloc变量指向NULL的节点指向刚创建的大块内存 for (large pool->large; large; large large->next) { if (large->alloc NULL) { large->alloc p; return p; }// 为了减少遍历节点所以只会从pool->large向后找三个节点// 如果这三个节点的alloc都不为NULL的话就不会在找了而是创建新的large节点 if (n > 3) { break; } }// 使用小块内存分配器分配large节点的头部信息 large ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); if (large NULL) { ngx_free(p); return NULL; }// 将节点的alloc指针指向分配的大块内存// 并将头部信息节点头插到large链表中 large->alloc p; large->next pool->large; pool->large large; return p;}

    重置内存池ngx_reset_pool

    // 内存池中大块内存和小块内存的内存重置voidngx_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) {// fixbug: 除了第一个内存池其余的内存池头部信息只有ngx_pool_data_t// 不需要跳过ngx_pool_t这么多 p->d.last (u_char *) p sizeof(ngx_pool_t); p->d.failed 0; // 重置内存池分配内存失败次数 }/*// 完美的重置小块内存池的方式for (p pool; p; p p->d.next) {if (p pool) { // 处理第一个内存池p->d.last (u_char *) p sizeof(ngx_pool_t);} else { // 处理第二个及其之后的内存池p->d.last (u_char *) p sizeof(ngx_pool_data_t);}p->d.failed 0;}*/// 重置内存池元信息 pool->current pool; // 分配内存的第一个内存池重置为pool pool->chain NULL; pool->large NULL; // 重置大块内存链表}void *ngx_palloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC)// 如果需要分配的内存小于等于pool->max那么就使用小内存分配// 否则就使用大内存块分配。注意pool->max最大也不能超过一个页面大小(4K) if (size max) { return ngx_palloc_small(pool, size, 1); }#endif return ngx_palloc_large(pool, size);}

    释放大块内存ngx_pfree

    // 释放内存池中起始地址为p的大块内存ngx_int_tngx_pfree(ngx_pool_t *pool, void *p){ ngx_pool_large_t *l; for (l pool->large; l; l l->next) {// 在大块内存块的链表中找到以p为起始位置的节点// 释放p指向大块内存块并将其头部信息节点的alloc置为NULL 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指针可用内存空间的起始地址或者直接释放该内存。
  • 从Nginx的应用场景来看Nginx是一个HTTP服务器并且是一个基于短连接的服务器。在客户端和服务端的一次request和response之后连接就会自动断开。即使是HTTP1.1中提供的keep-alive也会有时间限制并不是会一直占用这个连接。当连接断开的时候Nginx就会调用ngx_reset_pool重置整个内存池这个时候小块内存和大块内存就都会被释放。这个内存池也可以给下一个连接进行分配内存。正是因为Nginx是一个HTTP服务器而不是一个一直需要为一个用户提供服务的服务器因此可以不用释放小块内存的资源。
  • 销毁内存池ngx_destroy_pool

    // 销毁内存池voidngx_destroy_pool(ngx_pool_t *pool){ ngx_pool_t *p, *n; ngx_pool_large_t *l; ngx_pool_cleanup_t *c;// 执行用户注册的cleanup回调将内存块指向的资源释放掉 for (c pool->cleanup; c; c c->next) { if (c->handler) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "run cleanup: %p", c); c->handler(c->data); } }#if (NGX_DEBUG) /* * we could allocate the pool->log from this pool * so we cannot use this log while free()ing the pool */ for (l pool->large; l; l l->next) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc); } for (p pool, n pool->d.next; /* void */; p n, n n->d.next) { ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p, unused: %uz", p, p->d.end - p->d.last); if (n NULL) { break; } }#endif// 释放掉所有大块内存块 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节点的头信息都在小块内存池中// 所以小块内存池一定要最后才能被销毁}

    注册内存回收函数ngx_pool_cleanup_add

    如果直接释放掉内存的话那么内存块中指向的资源就资源泄漏了。因此用户需要将资源释放函数注册到内存池中这样在销毁内存池的时候就会依次将内存指向的资源大块内存小块内存全部释放从而不会造成内存资源泄漏的情况。

    // 注册cleanup回调函数用于释放内存块指向的资源// 相当于是内存块对象的析构函数了ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){ ngx_pool_cleanup_t *c;// 创建cleanup节点 c ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); if (c NULL) { return NULL; }// 给cleanup节点中的data开辟空间 if (size) { c->data ngx_palloc(p, size); if (c->data NULL) { return NULL; } } else { c->data NULL; } c->handler NULL; // 清理回调函数handler置空// 将ngx_pool_cleanup_t节点头插到cleanup链表中 c->next p->cleanup; p->cleanup c; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c); return c;}

    网友评论