Nginx源码分析-内存池
来源:互联网 发布:淘宝网广场舞裙子套装 编辑:程序博客网 时间:2024/06/06 14:13
总的来说内存池基本都有一个宗旨:申请大块内存,避免“细水长流”。
Nginx在特定的生命周期统一建立内存池,需要内存时统一在内存池中申请内存,并且在适当的时候释放内存池的内存。
内存池主要由两个结构来维护,一个是头部一个是数据部。此处数据部就是用来分配小块内存的地方。
ngx_pool_data_t用来维护内存池数据块
下面来看一下这个结构体
<span style="font-size:12px;">typedef struct { u_char *last; //当前内存分配结束位置,即下一段可分配内存的起始位置 u_char *end; //内存池结束位置 ngx_pool_t *next; //链接到下一个内存池 ngx_uint_t failed;//统计该内存池不能满足分配请求的次数} ngx_pool_data_t;</span>ngx_pool_t维护整个内存池的头部信息,结构体如下
struct ngx_pool_s { ngx_pool_data_t d; //数据块 size_t max; //数据块大小,即小块内存的最大值 ngx_pool_t *current; //保存当前内存值 ngx_chain_t *chain; //可以挂一个chain结构 ngx_pool_large_t *large; //分配大块内存用,即超过max的内存请求 ngx_pool_cleanup_t *cleanup; //挂载一些内存池释放的时候,同时释放的资源 ngx_log_t *log;};上面两个结构就可以创建内存池了,用来创建内存池的接口是:ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)(位于src/core/ngx_palloc.c中);
下面来分析这个函数
00015: ngx_pool_t *00016: ngx_create_pool(size_t size, ngx_log_t *log)00017: {00018: ngx_pool_t *p;00019:00020: p = <span style="color:#ff0000;">ngx_memalign(NGX_POOL_ALIGNMENT, size, log)</span>;//此函数下面分析00021: if (p == NULL) {00022: return NULL;00023: }00024:00025: p->d.last = (u_char *) p + sizeof(ngx_pool_t); //last指向ngx_pool_t结构体之后数据取起始位置00026: p->d.end = (u_char *) p + size; //end指向分配的整个size大小的内存的末尾00027: p->d.next = NULL;00028: p->d.failed = 0;00029:00030: size = size - sizeof(ngx_pool_t);00031: p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; //最大不超过4095B00032:00033: p->current = p;00034: p->chain = NULL;00035: p->large = NULL;00036: p->cleanup = NULL;00037: p->log = log;00038:00039: return p;00040: }调用这个函数就可以创建一个大小为size的内存池了,内存池的结构图如下
图中红色的字段表示上述的第一个结构,维护数据部分:end表示内存池的结束位置,所有内存分配不可以越过end。蓝色的max字段表示数据段最大大小是多少,如果申请的内存大于max,就认为申请的是大内存,就会在large字段下分配内存,如果小于max字段就在数据段进行内存分配,并且移动last指针
内存池物理结构图如下
紧接着,咱们就来分析下上面代码中所提到的:ngx_memalign()函数
- void *
- ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
- {
- void *p;
- int err;
- err = posix_memalign(&p, alignment, size);
- //该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。
- 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;
- }
- //从这个函数的实现体,我们可以看到p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
- //函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h文件中:
- #define NGX_POOL_ALIGNMENT 16
因此,nginx的内存池分配,是以16字节为边界对齐的。
分配内存调用函数
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
我们主要来分析一下ngx_palloc函数
<span style="font-size:12px;"><span style="color:#333333;">00115: void *00116: ngx_palloc(ngx_pool_t *pool, size_t size)00117: {00118: u_char *m;00119: ngx_pool_t *p;00120:00121: if (size <= pool->max) {//判断待分配内存与max值00122:00123: p = pool->current; //小于max值,则从current节点开始遍历pool链表00124:00125: do {00126: m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);//</span><span style="color:#ff0000;">对齐内存指针,加快存取速度</span><span style="color:#333333;">00127:00128: if ((size_t) (p->d.end - m) >= size) {00129: p->d.last = m + size; //在该节点指向的内存块中分配size大小的内存00130:00131: return m;00132: }00133:00134: p = p->d.next;00135:00136: } while (p);00137:00138: return </span><span style="color:#ff0000;">ngx_palloc_block(pool, size)</span><span style="color:#333333;">; //链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存00139: }00140:00141: return ngx_palloc_large(pool, size); //大于max值,则在large链表里分配内存00142: }</span></span>
上面函数中调用了ngx_palloc_block,下面来分析一下这个函数
<span style="font-size:12px;">00175: static void *00176: ngx_palloc_block(ngx_pool_t *pool, size_t size)00177: {00178: u_char *m;00179: size_t psize;00180: ngx_pool_t *p, *new, *current;00181:00182: psize = (size_t) (pool->d.end - (u_char *) pool); //计算pool的大小00183:00184: m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配一块与pool大小相同的内存00185: if (m == NULL) {00186: return NULL;00187: }00188:00189: new = (ngx_pool_t *) m;00190:00191: new->d.end = m + psize; //设置end指针00192: new->d.next = NULL;00193: new->d.failed = 0;00194:00195: m += sizeof(ngx_pool_data_t); //让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置00196: m = ngx_align_ptr(m, NGX_ALIGNMENT); //按4字节对齐00197: new->d.last = m + size; //在数据区分配size大小的内存并设置last指针00198:00199: current = pool->current;00200:00201: for (p = current; p->d.next; p = p->d.next) {00202: if (p->d.failed++ > 4) { //failed的值只在此处被修改00203: current = p->d.next; //失败4次以上移动current指针00204: }00205: }00206:00207: p->d.next = new; //将这次分配的内存块new加入该内存池00208:00209: pool->current = current ? current : new;00210:00211: return m;00212: }</span>注意:该函数分配一块内存后,last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置。而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。
内存池的物理结构如下图
内存池第一块内存前40字节为ngx_pool_t结构,后续加入的内存块前16个字节为ngx_pool_data_t结构,这两个结构之后便是真正可以分配内存区域。
大块内存的分配不会在内存池中进行申请内存,而是直接向操作系统申请内存(与直接使用malloc申请一样),然后将申请到的内存挂到内存池头部large字段下。内存池的作用在于解决小块内存池的频繁申请问题的,对于这种大块内存是可以忍受直接申请的。
销毁内存池
接下来,咱们来看内存池的销毁函数,pool指向需要销毁的内存池
- 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);
- }
- }
- //前面讲到,cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,
- //清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。
- 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);
- }
- }
- //这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,
- //即大内存块就是通过malloc和free操作进行管理的。
- #if (NGX_DEBUG)
- /**
- * we could allocate the pool->log from this pool
- * so we can not use this log while the free()ing the pool
- */
- 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;
- }
- }
- //只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。
- #endif
- for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {
- ngx_free(p);
- if (n == NULL) {
- break;
- }
- }
- }
- //该片段彻底销毁内存池本身。
cleanup资源
可以看到所有挂载在内存池上的资源将形成一个循环链表,一路走来,发现链表这种看似简单的数据结构却被频繁使用。
由图可知,每个需要清理的资源都对应有一个头部结构,这个结构中有一个关键的字段handler,handler是一个函数指针,在挂载一个资源到内存池上的时候,同时也会注册一个清理资源的函数到这个handler上。即是说,内存池在清理cleanup的时候,就是调用这个handler来清理对应的资源。
比如:我们可以将一个开打的文件描述符作为资源挂载到内存池上,同时提供一个关闭文件描述的函数注册到handler上,那么内存池在释放的时候,就会调用我们提供的关闭文件函数来处理文件描述符资源了。
内存的释放
nginx只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问题,nginx利用了web server应用的特殊场景来完成;
一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。
也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,为此连接创建一个内存池;连接上到来一个request后,又为request创建起一个内存池。
这样,在request被处理完后,就会释放request的整个内存池,但是不释放connection的内存池,因为还可能又其他请求;连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。
小结:通过内存的分配和释放可以看出,nginx只是将小块内存的申请聚集到一起申请,然后一起释放。避免了频繁申请小内存,降低内存碎片的产生等问题。
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- nginx源码分析--内存池
- Nginx内存池实现源码分析
- [nginx源码分析]ngx内存池实现
- nginx源码分析--内存池ngx_poll_t 内存池管理
- Nginx源码分析---内存池结构ngx_pool_t及内存管理
- 服务器数据库系列 - Nginx源码分析-内存池
- Nginx高级数据结构源码分析(四)-----内存池
- C++ GUI QT 第4版 第六章 布局管理 (1)
- 我的编程之路(十) 第一份任务
- 链表算法汇总
- MySQL 数据库表操作和数据导入导出方式总结笔记
- HttpURLConnection使用详解
- Nginx源码分析-内存池
- 解决新建Support7Demos的sample时出现编译错误和运行报错出现的问题
- Bone Collector
- hdu 2955 Robberies
- 从 Racket 入门函数式编程
- ACM比赛经验
- C/C++用匿名数据结构实现时间和空间名利双收
- MongoDB Doc
- POJ 2411 Mondriaan's Dream(状压DP)