nginx高级数据类型(3) - 缓冲区、数据链

2018-07-23 11:28:50

nginx 收发的大量数据有时是连续的内存块,有时是多个分散的内存块,甚至有时候数据过大,存入到磁盘文件。

ngx_str_t 结构可以表示内存块,但它过于简单,不能应付复杂的场景,所以 nginx 实现了 ngx_buf_t 和 ngx_chain_t 结构。

ngx_buf_t 表示一个单块的缓冲区,既可以是内存也可以是文件。它的结构比较复杂,可以分为两部分:缓冲区信息和标志位信息。

缓冲区信息

// 用于ngx_buf_t,关联任意的数据
typedef void *            ngx_buf_tag_t;

typedef struct ngx_buf_s  ngx_buf_t;
struct ngx_buf_s {
    u_char          *pos;           //内存数据的起始位置
    u_char          *last;          //内存数据的结束位置
    off_t            file_pos;      //文件数据的起始偏移量
    off_t            file_last;     //文件数据的结束偏移量

    u_char          *start;         //内存数据的上界
    u_char          *end;           //内存数据的下届
    ngx_buf_tag_t    tag;           //void*指针,可以是任意数据
    ngx_file_t      *file;          //存储数据的文件对象
    ...
};

因为 Nginx 里的缓冲数据可能在内存或者磁盘文件中,所以 ngx_buf_t 使用 pos/last 和 file_pos/file_last来指定数据在内存或者文件中的具体位置,究竟数据是在哪里则要由后面的标志位信息来确定。

start 和 end 两个成员变量标记了数据所在内存块的边界,如果内存块是可以修改的, 那么在操作时必须参考这两个成员防止越界。

tag是一个比较特殊的成员,它的类型是 void *,用户可以关联任意数据,在代码中任意解释,通常它指向的是使用该缓冲区的对象。

标志位信息

struct ngx_buf_s {
    ...

    unsigned         temporary:1;   //内存块临时数据,可以修改

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    // 内存缓存或只读内存,不允许修改
    unsigned         memory:1;

    unsigned         mmap:1;        //内存映射数据,不允许修改

    unsigned         recycled:1;
    unsigned         in_file:1;     //缓冲区在文件里
    unsigned         flush:1;       //要求Nginx立即输出本缓冲区
    unsigned         sync:1;        //要求Nginx同步操作本缓冲区
    unsigned         last_buf:1;    //最后一块缓冲区
    unsigned         last_in_chain:1;   //链里的最后一块缓冲区

    unsigned         last_shadow:1;
    unsigned         temp_file:1;       //缓冲区在临时文件里

    /* STUB */ int   num;
};

last_buf:1 标志代表 tcp/http 请求处理的结束。

一个有数据的缓冲区要么在内存里,要么在文件里,如果都不在,那么只起到控制作用,比如刷新(flush) 或者 同步(sync)。

操作函数

// 检查多个标志位,确定缓冲区是否在内存里
#define ngx_buf_in_memory(b)        (b->temporary || b->memory || b->mmap)
#define ngx_buf_in_memory_only(b)   (ngx_buf_in_memory(b) && !b->in_file)

// 起控制作用的特殊缓冲区
#define ngx_buf_special(b)                                                   \
    ((b->flush || b->last_buf || b->sync)                                    \
     && !ngx_buf_in_memory(b) && !b->in_file)

#define ngx_buf_sync_only(b)                                                 \
    (b->sync                                                                 \
     && !ngx_buf_in_memory(b) && !b->in_file && !b->flush && !b->last_buf)

// 计算缓冲区的大小,会根据是否在内存里使用恰当的指针
#define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                            (b->file_last - b->file_pos))
// 直接从内存池创建一个ngx_buf_t结构
#define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))

// 从内存池里分配一块size大小的缓冲区
// 并使用buf管理,注意temporary是1,内存可以修改
ngx_buf_t *
ngx_create_temp_buf(ngx_pool_t *pool, size_t size){
    ngx_buf_t *b;

    b = ngx_calloc_buf(pool);
    if (b == NULL) {
        return NULL;
    }

    b->start = ngx_palloc(pool, size);
    if (b->start == NULL) {
        return NULL;
    }

    b->pos = b->start;
    b->last = b->start;
    b->end = b->last + size;
    b->temporary = 1;

    return b;
}

数据块链

nginx 经常会创建很多缓冲区来存放数据,然后通过 ngx_chain_t 单向链来连接。

typedef struct ngx_chain_s  ngx_chain_t;

struct ngx_chain_s {
    ngx_buf_t    *buf;      //缓冲区指针
    ngx_chain_t  *next;     //下一个链表节点
};

// 释放链表节点,挂在空闲链表里
#define ngx_free_chain(pool, cl)    \
    cl->next = pool->chain;         \
    pool->chain = cl
// 从内存池的空闲链表里取一个对象
// 如果空闲链表是空才真正创建对象
// 这是对象池模式,提高运行效率
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 在 nginx 里用的很频繁,所以 nginx 对此进行了优化。在内存池里保存了一个空闲 ngx_chain_t 链表,分配时从这个链表里摘取,释放时再挂上去。

注意:由于 ngx_alloc_chain_link 内部使用的是 ngx_palloc(),所以 buf 和 next 指针可能是任意值,不能假设 next指针为空指针,如果是最后一个链,那么请手动指定 next 为 NULL。

//创建链表的参数结构
typedef struct {
    ngx_int_t    num;       //缓冲区的数量,即节点数量
    size_t       size;      //缓冲区的大小
} ngx_bufs_t;

ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs);

// 创建多个链表节点并连接好,每个链表里包含 一个buf。
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;
    }

    ll = &chain;

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

        b = ngx_calloc_buf(pool);
        if (b == NULL) {
            return NULL;
        }

        b->pos = p;
        b->last = p;
        b->temporary = 1;

        b->start = p;
        p += bufs->size;
        b->end = p;

        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NULL;
        }

        cl->buf = b;
        *ll = cl;
        ll = &cl->next;
    }

    *ll = NULL;

    return chain;
}

内存图大致如下

链表复制

下面的函数重新分配了链表的内存,链在chain后面。

// 从内存池里分配节点
// 拷贝in链表里的buf到chain里,不是直接连接
// 只是重新分配了 ngx_chain_t,里面的 buf没变。
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链表的末尾
    for (cl = *chain; cl; cl = cl->next) {
        ll = &cl->next;
    }

    // 从内存池里分配节点
    // 拷贝buf到chain里,不是直接连接
    while (in) {
        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NGX_ERROR;
        }

        cl->buf = in->buf;
        *ll = cl;
        ll = &cl->next;
        in = in->next;
    }

    *ll = NULL;

    return NGX_OK;
}

获取链表和buf

// 先看free里是否有空闲节点,有则直接使用
// 如果没有,就从内存池的空闲链表里获取
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;
    }

    cl = ngx_alloc_chain_link(p);
    if (cl == NULL) {
        return NULL;
    }

    cl->buf = ngx_calloc_buf(p);
    if (cl->buf == NULL) {
        return NULL;
    }

    cl->next = NULL;

    return cl;
}


备注

1.nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/d6


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。