nginx源碼初讀(2)--讓煩惱從數據結構開始(ngx_buf/ngx_chain)

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函數進行分配。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章