歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 解碼Nginx:內存池

解碼Nginx:內存池

日期:2017/2/27 16:03:47   编辑:Linux教程

背景

對於HTTP服務器而言,“每秒處理請求數”是非常關鍵的性能指標。處理路徑上每一步動作的時間開銷,都將對該指標造成影響。特別地,內存分配/釋放作為最基礎的動作,對總處理時間施加的影響更為巨大——想想那些伴隨復雜數據結構或細碎邏輯產生的內存分配/釋放動作,很可能在單次處理中執行數十次,假設每次動作都調用malloc()/free()函數,時間開銷將頗為可觀、無法接受。由此,Nginx針對HTTP應用的業務特點,重新設計出一套內存管理機制,以內存池的形式提供給上層結構使用,從而有效地優化性能。

根據分配/釋放頻繁程度,HTTP應用的內存使用特征可以劃分為兩類:
  1. 進程初始化時分配、終止時釋放的大塊內存,供應給配置文件等全程不會變化的數據結構使用;
  2. 處理單次請求時反復分配/釋放的小塊內存,供應給字符串處理等小型結構體和臨時數據使用。

對於前者,使用malloc()/free()帶來的時間開銷並無大礙;對於後者,一旦使用完後即刻調用free()釋放內存,則會產生許多不必要的時間開銷。更理想的作法是將零碎分配得來的內存收集起來,在某個時間點集中釋放掉,亦即“多次分配,一次釋放”。通過設計得當的數據結構和接口,可以在確保“誰分配,誰釋放”的內存使用原則下,提供更好的性能。

內存管理模型

Nginx將內存管理模型組織為兩層結構的內存池:
  1. 第一層結構采用“向系統請求儲備內存塊(多次)——按需分配零碎內存塊(多次)——集中釋放零碎內存塊(單次)”的設計思路,每次分配不超過某個固定長度上限的零碎內存塊;
  2. 第二層結構采用簡單的“向系統請求獨立內存塊(多次+轉發)”的設計思路,亦即轉發分配請求給malloc(),以分配第一層不負責管理的更大塊的獨立內存塊。

除此以外,還包裝了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()負責創建內存池,動作序列如下:
  1. 向系統請求size參數指定大小的儲備內存塊(按NGX_POOL_ALIGNMENT對齊,默認16字節),邏輯上劃分為(ngx_pool_t結構體+剩余內存區)兩部分;
  2. 初始化ngx_pool_data_t結構體各個字段(參考前一節,last字段指向初始分配地址);
  3. 計算單次分配的內存塊長度上限(max字段,最大不超過size參數值或NGX_MAX_ALLOC_FROM_POOL);
  4. 初始化其他字段(current字段指向自己)。

ngx_destroy_pool(pool)

ngx_destroy_pool()負責銷毀內存池,動作序列如下:
  1. 調用各個清理回調函數;
  2. 釋放各個ngx_pool_large_t結構體管理的儲備內存塊;
  3. 釋放各個ngx_pool_data_t結構體管理的獨立內存塊。

ngx_reset_pool(pool)

ngx_reset_pool()負責釋放零碎內存塊,動作序列如下:
  1. 釋放各個ngx_pool_large_t結構體管理的內存塊;
  2. 簡單地將各個ngx_pool_data_t結構體的last字段復原成初始分配地址,而不是釋放。

ngx_palloc(pool, size)/ngx_pnalloc(pool, size)/ngx_pcalloc(pool, size)

ngx_palloc()負責分配零碎內存塊,動作序列如下:
  1. 若size參數大於max字段值,轉發給ngx_palloc_large()處理(調用更底層的分配函數);
  2. 以current字段所指ngx_pool_data_t結構體為起點,在單向鏈表中搜索第一個能執行分配動作的結構體,完成分配(分配首址按NGX_ALIGNMENT對齊);
  3. 若以上動作均告失敗,轉發給ngx_palloc_block()處理(先分配新的ngx_pool_data_t結構體,再分配零碎內存塊)。

ngx_pnalloc()動作與ngx_palloc()類似,但不對齊分配首址。
ngx_pcalloc()將分配請求轉發給ngx_palloc()處理,並對返回的零碎內存塊進行清零初始化。
ngx_palloc_block(pool, size)
  1. ngx_palloc_block()負責向系統請求新的儲備內存塊,最終完成分配,動作序列如下:
  2. 向系統請求指定大小的儲備內存塊(按NGX_POOL_ALIGNMENT對齊,默認16字節),邏輯上劃分為(ngx_pool_data_t結構體+剩余內存區)兩部分;
  3. 初始化各個字段(包括分配首址,按NGX_ALIGNMENT對齊);
  4. 調整current字段的指向(實際上維持著一個“... 5 5 5 4 3 2 1 0 0”的failed字段值序列),將新的ngx_pool_data_t結構體掛入單向鏈表中。

ngx_palloc_large(pool, size)/ngx_pmemalign(pool, size, alignment)

ngx_palloc_large負責分配不歸第一層結構管理的獨立內存塊,動作序列如下:
  1. 調用ngx_alloc()分配新的獨立內存塊;
  2. 搜索第一個空閒的ngx_pool_large_t結構體,保存並返回獨立內存塊首址;
  3. 若前一步失敗,從本內存池中分配新的ngx_pool_large_t結構體並加入單向鏈表中,保存並返回獨立內存塊首址。

ngx_pmemalign()動作與ngx_palloc_large()類似,但會按alignment參數對齊獨立內存塊首址。
ngx_pfree(pool, p)

ngx_pfree()負責“釋放”內存塊,動作序列如下:
  1. 如果待“釋放”對象是獨立內存塊,則轉調用ngx_free()釋放。

實際上該函數並不真正釋放零碎內存塊,而是盡可能將釋放動作推遲到ngx_reset_pool()/ngx_destroy_pool()中。
ngx_pool_cleanup_add(p, size)

ngx_pool_cleanup_add()負責分配新的ngx_pool_cleanup_t結構體,以便調用端注冊回調函數。動作序列如下:
  1. 從本內存池中分配一個ngx_pool_cleanup_t結構體;
  2. 若size參數不為0,嘗試從本內存池分配相應大小的一塊內存並作為回調參數;
  3. 將新的結構體掛到單向鏈表中;
  4. 調整cleanup字段,保持回調函數按“先進後出”的性質(類似於壓棧操作)。

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()提供一個刪除文件並關閉相關文件句柄的缺省實現函數。
Copyright © Linux教程網 All Rights Reserved