不落辰

知不可乎骤得,托遗响于悲风

0%

轮子-nginx内存池

nginx内存池原理

轮子-nginx内存池

  • 内存池中分为大内存block和小内存block
  • 大内存block的头信息在小内存块中。大内存块头信息通过链表连接起来。

内存池结构

整体

  • 内存池
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //  内存池
    struct ngx_pool_s {
    ngx_pool_data_t d; // 小内存Block的头信息
    size_t max; // p->max:一个Block块内最多能分配多大的小内存。其大小受制于程序本身ngx_memalign开辟的大小,也受制于小内存定义的上限4095
    ngx_pool_t* current; // 指向当前内存块
    ngx_pool_large_t* large; // 大内存Block头信息的链表入口
    ngx_pool_cleanup_t* cleanup; // 外部资源的头信息链表
    };

小内存Block 头信息

1
2
3
4
5
6
7
8
9
//  小内存池头信息
typedef struct {
u_char *last; // 可用内存起始
u_char *end; // 内存末尾
ngx_pool_t *next;
ngx_uint_t failed; // 分配内存是否成功
} ngx_pool_data_t;

typedef struct ngx_pool_s ngx_pool_t;

大内存Block 头信息

1
2
3
4
5
6
7
8
//  大内存池头信息

typedef struct ngx_pool_large_s ngx_pool_large_t;

struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};

外部资源 头信息

1
2
3
4
5
6
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
};
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t

一些宏

1
2
3
4
5
6
7
8
#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)

创建内存池 ngx_create_pool

ngx_create_pool

  • 创造的是一个内存Block,从操作系统malloc而来。是打头的那个。有完整的ngx_pool_t,记录了整个内存池的信息。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //  size:内存池大小(实际可以使用的比size小)
    // log:日志
    ngx_pool_t *
    ngx_create_pool(size_t size, ngx_log_t *log)
    {
    ngx_pool_t *p;

    // 开辟内存池。依据平台调用相应函数
    // ngx_pool_t + 自由内存
    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; // 下一个小内存block
    p->d.failed = 0; // 当前内存块分配内存失败次数

    size = size - sizeof(ngx_pool_t); // 内存Block实际能使用的大小。整个内存池大小size - 内存池头信息

    // p->max:一个Block块内最多能分配多大的小内存。
    // 其大小受制于程序本身ngx_memalign开辟的大小,也受制于小内存定义的上限4095
    // p->max = min(size,4095)
    // max不超过一个页面的大小
    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

  • 向操作系统malloc内存
  • ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
    • 开辟内存池(size和log参数起作用),并根据平台选择是否内存对齐(alignment参数起作用)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      /*
      * Linux has memalign() or posix_memalign()
      * Solaris has memalign()
      * FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()
      * aligns allocations bigger than page size at the page boundary
      */

      // 根据平台选择是否内存对齐
      #if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)
      void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log); // 进行内存对齐
      #else
      #define ngx_memalign(alignment, size, log) ngx_alloc(size, log) // ngx_alloc(size,log) 不进行内存对齐
      #endif

      ----------------------------------------------------------------------------
      // ngx_alloc:就是malloc
      void *
      ngx_alloc(size_t size, ngx_log_t *log)
      {
      void *p;
      p = malloc(size);
      if (p == NULL) {
      ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
      "malloc(%uz) failed", size);
      }
      ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
      return p;
      }

      --------------------------------------------------
      // void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log); // 内存对齐的malloc

      #if (NGX_HAVE_POSIX_MEMALIGN)

      void *
      ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
      {
      void *p;
      int err;
      err = posix_memalign(&p, alignment, size);
      if (err) {
      ngx_log_error(NGX_LOG_EMERG, log, err,
      "posix_memalign(%uz, %uz) failed", alignment, size);
      p = NULL;
      }
      ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
      "posix_memalign: %p:%uz @%uz", p, size, alignment);
      return p;
      }

      #elif (NGX_HAVE_MEMALIGN)

      void *
      ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
      {
      void *p;
      p = memalign(alignment, size);
      if (p == NULL) {
      ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
      "memalign(%uz, %uz) failed", alignment, size);
      }
      ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
      "memalign: %p:%uz @%uz", p, size, alignment);
      return p;
      }
      #endif

内存Block分配 ngx_palloc

  • 小块内存分配、大块内存分配
  • ngx_palloc 考虑了内存对齐
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void *
    ngx_palloc(ngx_pool_t *pool, size_t size)
    {
    #if !(NGX_DEBUG_PALLOC)
    // 分配小块内存(优先从内存池分配,如果没有,再新开辟ngx_memalign)
    // size:上级要请求的
    // pool->max block块内空闲内存的上限
    if (size <= pool->max) {
    return ngx_palloc_small(pool, size, 1); // 指针 ,大小 ,1:对齐
    }
    #endif
    // 分配大块内存
    return ngx_palloc_large(pool, size);
    }

ngx_palloc_small 小块内存分配

  • 从操作系统开辟新的小块内存池。ngx_palloc_small调用ngx_palloc_block。ngx_palloc_block底层调用memalign。

  • 效率极高:如何分配内存:通过移动last指针分配内存。将移出的size内存返回给上级

  • ngx_palloc_small

    • 先尝试用已有的内存池中拿出size大小的内存块,当内存池里没有可分配的满足size大小的内存块时,再ngx_palloc_block开辟新block块。从block里拿出size大小的内存返回给上级。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      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); // 内存对齐:把指针调整到平台相关的4/8倍数。
      }
      // 内存池的空闲内存大于要申请的内存
      if ((size_t) (p->d.end - m) >= size) {
      // m指针偏移size字节,即内存池给应用程序分配内存
      p->d.last = m + size;
      return m;
      }
      // 如果本block块剩余的不够size,那么顺着p->d.next向下走到第二个内存块block。
      p = p->d.next;
      } while (p);

      // 从pool->current开始 遍历完了 所有的block,也没找到够用的空闲内存
      // 那么就只能新开辟block
      return ngx_palloc_block(pool, size);
      }
  • ngx_palloc_block
    • 原有内存池内存不够,分配新的内存块加入内存池
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      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); // Block大小

      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);
      m = ngx_align_ptr(m, NGX_ALIGNMENT);
      new->d.last = m + size;

      // 由于进入到这个函数必然意味着之前的block都分配内存失败
      // 所以要把前几个block的fail次数都++;
      // 当一个block了=块分配内存失败次数
      // 当一个block块失败次数>4之后,就认为这块Block的剩余内存已经很少,之后请求小内存时就不从这块Block开始请求
      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;
      }

ngx_free 释放大块内存
小块内存不释放

ngx_palloc_large 大内存块分配

  • 从操统malloc大块内存。ngx_palloc_large调用ngx_alloc。ngx_alloc调用malloc

  • malloc大块内存用于存储数据;从小内存池申请一块内存用作大内存池头信息;维护大内存池链表;将本次malloc的大块内存全部返回给上级使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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;
    }

    // 以pool->large为起始 遍历大内存池头信息节点
    n = 0;
    for (large = pool->large; large; large = large->next) {
    // 如果遍历到的这块大内存池节点的头信息的alloc==nullptr
    // 意味着并这个ngx_pool_large_t没有管理一块大内存。
    // 所以直接由他管理
    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起始的链表中
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
    }
  • ngx_alloc() 分配大块内存,不进行内存对齐

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void *
    ngx_alloc(size_t size, ngx_log_t *log)
    {
    void *p;

    p = malloc(size);
    if (p == NULL) {
    ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
    "malloc(%uz) failed", size);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

    return p;
    }
  • ngx_free()

    1
    #define ngx_free          free

大小内存Block的重置 ngx_reset_pool

  • nginx大块内存block通过malloc分配
    • 大块内存:通过free释放
  • nginx小内存通过在内存池(的一个小内存块)中移动last指针来实现内存分配
    • 小块内存:无free释放,只是通过移动last指针来重置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void
ngx_reset_pool(ngx_pool_t *pool)
{
ngx_pool_t *p; // 小内存Block的头信息
ngx_pool_large_t *l; // 大内存Block的头信息

// 大块内存 free掉
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc);
}
}

// 小块内存 不释放。只是移动指针。
// 因为从实现方式上来看,就释放不了。我们通过移动last指针来分配内存。可能我们要释放2号内存,但是我们只有last和end的两个指针,且last指向5号内存。
// 从nginx的功能来看,也无需释放。
// for (p = pool; p; p = p->d.next) {
// p->d.last = (u_char *) p + sizeof(ngx_pool_t);
// p->d.failed = 0;
// }
p = pool;
p->d.last = (u_char*)p+sizeof(ngx_pool_t);
p->d.failed = 0;
for(p=p->d.next;p;p=p->d.next)
{
p->d.last = (u_char*)p+sizeof(ngx_pool_data_t);
p->d.failed = 0;
}

// 重置首块内存Block中用于管理所有内存Block的
pool->current = pool;
pool->chain = NULL;
pool->large = NULL;
}

释放指定大内存块 ngx_pfree

  • 释放指定大内存块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ngx_int_t
    ngx_pfree(ngx_pool_t *pool, void *p)
    {
    ngx_pool_large_t *l;

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

为什么reset_pool中nginx内存池的小内存块不free释放??

  • 因为没有free小块内存的接口。
  • 所以只能移动last指针

    1. 为什么没free接口

  • 首先nginx内存池的没办法回收针对某一特定小内存块进行free回收。
    • 因为从实现方式上来看,就释放不了。我们通过移动last指针来分配内存。可能我们要释放2号内存,但是我们只有last和end的两个指针,且last指向3号下的free内存。那么如果移动last到2那里然后free,那么就会把3也free掉
  • 因此nginx没有提供free回收小块内存的接口函数
  • 所以重置时我们只能通过移动last指针来表示哪些内存是空闲内存

2. 为什么移动last可以

  • 那么在释放内存时为什么移动last就可以。这样能表明装有没用数据的内存是空闲的,不害怕有用的内存被覆盖吗?
  • reset_pool时,不会有有用的内存块。因为我们是短连接。
  • nginx本质:http服务器。短连接、间歇性。
    • client向nginx server发送请求后,nginx处理完请求,并发送完response响应后;即一次服务处理完后,就主动和客户端断开tcp连接,该次连接的资源就都可以释放了。(server就像和client端没见过面一样。之前的东西都不必再保留,一干二净。)
    • 所以,当nginx server 处理完一次请求后,就可以调用ngx_reset_pool,free大块内存;将小块内存的last指针回退,等待下次覆盖。
  • 长连接不论client是否发来请求,server和client是不能断开的。也即处理完请求后,连接也不能断开,资源也不允许释放,直到系统资源耗尽。长连接的这种server应该用SGI STL的二级空间配置器中的内存池。??????
    • 长连接举例:http1.1 keep-alive = 60s,即http服务器返回响应后,需等待60s。如果这段时间之内客户端又发来请求,那么重置等待时间。如果没发来请求,那么就断开连接。
  • 以上关于nginx用于短连接的场景我可以理解。不过他为什么不能用于长连接?我只写过短连接的webserver,还没写过长连接的。等写了长连接的server应该就能理解了吧。
  • 长连接短连接
  • nginx服务器入门

nginx内存池和sgi stl二级空间配置器

  • nginx对于小块内存的分配效率绝对比sgi stl二级空间配置器高,因为nginx只需要移动指针。
  • nginx无法释放小块内存。sgi stl 小块和大块都释放。
    • 故nginx适用于短连接Web服务器(http服务器)。sgi stl适用于长连接服务器。

内存池外部资源释放

外部资源信息头

1
2
3
4
5
6
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
};
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t

内存池外部资源释放

对于cleanup_add的使用有两种方式

    1. 一种是给c->data开辟一块内存,然后把外部资源拷贝进去。之后destroy中c->handler(void*p)
    • ngx_pool_cleanup_t * c = ngx_pool_cleanup_add(pool,sizeof(x))
    • 感觉适用于一个这块内存里很多指针管理很多外部资源,可以省去挨个设置外部资源信息头以及回调函数。
    1. 一种是不给c->data开辟内存,直接让c->data指向要释放的内存。
    • ngx_pool_cleanup_t * c = ngx_pool_cleanup_add(pool,0)
  • 我觉得在一块申请的内存中由很多指针管理外部资源时,【第一种优于第二种】。
    • 相较于【第二种不拷贝方法】单独为 每个指针管理的外部资源都设置一个外部资源信息头,以及一个释放的回调函数;不如【第一种方法】只设置一个回调函数和一个信息头。将这些外部资源拷贝到一块内存中,然后将这块内存传入即可。这样就可以用一个信息头,一个回调函数,即可释放多个资源。

源码论证

  • 第【1】种
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //  结构体
    typedef struct {
    ngx_fd_t fd;
    u_char *name;
    ngx_log_t *log;
    } ngx_pool_cleanup_file_t;

    // 为c->data开辟内存
    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
    ....
    cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;
    // 将管理的资源地址拷贝到c->data指向的内存
    clnf = cln->data;
    clnf->fd = file->fd;
    clnf->name = file->name.data;
    clnf->log = pool->log;

    // 预制的回调函数
    void
    ngx_pool_delete_file(void *data)
    {
    ngx_pool_cleanup_file_t *c = data;
    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
    c->fd, c->name);
    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
    ...
    }
    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
    ...
    }
    }
  • 第【2】种
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //  不申请内存
    cln = ngx_pool_cleanup_add(r->pool, 0);
    if (cln == NULL) {
    return NGX_ERROR;
    }
    // 直接将地址给cln->data
    cln->handler = ngx_http_file_cache_cleanup;
    cln->data = c;

    // 预制回调函数
    static void
    ngx_http_file_cache_cleanup(void *data)
    {
    ngx_http_cache_t *c = data;
    if (c->updated) {return;}
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
    "http file cache cleanup");
    if (c->updating && !c->background) {
    ...
    }
    ngx_http_file_cache_free(c, NULL);
    }

  • cleanup_add功能概述:通过预置回调函数,实现c++中对象析构的功能。也即释放内存。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    ngx_pool_cleanup_t *
    ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
    {
    ngx_pool_cleanup_t *c;
    // 从小内存block中 分配一块内存用作ngx_pool_cleanup_t
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
    return NULL;
    }

    // 看用户是怎么调用的
    // size>0 从内存池中申请一块内存。用作之后用户将外部资源拷贝到这里
    // size=0 不申请内存。用户直接将c->data = 外部资源地址
    if (size) {
    c->data = ngx_palloc(p, size);
    if (c->data == NULL) {
    return NULL;
    }

    } else {
    c->data = NULL;
    }

    c->handler = NULL;
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
    }

内存池销毁

  • 先遍历链表执行外部资源的释放操作;再遍历链表执行释放大块内存的操作;再遍历链表重置小块内存Block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
// 遍历外部资源信息头链表, 调用预设置的回调函数handler,释放外部资源。
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;
}
}
}

测试代码

  • 环境:ubuntu 18.04 ; nginx-1.12.2 ;工具:Source Insigh4.0
  • 指令
    1
    2
    gcc -c -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -o ngx_testpool.o  ngx_testpool.c
    gcc -o ngx_testpool ngx_testpool.o objs/src/core/ngx_palloc.o objs/src/os/unix/ngx_alloc.o
  • void *memcpy(void *des, const void *src, size_t n)
    • 将src指向的内存中的内容逐字节的拷贝到des指向的内存
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      #include <iostream>
      using namespace std;

      int main(int argc, char* argv[])
      {
      int a = 9;
      int b = 10;
      int* p = &a;
      int* q = &b; // q不能=null!因为是要把p指向的内存的内容拷贝到q的内存里。q如果是null则error
      memcpy(q, p, sizeof(int));
      cout << q << endl;
      cout << *q << endl;
      cout << p << endl;
      cout << *p << endl;
      }
      00000021ACF5F904
      9
      00000021ACF5F8E4
      9
  • ngx_testpool.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
    const char *fmt, ...)
    {

    }

    typedef struct Data stData;
    struct Data
    {
    // 多个指针
    char *ptr;
    FILE *pfile;
    char *ptr2;
    };


    typedef struct yData yData;
    struct yData
    {
    char *ptr;
    int x;
    int y;
    };



    void self_handler(void *p)
    {
    printf("self_handler\n");
    stData *q = (stData*) p;
    printf("free ptr mem!\n");
    free(q->ptr);
    printf("free ptr mem!\n");
    free(q->ptr2);
    printf("close file!\n");
    fclose(q->pfile);
    }


    void self_handler_02(void *p)
    {
    char *q = (char*)p;
    printf("self_handler_02\n");
    printf("free ptr mem!\n");
    free(q);
    }



    void main()
    {
    // 1. ngx_create_pool 造内存池
    // 第一块内存Block。里面有完整的ngx_pool_t
    // ngx_pool_t::max = min(512 - sizeof(ngx_pool_t) , 4095)
    ngx_pool_t *pool = ngx_create_pool(512, NULL);
    if(pool == NULL)
    {
    printf("ngx_create_pool fail...");
    return;
    }

    // 2. 小块内存以及外部资源
    // 向内存池申请小块内存
    stData *p1 = ngx_palloc(pool, sizeof(yData)); // 从小块内存池分配的
    if(p1 == NULL)
    {
    printf("ngx_palloc 128 bytes fail...");
    return;
    }
    // 小块内存保存的指针管理的外部资源
    p1->ptr = malloc(12);
    strcpy(p1->ptr, "hello world");
    p1->pfile = fopen("data.txt", "w");
    p1->ptr2 = malloc(15);
    strcpy(p1->ptr2, "hhhh");
    // 预置回调函数用于释放外部资源
    ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add(pool,sizeof(yData)); // 开辟内存,用于handler传参
    c1->handler = self_handler;
    memcpy(c1->data,p1,sizeof(yData)); // 用户只负责拷贝!!将外部资源拷贝一下到c1->data。将p1指针指向的内容逐字节拷贝到c->data指向的内存


    // 3. 大块内存以及外部资源
    // 向内存池申请大块内存
    yData *p2 = ngx_palloc(pool, 512); // 从大块内存池分配的
    if(p2 == NULL)
    {
    printf("ngx_palloc 512 bytes fail...");
    return;
    }
    // 外部资源
    p2->ptr = malloc(12);
    strcpy(p2->ptr, "hello world");

    // 预置回调函数用于释放外部资源
    ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add(pool,0); // 不开辟内存,直接让c->data指向要释放的内存
    c2->handler = self_handler_02;
    c2->data = p2->ptr;

    // 释放内存池
    ngx_destroy_pool(pool); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存

    return;
    }

    shc@shc-virtual-machine:~/code/nginx/nginx-1.12.2$ gcc -c -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -o ngx_testpool.o ngx_testpool.c
    shc@shc-virtual-machine:~/code/nginx/nginx-1.12.2$ gcc -o ngx_testpool.out ngx_testpool.o objs/src/core/ngx_palloc.o objs/src/os/unix/ngx_alloc.o
    shc@shc-virtual-machine:~/code/nginx/nginx-1.12.2$ ./ngx_testpool.out
    self_handler_02
    free ptr mem!
    self_handler
    free ptr mem!
    free ptr mem!
    close file!

内存对齐好处

  • 较少cpu的IO次数
  • 内存对齐博客待完善

gdb调试

  • gcc -g 生成的可执行文件可以调试
  • gdb …out
  • start 开始一行一行运行
  • n 下一行
  • s(step)进入函数
  • finish 跳出函数
  • quit 退出
  • l 当前附近代码

轮子-nginx内存池不足(我认为)

  • 链表管理
    • 链表的查找遍历时间复杂度是 O(n)。ngx_pfree 效率不高
  • 小内存块链表,current 问题:
    • 当遇到密集地分配比较大的小内存场景时,导致已分配结点,分配失败,failed 次数增加。current 指向新的结点,由于是单向链表,前面的结点其实还有足够的空闲空间分配给其它小内存的,导致空闲空间利用率不高。
  • ngx_reset_pool中没有释放外部资源。需要等到destroy时才可以释放。
  • ngx_reset_pool中,在重置小块内村的last指针时,有一部分没有重置,造成内存碎片。

我?

  • 移植之后解决了ngx_reset_pool中没有释放外部资源的问题
  • 解决了reset中last指针的问题。