读 nginx 源码 --- ngx_list

796 阅读2分钟

c 语言并未提供像 c++ 一样的 std 标准库,所以很多类似 list、变长数组、map 等常见的数据结构都需要自己实现。

链表使用指针将不同的内存地址相互关联,往往用于管理不连续内存,相对于数组,其内存管理更加灵活。今天让我们先看一下 nginx 中单向链表 list 的实现。

结构体与接口

typedef struct ngx_list_part_s  ngx_list_part_t;
// ngx_list_part_s 是一片连续内存,ngx_list_t 将多个 ngx_list_part_s 链接起来
// 也就是说 ngx_list_t 中的 node 其实是 ngx_list_part_s
struct ngx_list_part_s {
    void             *elts; // c 没有模版。只能通过 void * 来模拟模版
    ngx_uint_t        nelts; // 分配的元素的数量
    ngx_list_part_t  *next; // next node
};


typedef struct {
    ngx_list_part_t  *last; // ngx_list_part_s 链表的最后一个节点
    ngx_list_part_t   part; // ngx_list_part_s 链表的头元素
    size_t            size; // ngx_list_t 中元素的大小
    ngx_uint_t        nalloc; // ngx_list_t 中元素个数
    ngx_pool_t       *pool; // 其对应的内存池对象
} ngx_list_t;

// 创建一个 ngx_list_t
ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
// 插入一个元素
void *ngx_list_push(ngx_list_t *list);

ngx_list_t 本身其实是通过单向链表的形式来管理一个个的 ngx_list_part_s 结构体,ngx_list_part_s 则管理一片连续的内存(大小一但申请就不会变化)。每当调用 ngx_list_push 插入元素到 ngx_list_t 中,就会从 ngx_list_part_s 的空间中分配。

创建

/*
    创建一个 ngx_list_t,其中 n 为每个 ngx_list_part_s 中元素的个数,size 为这些元素的内存大小
*/

ngx_list_t *
ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    ngx_list_t  *list;
    // 使用内存池创建一个 ngx_list_t
    list = ngx_palloc(pool, sizeof(ngx_list_t));
    if (list == NULL) {
        return NULL;
    }
    // 为 ngx_list_part_s 链表中的 head 元素申请内存
    list->part.elts = ngx_palloc(pool, n * size);
    if (list->part.elts == NULL) {
        return NULL;
    }

    list->part.nelts = 0;
    list->part.next = NULL;
    list->last = &list->part;
    list->size = size;
    list->nalloc = n;
    list->pool = pool;

    return list;
}

插入元素

/*
    插入元素到链表中
*/
void *
ngx_list_push(ngx_list_t *l)
{
    void             *elt;
    ngx_list_part_t  *last;
    // ngx_list_part_s 链表的最后一个元素
    last = l->last;
    // 如果 ngx_list_part_s 中已经没有剩余空间
    if (last->nelts == l->nalloc) {
        // 构造一个新 ngx_list_part_s 对象
        last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
        if (last == NULL) {
            return NULL;
        }
        // 申请 ngx_list_part_s 存放对象的内存
        last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
        if (last->elts == NULL) {
            return NULL;
        }
        
        last->nelts = 0;
        last->next = NULL;
        // 将新的 ngx_list_part_s 插入到链表尾端
        l->last->next = last;
        l->last = last;
    }
    // 在 ngx_list_part_s 中分配一个元素
    elt = (char *) last->elts + l->size * last->nelts;
    last->nelts++;

    return elt;
}

总结

不同于常见的 list,ngx_list_t 中每个 node 是一个一片的连续内存,可以理解为一个个数组通过 next 指针相连。这样做可以降低单次插入元素的成本,但是却存在一定程度上的浪费空间。