背景对于HTTP服务器而言,“每秒处理请求数”是非常关键的性能指标。处理路径上每一步动作的时间开销,都将对该指标造成影响。特别地,内存分配/释放作为最基础的动作,对总处理时间施加的影响更为巨大——想想那些伴随复杂数据结构或细碎逻辑产生的内存分配/释放动作,很可能在单次处理中执行数十次,假设每次动作都调用malloc()/free()函数,时间开销将颇为可观、无法接受。由此,Nginx针对HTTP应用的业务特点,重新设计出一套内存管理机制,以内存池的形式提供给上层结构使用,从而有效地优化性能。根据分配/释放频繁程度,HTTP应用的内存使用特征可以划分为两类:
对于前者,使用malloc()/free()带来的时间开销并无大碍;对于后者,一旦使用完后即刻调用free()释放内存,则会产生许多不必要的时间开销。更理想的作法是将零碎分配得来的内存收集起来,在某个时间点集中释放掉,亦即“多次分配,一次释放”。通过设计得当的数据结构和接口,可以在确保“谁分配,谁释放”的内存使用原则下,提供更好的性能。 内存管理模型Nginx将内存管理模型组织为两层结构的内存池:
除此以外,还包装了malloc()/free()以及更为底层的memalign()/posix_memalign(),用以分配不归内存池管理的内存块。 第一层结构 ngx_create_pool() ngx_palloc()/ngx_pnalloc() ... | | | \- ngx_palloc_block() | | \- ngx_memalign() ------\ \- ngx_memalign() ------\ | | | | ngx_pool_t V ngx_pool_data_t V /--> +-------------------+ /----> +-----------------+ /-> ... | | ngx_pool_data_t d | | | last | --\ | | | +----------------+ | +-----------------+ | | | | | last | --\ | | end | --+--\ | | | +----------------+ | | +-----------------+ | | | | | | end | --+--\ | | next | --+--+--/ | | +----------------+ | | | +-----------------+ | | | | | next | --+--+--/ | failed | | | | | +----------------+ | | +-----------------+ | | | | | failed | | | | allocated | | | | +--+----------------+ | | | area | | | | | max | | | /-----------------/ <-/ | | +-------------------+ | | / unallocated / | \--- | current | | | | area | | +-------------------+ | | | | | | chain | --+--+-----\ | | | +-------------------+ | | | | | | | large | --+--+--\ | +-----------------+ <----/ +-------------------+ | | | | /--- | cleanup | | | | | | +-------------------+ | | | | | | log | | | | | | +-------------------+ | | | \-> ??? | | allocated | | | | | | area | | | | | /-------------------/ <-/ | \----> 第二层结构 | / unallocated / | (ngx_pool_large_t) | | area | | | | | | | | | | | | | | | +-------------------+ <----/ | | | ngx_pool_cleanup_t \--> +-------------------+ | handler | +-------------------+ | data | +-------------------+ /--- | next | | +-------------------+ | | ngx_pool_cleanup_t \--> +-------------------+ | handler | +-------------------+ | data | +-------------------+ /--- | next | | +-------------------+ | | \--> ... 注意点: 1. ngx_pool_t和ngx_pool_data_t结构体存放在分配出的储备内存块首址处,占用固定空间; 2. 内存长度大于max字段值的分配请求将转发给第二层结构处理; 3. 使用ngx_pool_data_t结构体构造出单向链表,管理各储备内存块; 4. 使用ngx_pool_cleanup_t结构体构造出单向链表,管理各清理回调函数。 第二层结构 ngx_palloc()/ngx_pnalloc() ngx_palloc()/ngx_pnalloc() | | \- ngx_palloc_large() --\ \- ngx_palloc_large() --\ | | ngx_pool_large_t V ngx_pool_large_t V +-------------------+ /-----> +-------------------+ /-----> ... | alloc | | | alloc | | +-------------------+ | +-------------------+ | | next | ------/ | next | ------/ +-------------------+ +-------------------+ 注意点: 1. ngx_pool_large_t结构体是从ngx_pool_data_t结构管理的储备内存块中分配出来的,并且会适当复用。 内存池接口函数 ngx_create_pool(size, log) ngx_create_pool()负责创建内存池,动作序列如下:
ngx_destroy_pool(pool) ngx_destroy_pool()负责销毁内存池,动作序列如下:
ngx_reset_pool(pool) ngx_reset_pool()负责释放零碎内存块,动作序列如下:
ngx_palloc(pool, size)/ngx_pnalloc(pool, size)/ngx_pcalloc(pool, size) ngx_palloc()负责分配零碎内存块,动作序列如下:
ngx_pnalloc()动作与ngx_palloc()类似,但不对齐分配首址。 ngx_pcalloc()将分配请求转发给ngx_palloc()处理,并对返回的零碎内存块进行清零初始化。 ngx_palloc_block(pool, size)
ngx_palloc_large(pool, size)/ngx_pmemalign(pool, size, alignment) ngx_palloc_large负责分配不归第一层结构管理的独立内存块,动作序列如下:
ngx_pmemalign()动作与ngx_palloc_large()类似,但会按alignment参数对齐独立内存块首址。 ngx_pfree(pool, p) ngx_pfree()负责“释放”内存块,动作序列如下:
实际上该函数并不真正释放零碎内存块,而是尽可能将释放动作推迟到ngx_reset_pool()/ngx_destroy_pool()中。 ngx_pool_cleanup_add(p, size) ngx_pool_cleanup_add()负责分配新的ngx_pool_cleanup_t结构体,以便调用端注册回调函数。动作序列如下:
ngx_pool_run_cleanup_file(p, fd)/ngx_pool_cleanup_file(data)/ngx_pool_cleanup_file(data) ngx_pool_run_cleanup_file()负责搜索注册在回调函数链表中的、与fd参数指定文件句柄对应的回调函数并调用之。 ngx_pool_cleanup_file()提供一个关闭指定文件句柄的缺省实现函数。 ngx_pool_delete_file()提供一个删除文件并关闭相关文件句柄的缺省实现函数。 转载请保留固定链接: https://linuxeye.com/Linux/947.html |