chain和buf是nginx過濾模塊涉及到的結構體,而pool則是管理內存分配的一個結構。在日常的過濾模塊中,這兩類結構使用非常頻繁,所以nginx採用類似freelist重複利用的原則,將使用完畢的chain或者buf結構體,放置到一個固定的空閒鏈表裏,以待下次使用。
比如,在通用內存池結構體中,pool->chain變量裏面就保存着釋放的chain。而一般的buf結構體,沒有模塊間公用的空閒鏈表池,都是保存在各模塊的緩存空閒鏈表池裏面。對於buf結構體,還有一種busy鏈表,表示該鏈表中的buf都處於輸出狀態,如果buf輸出完畢,這些buf就可以釋放並重複利用了。
再比如,nginx的過濾模塊在處理從別的filter模塊或者是handler模塊傳遞過來的數據(實際上就是需要發送給客戶端的http response)。這個傳遞過來的數據是以一個鏈表的形式(ngx_chain_t)。而且數據可能被分多次傳遞過來。也就是多次調用filter的處理函數,以不同的ngx_chain_t。如果chain最後一個節點的next值沒有被設爲NULL,數據就無法完成接收。
接下來我們就看看chain和buf的結構體,最後再研究一下pool。
第一點:ngx_buf_t
ngx_buf_t是Nginx處理大數據的關鍵數據結構,是一種抽象的數據結構,它代表某種具體的數據,既應用於內存數據(緩衝區)也應用於磁盤數據(文件)或者一些元數據(指示鏈表讀取者對鏈表的處理方式)。
下面我們來看看它的定義:
typedef struct ngx_buf_s ngx_buf_t;
typedef void * ngx_buf_tag_t;
struct ngx_buf_s {
u_char *pos;
/* 本次內存數據處理的起始位置,因爲一個buf可能被多次處理。
* if (ngx_buf_in_memory(in->buf)) {
in->buf->pos += (size_t) sent;
}
*/
u_char *last;
/* pos和last之間即爲nginx本次想要處理的區域。
* if (ngx_buf_in_memory(in->buf)) {
in->buf->pos = in->buf->last;
}
*/
off_t file_pos;
off_t file_last;
/* 處理文件時,概念與內存相同,表示相對於文件頭的偏移量。
* size = cl->buf->file_last - cl->buf->file_pos;
*/
u_char *start; /* start of buffer */
u_char *end; /* end of buffer */
/* 當buf所指向的數據在內存裏的時候,這一整塊內存包含的內容可能被包含在多個buf中,
* 比如在某段數據中間插入了其他的數據,這一塊數據就需要被拆分開。
* 那麼這些buf中的start和end都指向這一塊內存的開始地址和結束地址。
* 而pos和last指向本buf所實際包含的數據的開始和結尾。
*/
ngx_buf_tag_t tag;
/* 見上定義,表示當前緩衝區的類型,例如由哪個模塊使用就指向這個模塊ngx_module_t變量的地址。
* buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
*/
ngx_file_t *file;
/* 指向文件的引用。之後介紹這個結構體。*/
ngx_buf_t *shadow;
/* 當前緩衝區的影子緩衝區,該成員很少用到。當緩衝區轉發上游服務器的響應時才使用了shadow成員,
* 這是因爲nginx太節約內存了,分配一塊內存並使用ngx_buf_t表示接收到的上游服務器響應後,
* 在向下遊客戶端轉發時可能會把這塊內存存儲到文件中,也可能直接向下遊發送,此時nginx絕對不會
* 重新複製一份內存用於新的目的,而是再次建立一個ngx_buf_t結構體指向原內存,這樣多個ngx_buf_t
* 結構體指向了同一份內存,它們之間的關係就通過shadow成員來引用,一般不建議使用。
* buf = cl->buf->shadow;
*/
unsigned temporary:1;
/* 表示buf包含的內容在內存緩衝區中,在被處理過程中可以修改,不會造成問題。
* if (b->pos == dst) {
b->sync = 1;
b->temporary = 0;
}
*/
unsigned memory:1;
/* 與temporary相反,表示不可以被修改 */
unsigned mmap:1;
/* 爲1時表示該buf是通過mmap使用內存映射從文件中映射到內存中的,不可以被修改 */
unsigned recycled:1;
/* 可以回收的。也就是這個buf是可以被釋放的。這個字段通常是配合shadow字段一起使用的,
對於使用ngx_create_temp_buf 函數創建的buf,並且是另外一個buf的shadow,
那麼可以使用這個字段來標示這個buf是可以被釋放的。*/
unsigned in_file:1;
/* 爲1時表示該buf所包含的內容是在文件中。*/
unsigned flush:1;
/* 爲1時表示需要執行flush操作,遇到有flush字段被設置爲1的的buf的chain,
則該chain的數據即便不是最後結束的數據(last_buf被設置,標誌所有要輸出的內容都完了),
也會進行輸出,不會受postpone_output配置的限制,但是會受到發送速率等其他條件的限制。*/
unsigned sync:1;
/* 標誌位,對於操作這塊緩衝區時是否使用同步方式,需謹慎考慮,這可能會阻塞nginx進程,
nginx中所有操作幾乎都是異步的,這是它支持高併發的關鍵。
有些框架代碼在sync爲1時可能會有阻塞的方式進行I/O操作,它的意義視使用它的nginx模塊而定。*/
unsigned last_buf:1;
/* 數據被以多個chain傳遞給了過濾器,此字段爲1表明這是最後一個buf。
* if (in->buf->last_buf) last = 1;
*/
unsigned last_in_chain:1;
/* 在當前的chain裏面,此buf是最後一個。特別要注意的是last_in_chain的buf不一定是last_buf,
但是last_buf的buf一定是last_in_chain的。這是因爲數據會被以多個chain傳遞給某個filter模塊。
* if (in->last_in_chain) {
if (bsize < (off_t) size) {
size = (size_t) bsize;
recycled = 0;
}
// else if ...
}
*/
unsigned last_shadow:1;
/* 在創建一個buf的shadow的時候,通常將新創建的一個buf的last_shadow置爲1,表示爲最後一個shadow。
* if (cl->buf->last_shadow) {
if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
return NGX_ABORT;
}
cl->buf->last_shadow = 0;
}
*/
unsigned temp_file:1;
/* 由於內存使用限制,有時候一些buf需要被寫到磁盤上的臨時文件中去,那麼就設置此標誌表示爲臨時文件。*/
/* STUB */ int num;
};
對於此對象的創建,可以直接在某個ngx_pool_t上分配,然後根據需要,給對應的字段賦值。也可以使用定義好的2個宏,這兩個宏使用類似函數:
#define ngx_alloc_buf(pool) ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))
對於創建temporary字段爲1的buf(就是其內容可以被後續的filter模塊進行修改),可以直接使用函數ngx_create_temp_buf進行創建:
ngx_buf_t *
ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
ngx_buf_t *b = ngx_calloc_buf(pool);
if (b == NULL) return NULL;
// 從pool中分配出size大小的內存給buf
b->start = ngx_palloc(pool, size); // 指向內存開始的地方
if (b->start == NULL) return NULL;
// set by ngx_calloc_buf():
// b->file_pos = b->file_last = b->tag = 0;
// b->file = b->shadow = NULL;
// and flags
b->pos = b->start; // 指向內存塊的起始位置便於寫入
b->last = b->start;
b->end = b->last + size; // 指向內存結束的地方
b->temporary = 1;
return b;
}
爲了配合對ngx_buf_t的使用,nginx定義了以下的宏方便操作:
// buf是否在內存裏
#define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap)
// buf裏面的內容是否僅僅在內存裏,並且沒有在文件裏
#define ngx_buf_in_memory_only(b) (ngx_buf_in_memory(b) && !b->in_file)
// buf是否是一個特殊的buf,只含有特殊的標誌和沒有包含真正的數據
#define ngx_buf_special(b) \
((b->flush || b->last_buf || b->sync) \
&& !ngx_buf_in_memory(b) && !b->in_file)
// buf是否是一個只包含sync標誌而不包含真正數據的特殊buf
#define ngx_buf_sync_only(b) \
(b->sync \
&& !ngx_buf_in_memory(b) && !b->in_file && !b->flush && !b->last_buf)
// 返回該buf所含數據的大小,不管這個數據是在文件裏還是在內存裏。
#define ngx_buf_size(b) \
(ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos): \
(b->file_last - b->file_pos))
第二點:ngx_chain_t
ngx_chain_t:
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf; // 指向當前的buf緩衝區
ngx_chain_t *next; // 如果這是最後一個ngx_chain_t,需要把next置爲NULL
};
創建緩衝區鏈表的函數:
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool)
{
ngx_chain_t *cl;
cl = pool->chain;
if (cl)
{
pool->chain = cl->next;
return cl;
}
cl = ngx_palloc(pool, sizeof(ngx_chain_t));
if (cl == NULL)
{
return NULL;
}
return cl;
}
該宏釋放一個ngx_chain_t類型的對象:
#define ngx_free_chain(pool, cl) \
cl->next = pool->chain; \
pool->chain = cl
如果要釋放整個chain,則迭代此鏈表,對每個節點使用此宏即可。注意:對ngx_chaint_t類型的釋放,並不是真的釋放了內存,而僅僅是把這個對象掛在了這個pool對象的一個叫做chain的字段對應的chain上,以供下次從這個pool上分配ngx_chain_t類型對象的時候,快速的從這個pool->chain上取下鏈首元素就返回了,當然,如果這個鏈是空的,纔會真的在這個pool上使用ngx_palloc函數進行分配。