天天看點

ngx_buf_t資料結構

      導語:通過網上閱讀查閱,盡量把nginx源碼這一部分整理完善,讓以後想要學習nginx的同學也可以快速上手。這一節涉及src/core/ngx_buf.h|c的資料結構,其次,看一些大牛文章時候 get 了一個好的畫圖工具- graphviz 和一個拍照app-掃描全能王,可以幫助理清源碼的思路。

一、結構體、相關宏定義、函數聲明梳理        

       分析采用nginx-1.6.2源碼。在src/core/ngx_buf.h|c中的ngx_buf_t 結構體還是很重要,看源碼的過程中遇到幾次,确實有必要好好理理。ngx_buf_t 包含于ngx_chain_t 結構體中。

// 這個就是連結清單的形式,還是把ngx_buf_t串起來,
// 主要作用就是接受nginx伺服器的http請求標頭、包體、以及響應用戶端的應答標頭、包體
// 都會放在chain連結清單緩沖區
struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};
           

接着是ngx_buf_t結構體,ngx_buf_t 就是ngx_chain_t連結清單每個節點的實際資料,緩沖區ngx_buf_t是nginx處理大資料的關鍵資料結構,它既可以應用于記憶體也可以用于磁盤資料注釋。裡面的參數設定,考慮的情況真多,而且最後标志位都隻占1位,可以說對記憶體利用锱铢必較,值得借鑒學習。

typedef void *            ngx_buf_tag_t;

typedef struct ngx_buf_s  ngx_buf_t;

// ngx_buf_t 就是ngx_chain_t連結清單每個節點的實際資料,
// 緩沖區ngx_buf_t是nginx處理大資料的關鍵資料結構,它既可以應用于記憶體也可以用于磁盤資料
struct ngx_buf_s {
    // pos指向的是這段資料在記憶體中的開始位置
    u_char          *pos;
    // last指向的是這段資料在記憶體中的結束位置
    u_char          *last;
    // file_pos指向這段資料的開始位置在檔案中的偏移量
    off_t            file_pos;
    // file_last指向這段資料的結束位置在檔案中的偏移量
    off_t            file_last;

    // 如果ngx_buf_t 緩沖區用于記憶體,那麼start指向這段記憶體的起始位址
    u_char          *start;         /* start of buffer */
    // 如果ngx_buf_t 緩沖區用于記憶體,那麼end指向這段記憶體的結束位址
    u_char          *end;           /* end of buffer */

    // 實際上就是一個void *類型的指針。使用者可以關聯任何對象上去
    // 由哪個子產品使用就執行哪個子產品的ngx_module_t結構
    ngx_buf_tag_t    tag;
    // 當buf所包含的内容在檔案中時,file字段指向對應的檔案對象上去
    ngx_file_t      *file;

    // 當這個buf完整copy了另外一個buf的所有字段的時候,那麼這個兩個buf指向的實際上是同一塊記憶體,或者是
    // 同一個檔案的同一部分,此時這兩個buf的shadow字段指向對方的。釋放的時候特别注意。
    ngx_buf_t       *shadow;


    // 為1表示該buf所包含的内容是一個使用者建立的記憶體塊中,并且可以被在filter處理的過程中進行變更,
    // 不會造成問題
    unsigned         temporary:1;

    /*the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     *
     * 為1表示該buf所包含的内容是在記憶體中,但是這些内容卻不能被進行處理的filter進行變更
     */
    unsigned         memory:1;

    /* the buf's content is mmap()ed and must not be changed
     * 為1表示該buf所包含的内容是在記憶體中,是通過mmap使用記憶體映射從檔案中映射到記憶體中的,
     * 這些内容不能進行變更
     */
    unsigned         mmap:1;
    // 可以回收,也就是這個buf是可以釋放的
    unsigned         recycled:1;
    // 為1時表示該buf所包含的内容是在檔案中
    unsigned         in_file:1;
    // 遇到有flush字段被設定為1的buf的chain,則該chain的資料即便不是最後結束的資料,也會輸出 ??
    unsigned         flush:1;
    /* 對于操作這塊緩沖區是否使用同步方式,需要謹慎考慮,這可能會阻塞nginx程序,nginx中所有的操作
     * 幾乎都是異步的,這是它支援高并發的關鍵。
     */
    unsigned         sync:1;
    // 資料被多個chain傳遞給過濾器,此字段為1表明這是最後一個buf
    unsigned         last_buf:1;
    // 在目前的chain裡面,此buf是最後一個。
    unsigned         last_in_chain:1;

    // 在建立一個buf的shadow的時候,通常将新建立的一個buf的last_shadow置為1
    unsigned         last_shadow:1;
    // 由于記憶體使用的限制,有時候一些buf的内容需要被寫到磁盤上的臨時檔案中去,這個時候就設定次标志
    unsigned         temp_file:1;

    // 這個參數還沒有弄清楚 ????
    /* STUB */ int   num;
};
           

來一張手繪圖清晰明了,這個就是用掃描全能王app拍的,我畫的太醜了,能了解清除意思就可以

ngx_buf_t資料結構

 ngx_bufs_t結構體

// ngx_bufs_t起到了一個管理作用,用于說明目前使用的bufs的數量和每個buf的存儲空間
// 在ngx_create_chain_of_bufs可以看到怎麼使用
typedef struct {
    ngx_int_t    num;
    size_t       size;
} ngx_bufs_t;
           

 宏定義和部分函數聲明解釋,一些函數涉及body filter(包體過濾)http的東西先不管,主要說資料結構。nginx代碼看起來就很舒服,很多資料類型,宏定義,哪怕一個很簡單的操作都要用nginx的标準封裝,也是值得學習的地方

#define NGX_CHAIN_ERROR     (ngx_chain_t *) NGX_ERROR

// 傳回這個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))

// 對于建立temporary字段為1的buf(内容可以被後續的filter子產品進行修改),直接用ngx_create_temp_buf個函數建立
// 其中pool配置設定該buf和buf使用的記憶體,size是該buf使用的記憶體大小
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);
ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs);

#define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))

// 該函數建立一個ngx_chain_t對象,并傳回指向對象的指針,失敗傳回NULL
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);
// 該函數釋放一個ngx_chain_t的對象,如果要釋放整個chain,則疊代此連結清單
// 對ngx_chain_t的釋放,并不是真正的釋放,而是把這個對象挂在pool對象一個叫chain的
// 字段對應的chain上,以供下次可以快速取到
#define ngx_free_chain(pool, cl)                                             \
    cl->next = pool->chain;                                                  \
    pool->chain = cl
           

二、函數的解釋 , 函數在/src/core/ngx_buf.c檔案中。裡面的函數都比較簡單,說的徹底一點,還是指針指來指去

#include <ngx_config.h>
#include <ngx_core.h>

// 用ngx_create_temp_buf函數在記憶體池上建立一個臨時緩沖區
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
    ngx_buf_t *b;

    b = ngx_calloc_buf(pool);  // 從記憶體池pool中建立一個ngx_buf_t的大小的記憶體,在ngx_buf.h中宏定義了
    if (b == NULL) {
        return NULL;
    }

    b->start = ngx_palloc(pool, size);// 開辟緩沖區空間
    if (b->start == NULL) {
        return NULL;
    }
    /*
     * set by ngx_calloc_buf():
     *
     *     b->file_pos = 0;
     *     b->file_last = 0;
     *     b->file = NULL;
     *     b->shadow = NULL;
     *     b->tag = 0;
     *     and flags
     */
   // 起始位置pos和start
    b->pos = b->start;
    b->last = b->start;
    b->end = b->last + size; // end為緩沖區末尾
    b->temporary = 1;     // 臨時緩沖區,記憶體可以被修改

    return b;
}

// 建立緩沖區連結清單,如果有直接拿,麼有從pool請求一段空間
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool)
{
    ngx_chain_t  *cl;

    cl = pool->chain;

    if (cl) {
        //因為c1将會被使用,是以指向c1的下一個
        pool->chain = cl->next;
        return cl;
    }

    cl = ngx_palloc(pool, sizeof(ngx_chain_t));
    if (cl == NULL) {
        return NULL;
    }

    return cl;
}

// ngx_create_temp_buf隻是建立了單個緩沖區空間。如果要建立一片連續的緩沖區,
// 就由連結清單統一管理,則需要使用ngx_create_chain_of_bus函數
// 從記憶體池pool中建立一個bufs->num長度的buf->size大小的連結清單,傳回ngx_chain_t連結清單頭節點
ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs)
{
    u_char       *p;
    ngx_int_t     i;
    ngx_buf_t    *b;
    ngx_chain_t  *chain, *cl, **ll;

    p = ngx_palloc(pool, bufs->num * bufs->size); // 上面描述了
    if (p == NULL) {
        return NULL;
    }

    // 這裡用一個二級指針儲存第一個節點指針位址,同樣也儲存了後面節點指針位址,這麼做
    // 少了一次循環内的判斷,否則我們需要判斷出第一次循環,并給chain指派
    ll = &chain;

    for (i = 0; i < bufs->num; i++) {

        b = ngx_calloc_buf(pool); // 在記憶體池中建立ngx_buf_t對象,在ngx_buf.h頭檔案宏定義了
        if (b == NULL) {
            return NULL;
        }

        /*
         * set by ngx_calloc_buf():
         *
         *     b->file_pos = 0;
         *     b->file_last = 0;
         *     b->file = NULL;
         *     b->shadow = NULL;
         *     b->tag = 0;
         *     and flags
         *
         */

        // 初始化每個buf緩沖區的起始位置
        b->pos = p;
        b->last = p;
        b->temporary = 1;

        b->start = p;
        // 此時p會指向下一個緩沖區
        p += bufs->size;
        b->end = p;

        // 建立一個ngx_chain_t節點
        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NULL;
        }

        // 将ngx_chain_t與buf關聯起來
        cl->buf = b;
        // ll儲存的上一個節點指針的指針,指派相當于給上一個節點next指針指派
        *ll = cl;
        // 取本節點next指針位址,用于下次循環使用
        ll = &cl->next;
    }

    *ll = NULL;

    // 傳回ngx_chain_t連結清單的頭節點
    return chain;
}


// 将其他緩沖區拷貝到已有緩沖區末尾,也就是in連結清單插入到chain連結清單末尾
ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, ngx_chain_t *in)
{
    ngx_chain_t  *cl, **ll;

    ll = chain;

    // 找到chain連結清單的結尾,然後給ll
    for (cl = *chain; cl; cl = cl->next) {
        ll = &cl->next;
    }

    // 周遊in,并且建立配置設定chain的基本節點,并将其buf指向in的部分
    while (in) {
        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NGX_ERROR;
        }

        cl->buf = in->buf;  // 開始複制
        *ll = cl;           // 将next指向c1
        ll = &cl->next;     // 将ll指針移向c1的next
        in = in->next;      // in向下移動
    }

    *ll = NULL;  // 最後一個節點指向NULL

    return NGX_OK;
}


// 擷取一個可用的ngx_chain_t節點,上面需要有一個可用的ngx_buf_t
ngx_chain_t *ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free)
{
    ngx_chain_t  *cl;

    // 若有空閑連結清單中有節點,直接傳回
    if (*free) {
        cl = *free;
        *free = cl->next; // 不太明白為什麼再移動指針
        cl->next = NULL;
        return cl;
    }

    // 程式移動到這裡表示free是個空指針,需要申請空間
    cl = ngx_alloc_chain_link(p);
    if (cl == NULL) {
        return NULL;
    }

    cl->buf = ngx_calloc_buf(p);  // 申請新的buf空間
    if (cl->buf == NULL) {
        return NULL;
    }

    cl->next = NULL;

    return cl;
}


// ngx_chain_update_chains函數會将busy連結清單中的空閑節點回收到free連結清單中
// 将out連結清單插入到busy連結清單尾部,同時将合并後的連結清單從頭開始的所有沒有使用的節點,插入到空閑連結清單
// 合并連結清單後,out為NULL
void
ngx_chain_update_chains(ngx_pool_t *p, ngx_chain_t **free, ngx_chain_t **busy,
    ngx_chain_t **out, ngx_buf_tag_t tag)
{
    ngx_chain_t  *cl;

    if (*busy == NULL) {
        *busy = *out;

    } else {
        for (cl = *busy; cl->next; cl = cl->next) { /* void */ }

        cl->next = *out;
    }

    *out = NULL;

    while (*busy) {
        cl = *busy;

        // 這個節點記憶體占有,不滿足條件,直接退出
        if (ngx_buf_size(cl->buf) != 0) {
            break;
        }

       //  緩沖區類型不為void *,直接釋放,在循環開始
        if (cl->buf->tag != tag) {
            *busy = cl->next;
            ngx_free_chain(p, cl);
            continue;
        }

        // 重置緩沖區所有空間都可用
        cl->buf->pos = cl->buf->start;
        cl->buf->last = cl->buf->start;

        // 将該空閑區加入到free連結清單表頭
        *busy = cl->next;
        cl->next = *free;
        *free = cl;
    }
}
           

三、graphviz畫圖工具

       graphviz采用dot語言,就感覺像描述一樣,随便畫出來的圖就是這個樣子了

ngx_buf_t資料結構
ngx_buf_t資料結構

看源碼梳理函數完全可以用這個,我貼出來一些連結

   1.windows下Graphviz安裝及入門教程      http://m.blog.csdn.net/article/details?id=49472949

   2.利用Graphviz 畫結構圖

      http://www.cnblogs.com/sld666666/archive/2010/06/25/1765510.html

   3.使用Graphviz繪制流程圖和關系圖

      http://www.tuicool.com/articles/qeqeuyb

 如果有什麼問題,歡迎交流

繼續閱讀