Nginx 內存池管理
概述
Nginx 使用內存池對內存進行管理,內存管理的實現類似於前面文章介紹的《STL源碼剖析——空間配置器》,把內存分配歸結為大內存分配和小內存分配。若申請的內存大小比同頁的內存池最大值 max 還大,則是大內存分配,否則為小內存分配。
- 大塊內存的分配請求不會直接在內存池上分配內存來滿足請求,而是直接向系統申請一塊內存(就像直接使用 malloc 分配內存一樣),然後將這塊內存掛到內存池頭部的 large 字段下。
- 小塊內存分配,則是從已有的內存池數據區中分配出一部分內存。
Nginx 內存管理相關文件:
- src/os/unix/ngx_alloc.h/.c
- 內存相關的操作,封裝了最基本的內存分配函數。
- 如 free / malloc / memalign / posix_memalign,分別被封裝為 ngx_free,ngx_alloc / ngx_calloc, ngx_memalign
- ngx_alloc:封裝malloc分配內存
- ngx_calloc:封裝malloc分配內存,並初始化空間內容為0
- ngx_memalign:返回基於一個指定 alignment 的大小為 size 的內存空間,且其地址為 alignment 的整數倍,alignment 為2的冪。
- src/core/ngx_palloc.h/.c
- 封裝創建/銷燬內存池,從內存池分配空間等函數。
內存池基本結構
Nginx 內存池基本結構定義如下:
大塊內存分配的數據結構如下:
其他數據結構如下:
內存池基本機構之間的關係如下圖所示:
ngx_pool_t 的邏輯結構
上面數據結構之間邏輯結構圖如下:該圖是採用 UML 畫的,第一行黑色粗體表示對應數據結構,第二行是結構內的成員,冒號左邊是變量,冒號右邊是變量的類型;
內存池的操作
創建內存池
其中內存分配函數 ngx_memalign 定義如下:
銷燬內存池
銷燬內存池由 void ngx_destroy_pool(ngx_pool_t *pool) 函數完成。該函數將遍歷內存池鏈表,釋放所有內存,如果註冊了clenup (也是一個鏈表結構),亦將遍歷該 cleanup 鏈表結構依次調用 clenup 的 handler 清理。同時,還將遍歷 large 鏈表,釋放大塊內存。
重置內存池
重置內存池由 void ngx_reset_pool(ngx_pool_t *pool) 函數完成。該函數將釋放所有 large 內存,並且將 d->last 指針重新指向 ngx_pool_t 結構之後數據區的開始位置,使內存池恢復到剛創建時的位置。由於內存池剛被創建初始化時是不包含大塊內存的,所以必須釋放大塊內存。
內存分配
小塊內存分配
小塊內存分配,即請求分配空間 size 小於內存池最大內存值 max。小內存分配的接口函數如下所示
ngx_palloc 和 ngx_pnalloc 都是從內存池裡分配 size 大小內存。他們的不同之處在於,palloc 取得的內存是對齊的,pnalloc 則不考慮內存對齊問題。ngx_pcalloc 是直接調用 palloc 分配內存,然後進行一次 0 初始化操作。ngx_pmemalign 將在分配 size 大小的內存並按 alignment 對齊,然後掛到 large 字段下,當做大塊內存處理。
ngx_palloc的過程一般為,首先判斷待分配的內存是否大於 pool->max,如果大於則使用 ngx_palloc_large 在 large 鏈表裡分配一段內存並返回, 如果小於測嘗試從鏈表的 pool->current 開始遍歷鏈表,嘗試找出一個可以分配的內存,當鏈表裡的任何一個節點都無法分配內存的時候,就調用 ngx_palloc_block 生成鏈表裡一個新的節點, 並在新的節點裡分配內存並返回, 同時, 還會將pool->current 指針指向新的位置(從鏈表裡面pool->d.failed小於等於4的節點裡找出) 。
小內存分配之後如下圖所示:
上圖是由3個小內存池組成的內存池模型,由於第一個內存池上剩餘的內存不夠分配了,於是就創建了第二個新的內存池,第三個內存池是由於前面兩個內存池的剩餘部分都不夠分配,所以創建了第三個內存池來滿足用戶的需求。由圖可見:所有的小內存池是由一個單向鏈表維護在一起的。這裡還有兩個字段需要關注,failed和current字段。failed表示的是當前這個內存池的剩餘可用內存不能滿足用戶分配請求的次數,即是說:一個分配請求到來後,在這個內存池上分配不到想要的內存,那麼就failed就會增加1;這個分配請求將會遞交給下一個內存池去處理,如果下一個內存池也不能滿足,那麼它的failed也會加1,然後將請求繼續往下傳遞,直到滿足請求為止(如果沒有現成的內存池來滿足,會再創建一個新的內存池)。current字段會隨著failed的增加而發生改變,如果current指向的內存池的failed達到了4的話,current就指向下一個內存池了。
大塊內存分配
大塊內存申請之後如下所示:
cleanup 資源
《 Nginx源碼剖析之內存池,與內存管理》
《nginx源碼分析—內存池結構ngx_pool_t及內存管理》
《Nginx內存池實現源碼分析 》
《Nginx源碼分析-內存池》
《Ningx代碼研究》
閱讀更多 七度猿人見聞 的文章