先从一个简单的内存池(Nginx内存池,nginx-1.6.1版本)实现开始,来获取其大致的概念。一个内存池可以认为是一个内存分配器,有些内存池被设计成只能分配固定长度的内存块,而有些则设计成可分配不定长度的内存块。另一个特点是内存块的释放,在内存池被销毁后,从该内存池的分配的所有内存块都将会被销毁,可以称之为“一键销毁”功能!
接口设计:
Nginx内存池的核心接口,可以分成两大类(声明于ngx_palloc.h头文件中):
1、内存池操作接口
2、基于内存池的内存操作接口
3、接口的使用范式如下:
从接口的设计来看,一切还算比较明朗,但以下几点还是需要注意:
1) 从ngx_palloc()等内存分配函数的size参数可以看出,基于内存池的内存分配操作不是固定长度的。
2) 内存分配函数考虑了一些两种情况,内存初始化,以及内存地址对齐,因此内存分配操作可以按以下方式分类:
内存是否初始化 | 内存地址是否对齐 | |
ngx_palloc | 否 | 按内部默认方式对齐 |
ngx_pnalloc | 否 | 未对齐 |
ngx_pcalloc | 初始化为0 | 按内部默认方式对齐 |
ngx_pmemalign | 否 | 按用户指定方式对齐 |
3) 基于第1点,我们有理由相信ngx_create_pool()的size参数应该是限制该内存池所能分配的内存块大小的上限。但其实不然,后面关于实现的分析可以看到这点。
4) 在内存池生命周期内,可以通过ngx_free()归还内存块,但其实并不是所有从内存池分配的内存都可以通过该接口来释放的。
5)支持ngx_reset_pool()重置内存池操作
设计与实现:
在Nginx内存池的设计中,其所管理的内存有两种类型,block和large memory。
block是一个内存池每次从系统分配的固定长度的内存块(一般通过malloc分配),block的长度在创建内存池就已经决定(即由ngx_create_pool的size参数所决定),且对于一个内存池而言所有block的长度都是一致的。前面所讨论的ngx_create_pool函数的size参数表示的是,内存池内部实现中一个block的大小。一个block内存有三个方面的用处:1).用于block自身的管理,2).用于large memory的管理(只有需要分配large内存时才存在),3).分配给用户。从接口设计的角度讲,这个参数设计得并不好,因为它需要使用者了解内存池的内部实现,才能明白其准确意义!
large memory则指的是长度超过block限制大小或者是按用户指定内存地址对齐方式分配(即ngx_pmemalign函数)的内存块。
以上两种类型的内存在Nginx内存池中,各自通过链表管理起来。
Nginx内存池:
block:
large memory:
1.内存池管理函数的实现比较简单
点击(此处)折叠或打开
- /**创建内存池*/
- ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log)
- {
- ngx_pool_t *p;
- /*分配一个block(大小为size)的内存*/
- p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
- if (p == NULL) {
- return NULL;
- }
- /*初始化block管理数据
- *这是该内存池的第一个block,除了要一部分内存用于管理该block外,
- *还有一部分内存被用于管理内存池本身
- *由于ngx_pool_t 的定义是内嵌一个ngx_pool_data_t ,
- *因此总共用于管理block和内存池的数据长度就是sizeof(ngx_pool_t)*/
- 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;
- /*设置该内存池能从block分配的内存块的大小限制*/
- size = size - sizeof(ngx_pool_t);
- p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
- /*初始化内存池管理数据*/
- p->current = p;//从第一个block开始查询链表,即查询链表头
- p->chain = NULL;
- p->large = NULL;
- p->cleanup = NULL;
- p->log = log;
- return p;
- }
点击(此处)折叠或打开
- /**销毁内存池*/
- void ngx_destroy_pool(ngx_pool_t *pool)
- {
- ………
- /*遍历large内存链表释放large内存*/
- 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);
- }
- }
- ………
- /*遍历block链表, 释放block内存*/
- for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
- ngx_free(p);
- if (n == NULL) {
- break;
- }
- }
- /*注意以上两个for循环的次序不能颠倒,因为large内存的管理数据位于block中*/
- }
点击(此处)折叠或打开
- /**重置内存池*/
- void ngx_reset_pool(ngx_pool_t *pool)
- {
- ngx_pool_t *p;
- ngx_pool_large_t *l;
- /*遍历large链表释放内存给系统*/
- for (l = pool->large; l; l = l->next) {
- if (l->alloc) {
- ngx_free(l->alloc);
- }
- }
- /*遍历block链表, 但不释放内存给系统, 仅将block管理数据重置*/
- for (p = pool; p; p = p->d.next) {
- p->d.last = (u_char *) p + sizeof(ngx_pool_t);
- p->d.failed = 0;
- }
- /*重置查询表头*/
- pool->current = pool;
- pool->chain = NULL;
- pool->large = NULL;
- }
2.内存分配,我们从ngx_palloc开始分析,先看流程图
点击(此处)折叠或打开
- /**分配内存,返回内存地址按照某种方式对齐,内存未作任何初始化*/
- void *ngx_palloc(ngx_pool_t *pool, size_t size)
- {
- u_char *m;
- ngx_pool_t *p;
- /*判断分配大小是否超出block现在*/
- if (size <= pool->max) {
- /*大小没有超出block限制*/
- p = pool->current;
- /*dowhile 循环, 尝试从现存block中分配内存*/
- do {
- m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);//内存地址按NGX_ALIGNMENT对齐处理
- if ((size_t) (p->d.end - m) >= size) {
- p->d.last = m + size;
- return m;
- }
- p = p->d.next;
- } while (p);
- /*现存block无法满足分配需求,创建一个新的block用于内存分配*/
- return ngx_palloc_block(pool, size);
- }
-
- /*大小超出block限制,分配large内存块*/
- return ngx_palloc_large(pool, size);
- }
点击(此处)折叠或打开
- /**创建一个新的block*/
- static void *ngx_palloc_block(ngx_pool_t *pool, size_t size)
- {
- u_char *m;
- size_t psize;
- ngx_pool_t *p, *new, *current;
- /*计算block大小, 注意对于一个内存池而言,所有block的大小都是一致的.
- *为什么ngx_pool_t不维护这么一个域,而每次都去动态计算呢?*/
- psize = (size_t) (pool->d.end - (u_char *) pool);
- /*从系统中分配一个block, 并初始化block管理数据,注意管理数据所需的内存也来自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;
- /*注意第一个block已经在ngx_create_pool中创建了,所有本函数创建的block均由ngx_pool_data_t 管
- * 理,因此此处偏移sizeof(ngx_pool_data_t)就ok,没有必要偏移sizeof(ngx_pool_t)*/
- m += sizeof(ngx_pool_data_t);
- m = ngx_align_ptr(m, NGX_ALIGNMENT);//内存地址,按NGX_ALIGNMENT方式对齐
- new->d.last = m + size; //设置有效内存起始位置
- /*更新查询链表头,跳过所有内存分配次数大于4(为什么是4?)的block*/
- 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;//将新建的block加入block链表
- pool->current = current ? current : new;
- return m;
- }
点击(此处)折叠或打开
- /**分配large内存*/
- static void *ngx_palloc_large(ngx_pool_t *pool, size_t size)
- {
- void *p;
- ngx_uint_t n;
- ngx_pool_large_t *large;
- /*从系统分配large内存块*/
- p = ngx_alloc(size, pool->log);
- if (p == NULL) {
- return NULL;
- }
- /*尝试寻找large内存链表中无效的管理数据,更新该管理数据,
- *将新分配的large内存块直接加入large内存链表*/
- n = 0;
- for (large = pool->large; large; large = large->next) {
- if (large->alloc == NULL) {
- large->alloc = p;
- return p;
- }
- if (n++ > 3) {//为啥是3?
- break;
- }
- }
- /*需要从block中重新分配一个块内存用于管理新分配的large内存块
- *注意这里调用了ngx_palloc, 这是一个隐藏比较深的递归调用*/
- 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;
- /*对比ngx_palloc_large 与ngx_palloc_block ,发现,block的管理数据从block自身分出一般内存来使用的,*而large内存块的管理数据,则不在large内存块中,需另行分配(其实就从block中分配)*/
- return p;
- }
ngx_pnalloc的实现除了不对内存地址做对齐处理外,其他与ngx_pnalloc一致;ngx_pcalloc的实现是基于ngx_pnalloc分配内存,然后,将内存块清0。接下来是ngx_pmemalign。
点击(此处)折叠或打开
- /**分配内存,返回内存地址按alignment对齐,内存未作任何初始化*/
- void * ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
- {
- void *p;
- ngx_pool_large_t *large;
- /*用户指定alignment对齐要求,直接从系统中分配large内存块*/
- p = ngx_memalign(alignment, size, pool->log);
- if (p == NULL) {
- return NULL;
- }
-
- /*直接从block中分配管理large内存块的内存,
- * 为什么不像ngx_palloc_large那样先查找有无可用的管理内存块?*/
- large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
- if (large == NULL) {
- ngx_free(p);
- return NULL;
- }
- /*将large内存块加入链表中*/
- large->alloc = p;
- large->next = pool->large;
- pool->large = large;
- return p;
- }
点击(此处)折叠或打开
- /**释放内存*/
- ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
- {
- ngx_pool_large_t *l;
- /*遍历large链表,释放large内存块,
- *注意此处并没有释放管理large内存块的内存(即large链表节点),
- *而仅是将其alloc指针设为NULL*/
- 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;
- }
最后以一个内存池快照作总结: