标签:
先来看内存池的实现,nginx的内存池实现的非常简单。
这里内存池的一些图表可以看老朱同学的slides :
http://blog.zhuzhaoyuan.com/2009/09/nginx-internals-slides-video/
当内存池初始化的时候(下面会分析到)ngx_poll_s只相当于内存池的一个头,保存了当前内存池的一些必要信息而已。
当从内存池存取数据的时候,nginx是分为两种类型来处理得,一种是小块数据,它是直接从内存池中取得数据,另一方面,当为大块数据时,它是直接malloc一块数据(也就是从内存池外部分配数据),然后保存这个指针到内存池。可以看到很多内存池,比如py的内存池实现,也基本是这个思想。这里的细节,我们将会在下面分析内存池相关函数的时候详细分析。
这里还有一个要注意就是这些子内存池和父内存池是不一样的,我们后面分析函数的时候会详细介绍。
大小块数据得分割线是你创建内存池时传递进来的size和页大小之间的最小值。
下面就是内存池的结构:
- 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_pool_data_t。
这个结构很简单,它就是包含了我们所需要操作这个内存池的数据的一些指针。
其中last表示当前的数据区的已经使用的数据的结尾。
end表示当前的内存池的结尾。也就是说end-last就是内存池未使用的大小。
我们要知道当我们从一个内存池请求一块内存时,如果此时内存池已经满掉,这是一般都是扩大内存池,而nginx中不是这么做的,它会直接再分配一个内存池,然后链接到data的next指针上。也就是说在nginx中,其实每个内存池都会包含一些子内存池。因此我们请求内存的时候都会遍历这些子内存池。
failed域主要是为了标记我们请求内存(小块内存)由于内存池空间不够,我们需要重新分配一个子内存池的次数。 下面分析函数的时候会再会看到这个域。
- typedef struct {
- u_char *last;
- u_char *end;
- ngx_pool_t *next;
- ngx_uint_t failed;
- } ngx_pool_data_t;
ngx_chain_t这里就先不介绍了,我们现在只需要知道它是与buf相关的。
然后是ngx_pool_large_s,它表示了大块的内存。可以看到这个结构非常简单,就是一个指针指向下一块large,一个alloc指向数据。
- struct ngx_pool_large_s {
- ngx_pool_large_t *next;
- void *alloc;
- };
接下来是ngx_pool_cleanup_s,这个结构用来表示内存池中的数据的清理handler。
其中handler表示清理函数。
data表示传递给清理函数的数据。
next表示下一个清理handler,也就是说当destroy这个pool的时候会遍历清理handler链表,然后调用handler。
- struct ngx_pool_cleanup_s {
- ngx_pool_cleanup_pt handler;
- void *data;
- ngx_pool_cleanup_t *next;
- }
通过ngx_create_temp_buf创建一个buff,然后通过ngx_alloc_chain_link创建一个chain,然后通过cl->buf = rb->buf;将buff链接到chain中.
下面就是pool的内存图,摘自老朱同学的nginx internal。
ok,接下来我们来通过分析具体的函数,来更好的理解pool的实现。
首先来看ngx_create_pool也就是创建一个pool。
这里我们要知道虽然我们传递进来的大小是size可是我们真正能使用的数据区大小是要减去ngx_pool_t的大小的。
- #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
-
- ngx_pool_t *
- ngx_create_pool(size_t size, ngx_log_t *log)
- {
- ngx_pool_t *p;
- p = ngx_alloc(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;
- }
接下来我们来看如何从内存池中分配一块内存来使用。在nginx中有3个函数可以使用,分别是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配的内存会对齐。第二个函数用来分配一块清0的内存,第三个函数分配的内存不会对齐。
由于这三个函数差不多,因此我们就只分析一个就够了。我们就来看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 {
- 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);
- }
接下来就来看ngx_palloc_block的实现,这个函数主要就是重新分配一块内存池,然后链接到当前内存池的数据区指针。
然后要注意这里重新new的这个内存池大小是和它的父内存池一样大的。
并且新得内存池只保存了ngx_pool_data_t这个结构,也就是说数据区直接跟在ngx_pool_data_t下面。
- 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_alloc(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;
-
- 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;
- }
这里解释一下为什么这样设置current,这里的主要原因是我们在ngx_palloc中分配内存是从current开始的,而这里也就是设置current为比较新分配的内存。而当failed大于4说明我们至少请求了4次内存分配,都不能满足我们的请求,此时我们就假设老的内存都已经没有空间了,因此我们就从比较新的内存块开始。
接下来是ngx_palloc_large,这个函数也是很简单就是malloc一块ngx_poll_large_t,然后链接到主的内存池上。
- 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);
- if (p == NULL) {
- return NULL;
- }
-
- n = 0;
- for (large = pool->large; large; large = large->next) {
- if (large->alloc == NULL) {
- large->alloc = p;
- return p;
- }
- if (n++ > 3) {
- break;
- }
- }
-
- 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;
- }
ok,分配看完了,我们来看释放。这里要知道在nginx中,只有大块内存提供了free接口,可以供我们收工释放,而小块内存是没有提供这个接口的。也就是说小块内存只有当整个内存池被desrtoy掉时,才会被释放。
这里一些简单的函数就不分析了。
比如ngx_pfree(ngx_pool_t *pool, void *p),这个函数就是从pool的large链表中找到p,然后free掉它。
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
这个函数也就是添加一个ngx_pool_cleanup_t到当前的pool上,然后返回,我们此时就能通过返回的结构来给对应的handler赋值。
而ngx_pool_cleanup_t这个主要是当内存池destroy的时候我们可能需要做一些清理工作,此时我们就能add这些清理handler到pool中,然后当内存池要释放的时候就会自动调用。
ok,现在来看pool 被free的实现。
这个函数主要是遍历large,遍历current,然后一一释放。
- void
- ngx_destroy_pool(ngx_pool_t *pool)
- {
- ngx_pool_t *p, *n;
- ngx_pool_large_t *l;
- ngx_pool_cleanup_t *c;
-
- 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);
- }
- }
-
- for (l = pool->large; l; l = l->next) {
-
- ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
-
- if (l->alloc) {
- ngx_free(l->alloc);
- }
- }
-
- for (p = pool, n = pool->d.next;
- ngx_free(p);
-
- if (n == NULL) {
- break;
- }
- }
- }
通过上面我们可以看到在nginx中内存池中的小块数据是从来不释放的,这样就简化了内存池的操作。
接下来我们来看buf的实现。
buf分为两种类型,一种是file,一种是memory.因此这里会有文件的一些操作域。
可以看到buf相对于pool多了一个pos域(file_pos).这里我们要知道我们发送往套接字异或者其他的设备,我们这里会现将数据放到buf中,然后当设备或者套接字准备好了,我们就会从buf中读取,因此这里pos指针就是放到buf中的已经被执行的数据(也就是已经送往套接字)的位置。
- struct ngx_buf_s {
- u_char *pos;
- u_char *last;
- off_t file_pos;
- off_t file_last;
- u_char *start;
- u_char *end;
-
- ngx_buf_tag_t tag;
- ngx_file_t *file;
- ngx_buf_t *shadow;
-
-
- unsigned temporary:1;
-
- unsigned memory:1;
-
- unsigned mmap:1;
-
- unsigned recycled:1;
-
- unsigned in_file:1;
- unsigned flush:1;
- unsigned sync:1;
- unsigned last_buf:1;
- unsigned last_in_chain:1;
-
- unsigned last_shadow:1;
- unsigned temp_file:1;
-
-
- };
ok,接下来我们来看如何创建一个buf.在nginx中一般都是调用ngx_create_temp_buf来创建一个buf。函数很简单,就是从pool中分配内存然后初始化相关域。
- ngx_buf_t *
- ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
- {
- ngx_buf_t *b;
-
- b = ngx_calloc_buf(pool);
- if (b == NULL) {
- return NULL;
- }
-
- b->start = ngx_palloc(pool, size);
- if (b->start == NULL) {
- return NULL;
- }
-
- b->pos = b->start;
- b->last = b->start;
- b->end = b->last + size;
- b->temporary = 1;
-
- return b;
- }
然后我们来看chain的实现,chain其实也就是多个buf组合而成的。它主要是用来缓存一些未发出去的,或者接收的buf 以及 writev以及readv而存在的。
ok我们来看chain的实现,其实它的实现很简单,就是一个单链表。
- struct ngx_chain_s {
- ngx_buf_t *buf;
- ngx_chain_t *next;
- };
然后来看如何创建一个chain。这里取得一个chain后直接返回给供其他模块使用:
- ngx_chain_t *
- ngx_alloc_chain_link(ngx_pool_t *pool)
- {
- ngx_chain_t *cl;
-
- cl = pool->chain;
- if (cl) {
- pool->chain = cl->next;
- return cl;
- }
-
- cl = ngx_palloc(pool, sizeof(ngx_chain_t));
- if (cl == NULL) {
- return NULL;
- }
-
- return cl;
- }
接下来就是两个重要的chain,它们其实就是对chain再进行了一次封装。
1 ngx_output_chain_ctx_t , 这个chain主要是管理输出buf。
2 ngx_chain_writer_ctx_t 这个主要是用在upstream模块。
因此我们主要来看ngx_output_chain_ctx_t。
ngx_output_chain_ctx_t,它包含了三种类型的chain,分别是in,free以及busy。
现在来介绍这几个重要的域:
buf : 这个域也就是我们拷贝数据的地方,我们一般输出的话都是从in直接copy相应的size到buf中。
in : 这个就是我们保存那些需要发送数据的地方。
free : 这个保存了一些空的buf,也就是说如果free存在,我们都会直接从free中取buf到前面的buf域。
busy :这个保存了已经发送完毕的buf,也就是每次我们从in中将buf读取完毕后,确定数据已经取完,此时就会将这个chain拷贝到busy中。然后将比较老的busy buf拷贝到free中。
output_filter是一个回调函数,用来过滤输出。
剩下的就是一些标记域。
- typedef struct {
- ngx_buf_t *buf;
- ngx_chain_t *in;
- ngx_chain_t *free;
- ngx_chain_t *busy;
-
- unsigned sendfile:1;
- unsigned directio:1;
- #if (NGX_HAVE_ALIGNED_DIRECTIO)
- unsigned unaligned:1;
- #endif
- unsigned need_in_memory:1;
- unsigned need_in_temp:1;
-
- ngx_pool_t *pool;
- ngx_int_t allocated;
- ngx_bufs_t bufs;
- ngx_buf_tag_t tag;
-
- ngx_output_chain_filter_pt output_filter;
- void *filter_ctx;
- } ngx_output_chain_ctx_t;
它对应的主要是ngx_output_chain函数。这个函数主要功能就是拷贝in chain的数据到buf域中。这个函数很复杂,我们这里简要分析一下:
- ngx_int_t
- ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
- {
- off_t bsize;
- ngx_int_t rc, last;
- ngx_chain_t *cl, *out, **last_out;
-
- ...........................................
-
-
-
- if (in) {
- if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {
- return NGX_ERROR;
- }
- }
-
- out = NULL;
- last_out = &out;
- last = NGX_NONE;
-
- for ( ;; ) {
-
- while (ctx->in) {
-
-
-
- bsize = ngx_buf_size(ctx->in->buf);
- ..................................................
-
- if (ctx->buf == NULL) {
- rc = ngx_output_chain_align_file_buf(ctx, bsize);
-
- if (rc == NGX_ERROR) {
- return NGX_ERROR;
- }
-
- if (rc != NGX_OK) {
- if (ctx->free) {
-
-
-
- cl = ctx->free;
- ctx->buf = cl->buf;
- ctx->free = cl->next;
-
- ngx_free_chain(ctx->pool, cl);
-
- } else if (out || ctx->allocated == ctx->bufs.num) {
-
- break;
-
- }
-
- else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) {
- return NGX_ERROR;
- }
- }
- }
-
- rc = ngx_output_chain_copy_buf(ctx);
-
- if (rc == NGX_ERROR) {
- return rc;
- }
-
- if (rc == NGX_AGAIN) {
- if (out) {
- break;
- }
-
- return rc;
- }
-
-
-
- if (ngx_buf_size(ctx->in->buf) == 0) {
- ctx->in = ctx->in->next;
- }
-
- cl = ngx_alloc_chain_link(ctx->pool);
- if (cl == NULL) {
- return NGX_ERROR;
- }
-
- cl->buf = ctx->buf;
- cl->next = NULL;
- *last_out = cl;
- last_out = &cl->next;
- ctx->buf = NULL;
- }
-
- if (out == NULL && last != NGX_NONE) {
-
- if (ctx->in) {
- return NGX_AGAIN;
- }
-
- return last;
- }
-
- last = ctx->output_filter(ctx->filter_ctx, out);
-
- if (last == NGX_ERROR || last == NGX_DONE) {
- return last;
- }
-
- ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag);
- last_out = &out;
- }
- }
这里我只是简要的分析了下,详细的还需要接合其他模块来看。
最后来看ngx_chain_writer_ctx_t,这个主要用于ustream(由于没看这个模块,因此不理解这里为什么要多出来个writer).大概看了,觉得应该是ustream模块发送的数据量比较大,因此这里通过这个chain来直接调用writev来将数据发送出去。
- typedef struct {
- ngx_chain_t *out;
-
- ngx_chain_t **last;
- ngx_connection_t *connection;
- ngx_pool_t *pool;
- off_t limit;
- } ngx_chain_writer_ctx_t;
这里我们要知道out是会变化的。每次输出后,这个都会指向下一个需要发送的chain。
- ngx_int_t
- ngx_chain_writer(void *data, ngx_chain_t *in)
- {
- ngx_chain_writer_ctx_t *ctx = data;
-
- off_t size;
- ngx_chain_t *cl;
- ngx_connection_t *c;
-
- c = ctx->connection;
- for (size = 0; in; in = in->next) {
- ....................................
-
- size += ngx_buf_size(in->buf);
-
-
- cl = ngx_alloc_chain_link(ctx->pool);
- if (cl == NULL) {
- return NGX_ERROR;
- }
-
- cl->buf = in->buf;
- cl->next = NULL;
- *ctx->last = cl;
- ctx->last = &cl->next;
- }
-
- ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
- "chain writer in: %p", ctx->out);
-
- for (cl = ctx->out; cl; cl = cl->next) {
-
- size += ngx_buf_size(cl->buf);
- }
-
- if (size == 0 && !c->buffered) {
- return NGX_OK;
- }
-
- ctx->out = c->send_chain(c, ctx->out, ctx->limit);
-
- ........................
-
- return NGX_AGAIN;
- }
nginx的内存管理
标签:
原文地址:http://www.cnblogs.com/405845829qq/p/4398049.html