感悟
感悟有两点,
第一点就是别人已经把这个内存池总结的非常好了,我只是在继续炒冷饭而已,但是我还觉得该写点什么,我打算写 结构体的含义和 相关函数的功能。源码文件:ngx_palloc.c和ngx_palloc.h
第一点就是别人已经把这个内存池总结的非常好了,我只是在继续炒冷饭而已,但是我还觉得该写点什么,我打算写 结构体的含义和 相关函数的功能。源码文件:ngx_palloc.c和ngx_palloc.h
更多复杂、详细的内容建议您参考:
- Nginx源码剖析之内存池,与内存管理
- 深入剖析nginx P65
第二点就是如何看源码,昨日不知在哪里看源码的经验,需要三步:
- 看懂大致框架结构、各个模块的功能、各个模块的结构体、函数、接口的功能是什么,用图表示更好
- 挑重点模块来精读源码、一句一句仔细分析
- 再次总览各个模块,整体框架,分析分析。
我觉得这样说法很有道理,昨天我算精读了ngnix 中hash部分的源码,调试着一句一句的看,一句一句的分析,最后有种累感不爱的感觉,想着那茫茫多的源码我得分析成什么样子呀。所以突然看到上述三个步骤,就发现自己方法错了。所以现在的目标,不是去读每一句源码,而是分析整体框架和各个模块的功能、结构体、函数、接口,具体函数怎么实现应该往后放。
结构体
首先我们要明白,nginx对内存的管理机制是:nginx会事先向操作系统申请很多内存,作为内存池,然后自己管理,如果nginx其他模块需要内存,不能直接像操作系统申请,必须向ngnix自己管理的内存池申请,内存池再将内存拨给需要的模块。
ngx_pool_s
接着,我们来看看有哪些结构体,在这些结构体的相互作用下共同维护着内存池。
必须首先知道的是
ngx_pool_s:描述着内存池的一些信息,而内存池里面有很多内存块、有大的,有小的。描述信息的
ngx_pool_s只需要
一个。
另外,ngx_pool_t是它的别名:
虽然还有更多细节没有说到,但下面用一张图(来自 v_JULY_v)来总览一下比较好,
注意:我们的 ngx_pool_s结构体就在左上方的方块里,那是一个内存块,含有内存池的描述信息,也就是ngx_pool_s:
另外,ngx_pool_t是它的别名:
struct ngx_pool_s {
ngx_pool_data_t d;//指向一个小的内存块,这个内存块接在了ngx_pool_s描述信息的后面
size_t max;//一个内存块的最大大小,最大是一页的大小(4096) 再减一。
//i.e. 4095 on x86
ngx_pool_t *current;//指向一个内存块,需要如果内存,则从这个内存
ngx_chain_t *chain;//暂时不知道是哪里干什么的
ngx_pool_large_t *large;//指向一个大的内存块
ngx_pool_cleanup_t *cleanup;//如果申请内存块时,附带了打开了其他资源,比如:打开文件。
//那么当内存释放时,必然需要关闭文件。
//那么关闭文件的操作就注册在此处
ngx_log_t *log;//日志相关
};
typedef struct ngx_pool_s ngx_pool_t;
虽然还有更多细节没有说到,但下面用一张图(来自 v_JULY_v)来总览一下比较好,
注意:我们的 ngx_pool_s结构体就在左上方的方块里,那是一个内存块,含有内存池的描述信息,也就是ngx_pool_s:
ngx_pool_data_t
上图左上方的第一个内存块,其实已经将ngx_pool_data_t结构体的具体内容直接展示出来了,所以来分析一下ngx_pool_data_t:
typedef struct {
u_char *last;//指向内存当前内存块中可以分配的开始位置
u_char *end;//当前内存块的末尾
ngx_pool_t *next;//下一个内存块
ngx_uint_t failed;//在这个内存块试图拨内存给别的模块,可惜内存不足,失败了的次数
} ngx_pool_data_t;
在上图中,我们可以很清楚的看到
- last指向未分配的起始处
- end指向当前内存块的结尾
- next指向下一个小的内存块
ngx_pool_large_t
图中也可以看到ngx_pool_large_t以链表的形式组织在了一起:
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next;//下一个大的内存块
void *alloc;//大的内存块的起始处
};
注意:
- 什么时候会产生ngx_pool_large_t结构体为首的大的内存块呢?
- 答:申请的内存块大于ngx_pool_s的max时,因为小的内存块不够用,只有分大的给申请者。此时是向操作系统申请了。
从如下代码中我们可以看到,当size > pool->max时,直接调用ngx_palloc_large(pool, size),而ngx_palloc_large(pool, size)最终无非是调用了系统函数 malloc(size):
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
if (size <= pool->max) {
...
}
return ngx_palloc_large(pool, size);
}
ngx_pool_cleanup_t
图中可以看出,ngx_pool_cleanup_t也是链表组织在了一起:
那什么时候释放呢?我们可以看到在ngx_destroy_pool函数中,如下所示的for循环中逐步调用了释放资源的函数和传入了释放资源的数据:
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;//释放资源的函数
void *data;//需要释放资源的数据
ngx_pool_cleanup_t *next;//下一个需要释放的结构体数据
};
那什么时候释放呢?我们可以看到在ngx_destroy_pool函数中,如下所示的for循环中逐步调用了释放资源的函数和传入了释放资源的数据:
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);//调用了释放资源的函数和传入了释放资源的数据
}
}
...
}
函数
一共有多少函数
先来张总览图,看看有多少函数,这些函数都可以被外界调用的:
此外还有两个static,处理内部逻辑的:
具体含义
ngx_alloc | 直接调用malloc向操作系统系统申请,在src/os/unix/ngx_alloc.c的文档中 |
ngx_calloc | 调用ngx_alloc后,将内存空间全部清零,在src/os/unix/ngx_alloc.c的文档中 |
ngx_create_pool | 创建一个内存块,作为内存池的第一个内存块,返回的结构体含有内存池的基本信息 |
ngx_destroy_pool | 摧毁一个内存池 |
ngx_reset_pool | 重置一个内存池,这个内存块内存放的信息都会清零 |
ngx_palloc | 向内存池里面申请size的空间,作了对齐处理, 如果小于p->max,但目前内存块内的空间不足, 那就会调用ngx_palloc_block重新申请新的内存块 如果大于p->max,则会调用ngx_palloc_large |
ngx_pnalloc | 向内存池里面申请size的空间,不作对齐处理 |
ngx_pcalloc | 向内存池里面申请size的空间,作对齐处理,还会清零处理 |
ngx_pmemalign | 做了对齐处理,之后调用了ngx_alloc, 也就说无论申请的大小为多少,都会直接想操作系统申请 并且会挂载到内存池的ngx_pool_large_t 结构体的alloc字段下 |
ngx_pfree | 只能对大的内存块调用系统函数:free,也就是释放大的内存块 |
ngx_pool_cleanup_add | 注册需要在释放内存时一起释放的资源,返回ngx_pool_cleanup_t结构体后, 在这个结构体的相应字段填写就可以了 |
ngx_pool_run_cleanup_file | 会关闭挂载到ngx_pool_cleanup_t结构体内的所有文件 |
ngx_pool_cleanup_file | 对指定的文件的进行关闭,指定的文件存放在:ngx_pool_cleanup_file_t结构体内 |
ngx_pool_delete_file | 对指定的文件的进行删除,指定的文件存放在:ngx_pool_cleanup_file_t结构体内 |
ngnix内存的特点
- 小的内存统一向内存池申请,避免了内存碎片的产生
- 注意到唯一的释放内存的函数只有一个:ngx_pfree,且只能释放大块内存。
小块内存的释放,严格说来不是释放,归还给操作系统,而且直接用ngx_pool_s结构体的 last 指针来改一下指的地方就可以了。
如下,将p->d.last指向内存块的可分配的内存的起始位置即可:
void ngx_reset_pool(ngx_pool_t *pool) { ... for (p = pool; p; p = p->d.next) { p->d.last = (u_char *) p + sizeof(ngx_pool_t); } }
- 根据WebServer的特点,Ngnix是针对阶段来创建内存池,然后这个阶段过后,就销毁内存池就完了。
具体说来就是如下:摘自(Nginx源码分析-内存池)
一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,就在worker进程的内存池上为该连接创建起一个内存池;连接上到来一个request后,又在连接的内存池上为request创建起一个内存池。这样,在request被处理完后,就会释放request的整个内存池,连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。
但是我有一个疑问:
上段中的划线+黑体:如何做到在一个内存池上创建另一个内存池,因为用到"创建"一词,我认为作者所说的方法是:ngx_create_pool。但是这个方法是直接向操作系统申请内存,那就不是在一个内存池上的申请。
那如果是调用ngx_palloc,那就是在worker进程的内存池申请了一些内存,那么我认为就是和我2中所说的一样,要释放小块内存,那么直接改动p->d.last的指向即可。那么上段中所述“创建”一词不太标准。(当然,我也很好奇如何让p->d.last指对地方)
学习源码的画图工具
mind map:好像linux下面没有。