不落辰

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

0%

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指针的问题。

new、malloc区别。free、delete区别

  • malloc free 称为C的库函数
  • new delete 称作运算符
    1
    2
    3
    4
    5
    int *p = malloc();
    free(p);
    ==================================
    int *p = new int(...);
    delete p;

malloc和new的区别?

  • new调用operator newoperator new调用malloc
  • malloc按字节开辟内存的;new开辟内存时需要指定类型 new int[10];所以malloc开辟内存返回的都是void*
  • malloc只负责开辟空间,new不仅仅有malloc的功能,可以进行数据的初始化new int(20); new int[20]();
  • malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int* p = (int*)malloc(sizeof(int));
    if (p == nullptr) cout << "wrong" << endl;

    try {
    int* pp = new int(10);
    }
    catch (const bad_alloc& e) {

    }

    // int *pp = new int[20]; 在堆上开辟后不会初始化
    int *pp = new int[20](); // 在堆上开辟后会初始化
    delete []pp;

new

  • 先分配memory,再调用ctor(构造函数)
  • Complex* pc = new Complex(1,2);
  • 编译器转化为
    1
    2
    3
    4
    Complex *pc;
    void* mem = operator new(sizeof Complex); //分配内存 operator new 内部调用malloc(n)
    pc = static_cast<Complex*>(mem); //强转
    pc->Complex::Complex(1,2); //构造 Complex::(pc,1,2); pc即为this

    operator new

  • operator new仅仅负责开辟内存,在内存上写什么、任何与对象内容有关系的事情都与operator new无关
    1
    2
    3
    4
    5
    6
    7
    void *operator new(size_t size)
    {
    void *p = malloc(size); // 按Byte
    if(p==nullptr)
    throw bad_alloc();
    return p;
    }

有几种new

1
2
3
4
5
6
7
int *p1 = new int(20);
int *p2 = new (nothrow) int;
const int *p3 = new const int(20);
// 定位new 在指定内存上构造对象
int data = 0;
int *p4 = new (&data) int(5);
cout<<data<<endl; // 5

free和delete的区别?

  • delete调用operator delete,operator delete 调用free
  • delete (int*)p: 调用析构函数;再free(p)

new和delete能混用吗?C++为什么区分单个元素和数组的内存分配和释放呢?
new delete
new[] delete[]
对于普通的编译器内置类型 new/delete[] new[]/delete

自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候,
会多开辟4个字节,记录对象的个数

delete

  • 先调用dtor,再释放内存memory
  • Complex *pc = new Complex(1,2);
    delete pc
  • 编译器转化为
    1
    2
    3
    Complex::~Complex(pc);  //dtor 调用析构函数是为了释放(基类、派生类)成员指针管理的内存,而不会释放本对象本身占据的内存。
    // 如果本对象是栈上的,那么离开作用域后会自动调用析构函数 ,然后释放内存(也就是说析构函数何释放本对象内存是两回事)。很可惜,new出来的对象在堆上,得手动调用operator delete释放。(所以要保证pc指向得位置是对的(虚基类里就不对))
    operator delete(pc); //释放memory 内部调用 free(pc)

operator delete

  • operator delete仅仅负责释放内存,在内存上析构什么对象、任何与对象内容有关系的事情都与operator new无关。之前就已经做完了。
    1
    2
    3
    4
    void operator delete(void *ptr)
    {
    free(ptr);
    }

new[] delete[] ; new delete

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
/*
* new:
* void *mem = operator new(size);
* p = static_case<>(mem);
* p->crot();;
*
* delete:
* p->dtor;
* operator delete
*/

void* operator new(unsigned long long size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new : " << p << endl;
return p;
}

void operator delete(void* ptr)
{
cout << "operator delete : " << ptr << endl;
free(ptr);
}

void* operator new[](unsigned long long size)
{
void* p = malloc(size);
if (p == nullptr) {
throw bad_alloc();
}
cout << "operator new[] " << p << endl;
return p;
}


void operator delete[](void *ptr)
{
cout << "operator delete[] " << ptr << endl;
free(ptr);
}
  • new[] delete[]正常使用
  • new[]
    • 除了开辟对象内存之外,还会在栈顶方向(p-8)开辟一块内存用于记录对象个数。
    • 用于释放数组时,得知需要调用几个对象的析构函数
    • delete时会从那块内存(p-8)开始释放
  • delete[]
    • 会从对象栈顶方向4/8bytes开始。也会把那块内存的数据当作要析构的对象个数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      Test* p2 = new Test[5]; 
      // 除了开辟对象内存之外,还会在栈顶方向(p-8)开辟一块内存用于记录对象个数。
      // delete时也会连带释放那块内存(p-8)
      cout << "p2:" << p2 << endl;
      delete[] p2; // Test[0]对象析构, 直接free(p2)

      operator new[] 0000021E55300DF0
      Test()
      Test()
      Test()
      Test()
      Test()
      p2:0000021E55300DF8
      ~Test()
      ~Test()
      ~Test()
      ~Test()
      ~Test()
      operator delete[] 0000021E55300DF0
  • new delete
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Test* p1 = new Test();
    cout << "p1 : " << p1 << endl;
    delete p1;

    operator new : 000001A1BF04D6B0
    Test()
    p1 : 000001A1BF04D6B0
    ~Test()
    operator delete : 000001A1BF04D6B0
  • new delete[]混用后果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class Test
{
public:
Test(int data = 10) { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
private:
int ma;
};

int main()
{
Test* p1 = new Test();
delete[]p1;
}

operator new : 000001D8BC681C50
Test()
p1 : 000001D8BC681C50
~Test()
~Test()
~Test()
~Test() ...死循环
  • new[] delete
    • 只会调用一个对象的析构函数,并且用于记录对象个数的内存没有被释放

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      Test* p2 = new Test[5];
      cout << "p2:" << p2 << endl;
      delete p2; // Test[0]对象析构, 直接free(p2)

      operator new[] 00000299D1C59BA0
      Test()
      Test()
      Test()
      Test()
      Test()
      p2:00000299D1C59BA8
      ~Test()
      operator delete : 00000299D1C59BA8

对象池

  • 简单来说:将对象所占的内存提前开辟出来,放到一个池子里面(链表维护),当需要构造对象的时候,就从池子里面取出内存来在那上面构造。

链式队列 + 内置对象池

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
114
115
#include<iostream>
using namespace std;

// 对象池 链式队列
template<typename T>
class Queue
{
public:
Queue()
{
_front = _rear = new QueueItem();
}
~Queue()
{
QueueItem *cur = _front , *ne=nullptr;
while(cur)
{
ne = cur->_next;
delete cur; // delete 时 会调用QueueItem类内重载的operator delete。while会将这些节点全部加入内存池。不过没释放。
cur = ne;
}
}

void push(const T& val)
{
_rear->_next = new QueueItem(val); // 产生节点
_rear = _rear->_next;
}

void pop()
{
if(empty()) return ;
QueueItem *first = _front->_next;
_front->_next = first->_next;
delete first;
if(_front->_next==nullptr)
{
_rear = _front;
}
return ;
}

T front() {return _front->_next->_data;}
bool empty() {return _front==_rear;}
private:
/*
* new : operator new ; 强转 ; 构造函数 ; 返回
*/
// 给QueueItem提供内存管理 operator new ; operator delete
struct QueueItem // 内置节点类型
{
QueueItem(T data = T() , QueueItem *next=nullptr)
:_data(data),_next(next){}

// 编译器会自动处理为static
// 对象池为空时,再造一个对象池。(这个对象池的节点和前一个对象池的节点不是割裂的。之后会通过pop()(_next)连上的
// 对象池不为空时,从对象池里取对象。
void* operator new(size_t size) // size没有用,只是为了构成重载
{
if(_itemPool==nullptr)
{
cout<<"!"<<endl;
// 分配足够大内存 用作空闲池
_itemPool = (QueueItem*) malloc(sizeof(QueueItem)*POOL_ITEM_SIZE);
QueueItem *p = _itemPool;
// 虽然还没构造这个QueueItem对象,不过QueueItem类型指针指向那里,就认为那里的内存存储的数据是QueueItem对象。p也就认为相应位置存在着一个相应成员
// operator new之后才调用ctor构造函数。构造函数的作用也就是给相应位置的内存存入相应数据。并没有开辟内存的作用
// p指向的内存已经被申请来,不用担心会被程序的其他指令要求占用
for(;p<_itemPool + POOL_ITEM_SIZE - 1;++p)
{
p->_next = p+1;
}
p->_next = nullptr; // 最后一个空闲节点的下一位置 为空 代表空闲池耗尽。走到nullptr就意味着我们要造新池子。
}
// 现在要从这堆空闲内存取出一个 取出链表头部的
QueueItem *p = _itemPool;
_itemPool = _itemPool->_next;
return p; // 接下来要通过p在p指向的内存上调用构造函数 (此时p的_next会被改变,不再指向下一个空闲节点,无所谓。本来就该这样。此时p应该在链式队列的链表上。而非对象池的链表上)
}

// 管理已经无用的内存块 将其放入空闲内存链表中
void operator delete(void *ptr)
{
// cout<<ptr<<endl;
// 头插法 加入对象池。而非释放这块内存。
QueueItem *p = static_cast<QueueItem*>(ptr);
p->_next = _itemPool->_next;
_itemPool->_next = p;
}

T _data;
QueueItem *_next;
static QueueItem *_itemPool; // 指向内存池的首个节点(即第一块空闲内存)
const static int POOL_ITEM_SIZE = 10000; // 新标准 static常量可以类内初始化。
};
QueueItem *_front; // 头节点(无意义)
QueueItem *_rear;
};

template<typename T> // 模板名 + 参数名 才是类型 所以是Queue<T>::
typename Queue<T>:: QueueItem*
Queue<T>::QueueItem::_itemPool = nullptr;

int main()
{
Queue<int> q;
for(int i=0;i<10000;++i)
{
q.push(rand()%20);
q.pop();
}
cout<<q.empty()<<endl;
}
// !
// 1
  • 新标准:静态常量可以在类里面初始化,不用在外面初始化。