博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Nginx 内存管理源码分析
阅读量:3942 次
发布时间:2019-05-24

本文共 6884 字,大约阅读时间需要 22 分钟。

文章目录

Nginx 介绍

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

Nginx 内存管理用到的数据数据结构

typedef struct {
u_char *last; //指向内存分配的起始位置 u_char *end; //指向内存开辟的结束位置 ngx_pool_t *next; //指向下一个内存池中的内存块 ngx_uint_t failed; //当前内存块不满足分配的次数} ngx_pool_data_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next; //指向下一个大内存块的位置 void *alloc; //指向当前大内存块的位置};
struct ngx_pool_s {
ngx_pool_data_t d; size_t max; //小块内存块的最大值 ngx_pool_t *current; //指向开始分配内存的内存池 ngx_chain_t *chain; //该指针挂在一个ngx_chain_t结构 ngx_pool_large_t *large; //指向大块内存分配的起始地址 ngx_pool_cleanup_t *cleanup; // 挂在释放内存时需要清理资源的一些操作 ngx_log_t *log; //内存分配相关的日志};

在这里插入图片描述

图虽然不好看,但能够说明上面结构体之间的关系
在这里插入图片描述
上图显示了大内块之间的连接关系,并不是大内存块之间相互连接,而是每一个大内存块都在内存池上有一个对应的ngx_pool_large_s 进行维护,这样内存块之间存储数据的部分与两个内存块之间的链接关系(next指针)分开存储。这样使得分配给用户的内存就不必保存该内存在链表中的关系了。
现在我们了解了内存池的内部结构了,下面我们看一下具体的分配算法

分配算法

1、当用户从刚刚初始化的内存池中申请一块大小为 size 的内存时

首先 内存池先比较size与内存池中的max的大小关系
如果size > max 则调用malloc 分配一个大内存块,并将大内存块链接入内存池的large链表
否则,直接

p = last;last = last - size;return p;

当然,源代码肯定没有这么随便,先了解一下大概的过程,再来看源代码

2、用户不可能内存都能从第一个内存池中获取到内存块

当size <= max 并且内存池中剩余的内存大小不能满足用户的需求时,这时候我们会先检查本内存池的next 指向是否为空
若不为空,则循环向后检索,直到遇到能够需求的内存池为止。
若为空,则重新申请一块内存,与前面的内存池的的大小相同,然后填上相应的标识信息,从新的内存池中分配一块给用户,然后将该内存池链入总内存池

3、既然每次都要检索小的内存池,如果我们每次都从第一个进行检索,在内存池分配有了长时间以后,检索的效率就会越来越低,因为前面的内村块已经内存块已经分配的差不多了,不能满足大部分用户的需要了,这里Nginx也考虑到了,在最开始的结构体中有一个failed 和 current 变量,current 指向开始检索的内存池的位置,failed变量记录本内存池分配失败的次数,因为每次分配前面的内存池分配失败才会检索后面的内存池,所以前面内存池的failed 值一定大于等于后面内存池的failed,当该次数大于5次时,就将current指向后面的内存池,即下次检索从下一个内存池开始,这样就优化了检索的效率。

代码分析

创建一个内存池 ngx_create_pool

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){
ngx_pool_t *p; /* 按NGX_POOL_ALIGNMENT 字节对齐分配一块内存 p 指向这块内存*/ p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); if (p == NULL) {
return NULL; } /*last 指向p的数据部分的起始位置 */ p->d.last = (u_char *) p + sizeof(ngx_pool_t); /* end 指向p 数据部分过的结束位置 */ 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->current = p; p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log; return p;}

重置内存池

ngx_reset_pool

ngx_reset_pool(ngx_pool_t *pool){
ngx_pool_t *p; ngx_pool_large_t *l; /*如果存在大内存快 释放大内存快 */ for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc); } } //将last end 恢复原来的位置 //failed 置为0 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;}

申请内存函数

在代码中对相应的语句进行了注释

ngx_palloc

void *ngx_palloc(ngx_pool_t *pool, size_t size){
#if !(NGX_DEBUG_PALLOC) if (size <= pool->max) {
//按字节对齐的方式分配一块 size 大小的内存 最后的参数为 1 //在ngx_palloc_small 函数中可以看出 return ngx_palloc_small(pool, size, 1); }#endif // 如果请求的size 大于 最大分配的内存块的大小,则从 large 内存块中分配一块 return ngx_palloc_large(pool, size);}

ngx_pnalloc

/* 该函数与 ngx_palloc 作用一样,唯一的区别是该函数分配的内存不按字节对齐的方式*/void *ngx_pnalloc(ngx_pool_t *pool, size_t size){
#if !(NGX_DEBUG_PALLOC) if (size <= pool->max) {
return ngx_palloc_small(pool, size, 0); }#endif return ngx_palloc_large(pool, size);}

ngx_pmemalign

void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment){
void *p; ngx_pool_large_t *large; //按alignment字节对齐的方式 申请一块内存 p = ngx_memalign(alignment, size, pool->log); if (p == NULL) {
return NULL; } //从内存池中申请一小块内存用来用来指向p内存块 large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); if (large == NULL) {
ngx_free(p); return NULL; } //将p内存块挂入内存池的large表中 large->alloc = p; large->next = pool->large; pool->large = large; return p;}

ngx_pcalloc

void *ngx_pcalloc(ngx_pool_t *pool, size_t size){
void *p; p = ngx_palloc(pool, size); if (p) {
//用0 初始化申请内存 ngx_memzero(p, size); } return p;}

分配小内存块

ngx_palloc_small

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; //每次分配总是从pool->current 指向的内存块开始查找 p = pool->current; do {
m = p->d.last; //align == 1时按NGX_ALIGNMENT字节对齐的方式分配一开内存给m if (align) {
m = ngx_align_ptr(m, NGX_ALIGNMENT); } //如果剩余内存够给用户分配,则直接分配,last指向下一个位置 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);}

分配大块内存

static void *ngx_palloc_large(ngx_pool_t *pool, size_t size){
void *p; ngx_uint_t n; ngx_pool_large_t *large; //调用malloc 分配size大小的内存块 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; } //如果查找了前4个内与找到,直接退出,不再查找。 //避免查找带来的开销较大的问题 //在内存池重新开辟一个新的地址存放大块内存 if (n++ > 3) {
break; } } //在内存池中申请一块内存,用来挂入malloc 出的大内存块 large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); if (large == NULL) {
ngx_free(p); return NULL; } //申请成功,将大内存快挂入内存池中 large->alloc = p; large->next = pool->large; pool->large = large; //返回malloc 出的内存块 return p;}

扩容内存池

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); //按NGX_POOL_ALIGNMENT 字节对齐的方式分配一块psize 大小的内存块 m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); if (m == NULL) {
//分配失败直接返回 return NULL; } //申请内存成功,将new指向开辟的内存 new = (ngx_pool_t *) m; //end 指向新内存的结尾 new->d.end = m + psize; //next 指向NULL new->d.next = NULL; new->d.failed = 0; //m指向数据的起始位置 m += sizeof(ngx_pool_data_t); //按NGX_ALIGNMENT字节对齐的方式 拿新申请的内存池块给用户分配内存 m = ngx_align_ptr(m, NGX_ALIGNMENT); //分配后last 指向新的位置 new->d.last = m + size; for (p = pool->current; p->d.next; p = p->d.next) {
//如果当前block 分配有5次未得到满足,则pool->current 指向下一个位置 if (p->d.failed++ > 4) {
pool->current = p->d.next; } } //将新申请的内村块挂入内存池 p->d.next = new; //给用户返回内存 return m;}

总结

这里只是把简单的内存分配分析了一下,Ngnix 的还有一些其他资源的分配,留着以后分析吧

转载地址:http://ltnwi.baihongyu.com/

你可能感兴趣的文章
read obj in matlab
查看>>
find out the neighbour matrix of a mesh
查看>>
Operators and special characters in matlab
查看>>
As-Conformal-As-Possible Surface Registration
查看>>
qmake Variable Reference
查看>>
Lesson 2 Gradient Desent
查看>>
find border vertex
查看>>
matlab sliced variable
查看>>
create symbolic array
查看>>
TAUCS库的编译(vs2010)
查看>>
color vector using in plotting example points and lines between corresponding vertices
查看>>
mex 里面调用matlab函数
查看>>
matlab中cuda编程中分配grid和block dimension的时候的注意事项
查看>>
GPU CUDA and MEX Programming
查看>>
arrayfun用法
查看>>
矩阵积分
查看>>
optimization on macOS
查看>>
Template-Based 3D Model Fitting Using Dual-Domain Relaxation
查看>>
install libfreenect2 on ubuntu 16.04
查看>>
how to use automake to build files
查看>>