一:设计思路
nginx内存池的主要思路是预先申请一块空间,以后每次分配小块内存时从这块空间中划分,从而减少了内存申请次数,并避免产生过多的内存碎片.
当申请大块内存(大于阈值或预申请空间大小)时, 则向系统单独申请新的空间. 阈值为内存分页大小,由系统调用getpagesize()获得
释放时如果是大块内存,则直接释放,小块内存则不进行处理.
二,结构详解
注: 按nginx命名惯例, ngx_xxx_t 定义时都写为 ngx_xxx_s 再typedef成 nginx_xxx_t
ngx_pool_t 结构定义:
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; };
1,ngx_pool_data_t d; 管理预申请的内存块,本文称为block. 它是一个链表,当内存池空间不足时会进行扩容, 每次扩容生成一个新的block,它们组成一个链表.
2,max: 用来界定大块内存和小块内存的阈值. 它是系统阈值和预申请空间的较小者.
3 current指针, 当前工作的ngx_pool_data_t, 如前所述,d 是一个链表,每次申请空间时会选取一块合适的预申请空间进行分配,但这个选取并不是从链表头部开始,而是从current开始
4, chain 这个参数好像没有用到
5,large : 这是一个链表,用来管理向系统申请的大块内存.
6,cleanup: 这个好像是用来管理通过内存池申请的套接字等文件,在destroy的时候关闭它们,本文不进行讨论
7, log nginx的log系统, 本文不讨论.
ngx_pool_data_t 结构定义:
typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed; } ngx_pool_data_t;
ngx_pool_data_t 用来管理每一块(block)预申请的内存
1,last: 上一次分配内存的地址
2,end: 所申请内存空间的结尾地址
3,next: 下一块预申请空间
4,failed: 分配空间的失败次数, 每当向pool申请小块空间时,如果所有当前block都分配失败,就会申请新的block, 这是每个block.failed 加1,如果这个数值超过4, current指针就会指向这个block.next. 因为每次分配内存从current开始,所以pool将不再尝试从failed>4的block中申请空间
ngx_pool_large_t 结构定义:
struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
ngx_pool_large_t 用来管理单独向系统申请的大块内存
1,next 下一个large 元素
2,alloc 所申请的内存空间
三:主要API
(1),创建 ngx_create_pool
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);1,通过ngx_memalign申请内存, 根据操作系统的不同调用posix_memalign(freebsd) , malloc(early version)等
2,根据所申请空间初始化第一个block,并将current指向它.
3,计算max, 如前文所述,是size和系统阈值的较小者
4,将large等指针置空
初始化后的内存分配如图所示:
(2),申请空间 ngx_palloc
void *ngx_palloc(ngx_pool_t *pool, size_t size);1,根据max判断申请的是大块内存还是小块内存
2,如果是大块内存,通过 ngx_palloc_large 单独向系统申请
3,如果是小块内存,则从current开始遍历,如果有block的可用空间 > size, 就直接分配
4,如果所有block都不能完成分配, 则通过 ngx_palloc_block 创建新的block并进行分配
(2.1) 静态函数 ngx_palloc_block
该函数和下面的ngx_palloc_large 是static 函数,不能被外部访问
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size)1,新申请一块内存, 大小与第一块相等, 建立一个新的block插在链表d的尾部
2,对current及current之后的所有block.faild +1, 如果 failed>4 , current 指向其下一个block
3,在新block上分配空间
(2.2) 静态函数ngx_palloc_large
static void * ngx_palloc_large(ngx_pool_t *pool, size_t size)1,申请一块大小为size的内存p
2,遍历large链表的前3个元素, 如果其alloc为NULL, 则将其指向p 并返回. alloc为空的large元素是在ngx_pfree的时候产生的,但nginx只检查前3个large元素
3,新建一个ngx_pool_large_t ,将p赋给它的alloc,并插入large链表的首部.
(3) 释放函数 ngx_pfree
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)1,如果p出现在large链表中,就释放该节点的alloc并置NULL, 注意并不会释放该节点本身,因此会产生上文所说的alloc的large节点, 返回NGX_OK(0)
2, 否则说明p是小块内存, 不做任何操作并返回NGX_DECLINED (一个负值)
(4) 重置函数 ngx_reset_pool
void ngx_reset_pool(ngx_pool_t *pool)1,释放所有large节点的alloc成员, 并释放large节点本身
2,将每个block置成初始状态, 即将last指针提前至ngx_pool_t结构体本身之后
(5)销毁函数ngx_destroy_pool
void ngx_destroy_pool(ngx_pool_t *pool)1,销毁每个large节点
2,销毁每个block
四:其他
(1), 每个large节点和本文未讨论的cleanup等结构体本身都是由pool申请的, 所以销毁时都是先销毁这些再销毁block
(2) 关于内存对齐问题, 简单来说内存对齐是指申请的空间一定要被2的n次整除,即后n位为0, 这由硬件和os决定,可以提高寻址速度, 详情请baidu. 通过ngx_palloc申请的空间都是有内存对齐的, 但这回损失一定空间, nginx_pnalloc则不会进行对齐, 除了这一点与ngx_pnalloc相同
(3) 类似calloc, nginx也提供了ngx_pcalloc函数, 只是一个初始化了空间的ngx_palloc
(4) 我的nginx 源码版本是 nginx-1.4.7 for redhat