ngx_pool_t是一個非常重要的數據結構,在很多重要的場合都有使用,很多重要的數據結構也都在使用它。那么它究竟是一個什么東西呢?簡單的說,它提供了一種機制,幫助管理一系列的資源(如內存,文件等),使得對這些資源的使用和釋放統一進行,免除了使用過程中考慮到對各種各樣資源的什么時候釋放,是否遺漏了釋放的擔心。
例如對于內存的管理,如果我們需要使用內存,那么總是從一個ngx_pool_t的對象中獲取內存,在最終的某個時刻,我們銷毀這個ngx_pool_t對象,所有這些內存都被釋放了。這樣我們就不必要對對這些內存進行malloc和free的操作,不用擔心是否某塊被malloc出來的內存沒有被釋放。因為當ngx_pool_t對象被銷毀的時候,所有從這個對象中分配出來的內存都會被統一釋放掉。
再比如我們要使用一系列的文件,但是我們打開以后,最終需要都關閉,那么我們就把這些文件統一登記到一個ngx_pool_t對象中,當這個ngx_pool_t對象被銷毀的時候,所有這些文件都將會被關閉。
從上面舉的兩個例子中我們可以看出,使用ngx_pool_t這個數據結構的時候,所有的資源的釋放都在這個對象被銷毀的時刻,統一進行了釋放,那么就會帶來一個問題,就是這些資源的生存周期(或者說被占用的時間)是跟ngx_pool_t的生存周期基本一致(ngx_pool_t也提供了少量操作可以提前釋放資源)。從最高效的角度來說,這并不是最好的。比如,我們需要依次使用A,B,C三個資源,且使用完B的時候,A就不會再被使用了,使用C的時候A和B都不會被使用到。如果不使用ngx_pool_t來管理這三個資源,那我們可能從系統里面申請A,使用A,然后在釋放A。接著申請B,使用B,再釋放B。最后申請C,使用C,然后釋放C。但是當我們使用一個ngx_pool_t對象來管理這三個資源的時候,A,B和C的釋放是在最后一起發生的,也就是在使用完C以后。誠然,這在客觀上增加了程序在一段時間的資源使用量。但是這也減輕了程序員分別管理三個資源的生命周期的工作。這也就是有所得,必有所失的道理。實際上是一個取舍的問題,要看在具體的情況下,你更在乎的是哪個。
可以看一下在nginx里面一個典型的使用ngx_pool_t的場景,對于nginx處理的每個http request, nginx會生成一個ngx_pool_t對象與這個http request關聯,所有處理過程中需要申請的資源都從這個ngx_pool_t對象中獲取,當這個http request處理完成以后,所有在處理過程中申請的資源,都將隨著這個關聯的ngx_pool_t對象的銷毀而釋放。
ngx_pool_t相關結構及操作被定義在文件src/core/ngx_palloc.h|c中。
[](http:// "點擊提交Issue,反饋你的意見...")
typedef struct ngx_pool_s ngx_pool_t;
struct ngx_pool_s {
ngx_pool_data_t d;
size_t max;
ngx_pool_t *current;
ngx_chain_t *chain;
ngx_pool_large_t *large;
ngx_pool_cleanup_t *cleanup;
ngx_log_t *log;
};
從ngx_pool_t的一般使用者的角度來說,可不用關注ngx_pool_t結構中各字段作用。所以這里也不會進行詳細的解釋,當然在說明某些操作函數的使用的時候,如有必要,會進行說明。
下面我們來分別解釋下ngx_pool_t的相關操作。
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
創建一個初始節點大小為size的pool,log為后續在該pool上進行操作時輸出日志的對象。 需要說明的是size的選擇,size的大小必須小于等于NGX_MAX_ALLOC_FROM_POOL,且必須大于sizeof(ngx_pool_t)。
選擇大于NGX_MAX_ALLOC_FROM_POOL的值會造成浪費,因為大于該限制的空間不會被用到(只是說在第一個由ngx_pool_t對象管理的內存塊上的內存,后續的分配如果第一個內存塊上的空閑部分已用完,會再分配的)。
選擇小于sizeof(ngx_pool_t)的值會造成程序崩潰。由于初始大小的內存塊中要用一部分來存儲ngx_pool_t這個信息本身。
當一個ngx_pool_t對象被創建以后,該對象的max字段被賦值為size-sizeof(ngx_pool_t)和NGX_MAX_ALLOC_FROM_POOL這兩者中比較小的。后續的從這個pool中分配的內存塊,在第一塊內存使用完成以后,如果要繼續分配的話,就需要繼續從操作系統申請內存。當內存的大小小于等于max字段的時候,則分配新的內存塊,鏈接在d這個字段(實際上是d.next字段)管理的一條鏈表上。當要分配的內存塊是比max大的,那么從系統中申請的內存是被掛接在large字段管理的一條鏈表上。我們暫且把這個稱之為大塊內存鏈和小塊內存鏈。
[](http:// "點擊提交Issue,反饋你的意見...")
void *ngx_palloc(ngx_pool_t *pool, size_t size);
從這個pool中分配一塊為size大小的內存。注意,此函數分配的內存的起始地址按照NGX_ALIGNMENT進行了對齊。對齊操作會提高系統處理的速度,但會造成少量內存的浪費。
[](http:// "點擊提交Issue,反饋你的意見...")
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
從這個pool中分配一塊為size大小的內存。但是此函數分配的內存并沒有像上面的函數那樣進行過對齊。
[](http:// "點擊提交Issue,反饋你的意見...")
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
該函數也是分配size大小的內存,并且對分配的內存塊進行了清零。內部實際上是轉調用ngx_palloc實現的。
[](http:// "點擊提交Issue,反饋你的意見...")
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
按照指定對齊大小alignment來申請一塊大小為size的內存。此處獲取的內存不管大小都將被置于大內存塊鏈中管理。
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
對于被置于大塊內存鏈,也就是被large字段管理的一列內存中的某塊進行釋放。該函數的實現是順序遍歷large管理的大塊內存鏈表。所以效率比較低下。如果在這個鏈表中找到了這塊內存,則釋放,并返回NGX_OK。否則返回NGX_DECLINED。
由于這個操作效率比較低下,除非必要,也就是說這塊內存非常大,確應及時釋放,否則一般不需要調用。反正內存在這個pool被銷毀的時候,總歸會都釋放掉的嘛!
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
ngx_pool_t中的cleanup字段管理著一個特殊的鏈表,該鏈表的每一項都記錄著一個特殊的需要釋放的資源。對于這個鏈表中每個節點所包含的資源如何去釋放,是自說明的。這也就提供了非常大的靈活性。意味著,ngx_pool_t不僅僅可以管理內存,通過這個機制,也可以管理任何需要釋放的資源,例如,關閉文件,或者刪除文件等等。下面我們看一下這個鏈表每個節點的類型:
[](http:// "點擊提交Issue,反饋你的意見...")
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
typedef void (*ngx_pool_cleanup_pt)(void *data);
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
};
| data: | 指明了該節點所對應的資源。 |
|-----|-----|
| handler: | 是一個函數指針,指向一個可以釋放data所對應資源的函數。該函數只有一個參數,就是data。 |
| next: | 指向該鏈表中下一個元素。 |
看到這里,ngx_pool_cleanup_add這個函數的用法,我相信大家都應該有一些明白了。但是這個參數size是起什么作用的呢?這個 size就是要存儲這個data字段所指向的資源的大小,該函數會為data分配size大小的空間。
比如我們需要最后刪除一個文件。那我們在調用這個函數的時候,把size指定為存儲文件名的字符串的大小,然后調用這個函數給cleanup鏈表中增加一項。該函數會返回新添加的這個節點。我們然后把這個節點中的data字段拷貝為文件名。把hander字段賦值為一個刪除文件的函數(當然該函數的原型要按照void (*ngx_pool_cleanup_pt)(void *data))。
[](http:// "點擊提交Issue,反饋你的意見...")
void ngx_destroy_pool(ngx_pool_t *pool);
該函數就是釋放pool中持有的所有內存,以及依次調用cleanup字段所管理的鏈表中每個元素的handler字段所指向的函數,來釋放掉所有該pool管理的資源。并且把pool指向的ngx_pool_t也釋放掉了,完全不可用了。
[](http:// "點擊提交Issue,反饋你的意見...")
void ngx_reset_pool(ngx_pool_t *pool);
該函數釋放pool中所有大塊內存鏈表上的內存,小塊內存鏈上的內存塊都修改為可用。但是不會去處理cleanup鏈表上的項目。
- 上篇:nginx模塊開發篇
- nginx平臺初探
- 初探nginx架構
- nginx基礎概念
- connection
- request
- keepalive
- pipe
- lingering_close
- 基本數據結構
- ngx_str_t
- ngx_pool_t
- ngx_array_t
- ngx_hash_t
- ngx_hash_wildcard_t
- ngx_hash_combined_t
- ngx_hash_keys_arrays_t
- ngx_chain_t
- ngx_buf_t
- ngx_list_t
- ngx_queue_t
- nginx的配置系統
- 指令參數
- 指令上下文
- nginx的模塊化體系結構
- 模塊的分類
- nginx的請求處理
- handler模塊
- handler模塊簡介
- 模塊的基本結構
- 模塊配置結構
- 模塊配置指令
- 模塊上下文結構
- 模塊的定義
- handler模塊的基本結構
- handler模塊的掛載
- handler的編寫步驟
- 示例: hello handler 模塊
- handler模塊的編譯和使用
- 更多handler模塊示例分析
- http access module
- http static module
- http log module
- 過濾模塊
- 過濾模塊簡介
- 過濾模塊的分析
- upstream模塊
- upstream模塊
- upstream模塊接口
- memcached模塊分析
- 本節回顧
- 負載均衡模塊
- 配置
- 指令
- 鉤子
- 初始化配置
- 初始化請求
- peer.get和peer.free回調函數
- 本節回顧
- 其他模塊
- core模塊
- event模塊
- 模塊開發高級篇
- 變量
- 下篇:nginx原理解析篇
- nginx架構詳解
- nginx的源碼目錄結構
- nginx的configure原理
- 模塊編譯順序
- nginx基礎設施
- 內存池
- nginx的啟動階段
- 概述
- 共有流程
- 配置解析
- nginx的請求處理階段
- 接收請求流程
- http請求格式簡介
- 請求頭讀取
- 解析請求行
- 解析請求頭
- 請求體讀取
- 讀取請求體
- 丟棄請求體
- 多階段處理請求
- 多階段執行鏈
- POST_READ階段
- SERVER_REWRITE階段
- FIND_CONFIG階段
- REWRITE階段
- POST_REWRITE階段
- PREACCESS階段
- ACCESS階段
- POST_ACCESS階段
- TRY_FILES階段
- CONTENT階段
- LOG階段
- Nginx filter
- header filter分析
- body filter分析
- ngx_http_copy_filter_module分析
- ngx_http_write_filter_module分析
- subrequest原理解析
- https請求處理解析
- 附錄A 編碼風格
- 附錄B 常用API
- 附錄C 模塊編譯,調試與測試