njs内存池代码阅读

1. njs内存池总览

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oOLdpDOs-1661698484065)(./1.png)]

2. 结构定义

njs_mp_s

struct njs_mp_s {
    /* rbtree of njs_mp_block_t. */
    njs_rbtree_t                blocks;            //block红黑树(根据njs_mp_block_t-start地址排序)

    njs_queue_t                 free_pages;        //可用的page池

    uint8_t                     chunk_size_shift;  //最小单位的指数幂
    uint8_t                     page_size_shift;   //page大小的指数幂
    uint32_t                    page_size;         //page大小
    uint32_t                    page_alignment;    //对其内存大小
    uint32_t                    cluster_size;      //一个block资源内存的总大小(cluster_size>>page_size_shift 是block page的总数量)

    njs_mp_cleanup_t            *cleanup;          //内存池清理函数链

    njs_mp_slot_t               slots[];           //槽,每个槽使用的size不一样,根据内存申请的大小去判断使用哪个槽
};

这个结构是njs内存池的主结构,描述了整个内存池的状态

slots是描述内存大小分块,把内存分类(分为16、32、64、128…)等内存块,使用内存分配是取最小的那个分配

njs_mp_slot_t

typedef struct {
    njs_queue_t                 pages;          //这个slot可使用的page(没有会去njs_mp_s->free_pages要)

    /* Size of page chunks. */
#if (NJS_64BIT)
    uint32_t                    size;           //page中每一个内存块的大小
#else
    uint16_t                    size;
#endif

    /* Maximum number of free chunks in chunked page. */
    uint8_t                     chunks;         //page中最大的内存块数量
} njs_mp_slot_t;

这个结构保存了这个槽使用的内存的大小(16、32、64…),chunks是这个page内存块最大的内存数量

njs_mp_block_t

typedef struct {
    NJS_RBTREE_NODE             (node);         //挂载到njs_mp_s->blocks的节点变量
    njs_mp_block_type_t         type:8;

    /* Block size must be less than 4G. */
    uint32_t                    size;           //start申请内存的大小

    u_char                      *start;         //申请的内存块(内存池真正使用的资源)
    njs_mp_page_t               pages[];        //pages数组大小是(mp->cluster_size>>page_size_shift)
} njs_mp_block_t;

start 是这个内存池真正使用的内存

size 是start内存的大小

njs_mp_page_t

typedef struct {
    njs_queue_link_t            link;       //挂载在njs_mp_slot_t->pages的节点变量
    uint8_t                     size;       //代表多少个最小单位(chunk)的数量
    uint8_t                     number;     //page在njs_mp_block_t的下标
    uint8_t                     chunks;     //剩余可使用的资源数量
    uint8_t                     _unused;
    uint8_t                     map[4];     //位图,用来标志内存是否被使用(是否释放)
} njs_mp_page_t;

这个结构其实算是njs_mp_block_t的描述结构

3. 创建内存池

NJS_EXPORT njs_mp_t *njs_mp_create(size_t cluster_size, size_t page_alignment,
    size_t page_size, size_t min_chunk_size) NJS_MALLOC_LIKE;
njs_mp_t *
njs_mp_create(size_t cluster_size, size_t page_alignment, size_t page_size,
    size_t min_chunk_size)
{
    /* Alignment and sizes must be a power of 2. */
	//检查这些参数是不是2的倍数
    if (njs_slow_path(!njs_is_power_of_two(page_alignment)
                     || !njs_is_power_of_two(page_size)
                     || !njs_is_power_of_two(min_chunk_size)))
    {
        return NULL;
    }
	//NJS_MAX_ALIGNMENT 最小对齐单位是16
    page_alignment = njs_max(page_alignment, NJS_MAX_ALIGNMENT);
	//参数校验
    if (njs_slow_path(page_size < 64
                     || page_size < page_alignment
                     || page_size < min_chunk_size
                     || min_chunk_size * 32 < page_size		//这里有个细节就是njs_mp_page_t的map最多表示32位位图
                     || cluster_size < page_size
                     || cluster_size / page_size > 256
                     || cluster_size % page_size != 0))
    {
        return NULL;
    }

    return njs_mp_fast_create(cluster_size, page_alignment, page_size,
                              min_chunk_size);
}

njs_mp_t *
njs_mp_fast_create(size_t cluster_size, size_t page_alignment, size_t page_size,
    size_t min_chunk_size)
{
    njs_mp_t       *mp;
    njs_uint_t     slots, chunk_size;
    njs_mp_slot_t  *slot;

    slots = 0;
    chunk_size = page_size;
    //计算njs_mp_t->slots数组大小
    do {
        slots++;
        chunk_size /= 2;
    } while (chunk_size > min_chunk_size);

    mp = njs_zalloc(sizeof(njs_mp_t) + slots * sizeof(njs_mp_slot_t));

    if (njs_fast_path(mp != NULL)) {
        mp->page_size = page_size;
        mp->page_alignment = njs_max(page_alignment, NJS_MAX_ALIGNMENT);
        mp->cluster_size = cluster_size;

        slot = mp->slots;
        //初始化每个数组
        do {
            njs_queue_init(&slot->pages);

            slot->size = chunk_size;
            /* slot->chunks should be one less than actual number of chunks. */
            slot->chunks = (page_size / chunk_size) - 1;

            slot++;
            chunk_size *= 2;
        } while (chunk_size < page_size);
        //保存2的指数幂
        mp->chunk_size_shift = njs_mp_shift(min_chunk_size);
        mp->page_size_shift = njs_mp_shift(page_size);

        njs_rbtree_init(&mp->blocks, njs_mp_rbtree_compare);

        njs_queue_init(&mp->free_pages);
    }

    return mp;
}

将下列结果带入

cluster_size= 8192

page_size = 512

min_chunk_size = 16

初始化结果就是这张图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3FljCuJJ-1661698484066)(./2.png)]

4. 内存申请

4.1 size >mp->page_size

NJS_EXPORT void *njs_mp_alloc(njs_mp_t *mp, size_t size)
    NJS_MALLOC_LIKE;
void *
njs_mp_alloc(njs_mp_t *mp, size_t size)
{
    njs_debug_alloc("mp alloc: %uz\n", size);

#if !(NJS_DEBUG_MEMORY)

    if (size <= mp->page_size) {
        return njs_mp_alloc_small(mp, size);
    }

#endif

    return njs_mp_alloc_large(mp, NJS_MAX_ALIGNMENT, size);
}

可以看到内存申请函数有两个分支

当申请的内存大小大于mp->page_size直接走大块内存分配

我们先看njs_mp_alloc_large这个函数

static void *
njs_mp_alloc_large(njs_mp_t *mp, size_t alignment, size_t size)
{
    u_char          *p;
    size_t          aligned_size;
    uint8_t         type;
    njs_mp_block_t  *block;
    //不超过4G
    /* Allocation must be less than 4G. */
    if (njs_slow_path(size >= UINT32_MAX)) {
        return NULL;
    }
    //是否是2的倍数
    if (njs_is_power_of_two(size)) {
        block = njs_malloc(sizeof(njs_mp_block_t));
        if (njs_slow_path(block == NULL)) {
            return NULL;
        }

        p = njs_memalign(alignment, size);
        if (njs_slow_path(p == NULL)) {
            njs_free(block);
            return NULL;
        }

        type = NJS_MP_DISCRETE_BLOCK;

    } else {
        //使用字节对齐(32位是4字节对齐,64位是8字节对齐)
        aligned_size = njs_align_size(size, sizeof(uintptr_t));

        p = njs_memalign(alignment, aligned_size + sizeof(njs_mp_block_t));
        if (njs_slow_path(p == NULL)) {
            return NULL;
        }

        block = (njs_mp_block_t *) (p + aligned_size);
        type = NJS_MP_EMBEDDED_BLOCK;
    }

    block->type = type;
    block->size = size;
    block->start = p;

    njs_rbtree_insert(&mp->blocks, &block->node);

    return p;
}

总申请内存大小的 sizeof(njs_mp_block_t) + size,这里分了两种申请方式。

第一种是分两段式申请内存

第二种是一次性分配完

申请完成后把这个block挂到红黑树中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sJo78WSS-1661698484066)(./3.png)]

这个大块内存申请相对简单,和我们正常使用malloc相差不大

最后看看这个小内存申请函数 njs_mp_alloc_small


static void *
njs_mp_alloc_small(njs_mp_t *mp, size_t size)
{
    u_char            *p;
    njs_mp_page_t     *page;
    njs_mp_slot_t     *slot;
    njs_queue_link_t  *link;

    p = NULL;

    if (size <= mp->page_size / 2) {

        //查找匹配size区间的slot
        for (slot = mp->slots; slot->size < size; slot++) { /* void */ }

        size = slot->size;
        //slot的page不为空
        if (njs_fast_path(!njs_queue_is_empty(&slot->pages))) {

            link = njs_queue_first(&slot->pages);
            page = njs_queue_link_data(link, njs_mp_page_t, link);

            p = njs_mp_page_addr(mp, page);
            p += njs_mp_alloc_chunk(page->map, size);
            //剩余数组元素数量减一
            page->chunks--;
            //没有剩余直接移除
            if (page->chunks == 0) {
                /*
                 * Remove full page from the mp chunk slot list
                 * of pages with free chunks.
                 */
                njs_queue_remove(&page->link);
            }

        } else {
            //从mp->free_pagrs获取到一个page,如果没用则创建一个block
            page = njs_mp_alloc_page(mp);

            if (njs_fast_path(page != NULL)) {

                njs_queue_insert_head(&slot->pages, &page->link);

                //把位图 map 最高位置为1
                page->map[0] = 0x80;
                page->map[1] = 0;
                page->map[2] = 0;
                page->map[3] = 0;

                // chunks 是数组元素剩余的个数
                page->chunks = slot->chunks;
                // size 存的是最小元素的个数
                page->size = size >> mp->chunk_size_shift;

                p = njs_mp_page_addr(mp, page);
            }
        }

    } else {
        page = njs_mp_alloc_page(mp);

        if (njs_fast_path(page != NULL)) {
            page->size = mp->page_size >> mp->chunk_size_shift;

            p = njs_mp_page_addr(mp, page);
        }

#if (NJS_DEBUG)
        size = mp->page_size;
#endif
    }

    njs_debug_alloc("mp chunk:%uz alloc: %p\n", size, p);

    return p;
}

在分配大块内存的时候我们知道一个njs_mp_block_t结构这个结构的start成员指向了一个真正申请的内存资源。这一次的小内存申请的主角是njs_mp_page_t结构,这个结构负责去管理njs_mp_block_t申请的内存

这个内存分配函数也有几种情况

  • size<= mp->page_size / 2
  • mp->page_size / 2 < size <= mp->page_size (>mp->page_size 走大块内存分配)

4.2 mp->page_size / 2 < size <= mp->page_size

我们继续走简单的分支

static njs_mp_page_t *
njs_mp_alloc_page(njs_mp_t *mp)
{
    njs_mp_page_t     *page;
    njs_mp_block_t    *cluster;
    njs_queue_link_t  *link;
    //如果mp page为空
    if (njs_queue_is_empty(&mp->free_pages)) {
        cluster = njs_mp_alloc_cluster(mp);
        if (njs_slow_path(cluster == NULL)) {
            return NULL;
        }
    }
    //从mp队列头部取出
    link = njs_queue_first(&mp->free_pages);
    njs_queue_remove(link);
    //offset 拿到真正类型的首地址
    page = njs_queue_link_data(link, njs_mp_page_t, link);

    return page;
}

static njs_mp_block_t *
njs_mp_alloc_cluster(njs_mp_t *mp)
{
    njs_uint_t      n;
    njs_mp_block_t  *cluster;
	//计算page的个数
    n = mp->cluster_size >> mp->page_size_shift;

    cluster = njs_zalloc(sizeof(njs_mp_block_t) + n * sizeof(njs_mp_page_t));

    if (njs_slow_path(cluster == NULL)) {
        return NULL;
    }

    /* NJS_MP_CLUSTER_BLOCK type is zero. */

    cluster->size = mp->cluster_size;
    //mp->cluster_size内存大小
    cluster->start = njs_memalign(mp->page_alignment, mp->cluster_size);
    if (njs_slow_path(cluster->start == NULL)) {
        njs_free(cluster);
        return NULL;
    }
    //初始化page的number并且添加到mp->free_pages队列
    n--;
    cluster->pages[n].number = n;
    njs_queue_insert_head(&mp->free_pages, &cluster->pages[n].link);

    while (n != 0) {
        n--;
        cluster->pages[n].number = n;
        njs_queue_insert_before(&cluster->pages[n + 1].link,
                                &cluster->pages[n].link);
    }

    njs_rbtree_insert(&mp->blocks, &cluster->node);

    return cluster;
}

page = njs_mp_alloc_page(mp);

if (njs_fast_path(page != NULL)) {
    page->size = mp->page_size >> mp->chunk_size_shift;

    p = njs_mp_page_addr(mp, page);
}

njs_mp_alloc_page函数是创建一个njs_mp_page_t结构,这个结构又两种获取方式

  • mp->free_pages队列去要
  • 使用njs_mp_alloc_cluster函数去创建 (一次创建mp->cluster_size >> mp->page_size_shift个)

第一种方式是njs_mp_page_t的缓存队列释放时候和创建的时候都会放在这里,内存池不会销毁这个结构块而是会缓存起来。但是在刚开始时这个队列肯定时空的。

我们看直接创建的方式njs_mp_alloc_cluster函数

  • 首先使用 mp->cluster_size >> mp->page_size_shift计算page个数
  • 分配njs_mp_block_t + page个数 的内存
  • 分配mp->cluster_size的内存给cluster->start
  • 之后就是初始化page和加入free_pages队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ftcHpe3S-1661698484067)(./4.png)]

使用njs_mp_alloc_cluster函数创建完page后在从队列中取出一个出去

回到njs_mp_alloc_small的那个分支

page = njs_mp_alloc_page(mp);

if (njs_fast_path(page != NULL)) {
    page->size = mp->page_size >> mp->chunk_size_shift;

    p = njs_mp_page_addr(mp, page);
}

page->size的值存储的时mp->page_size >> mp->chunk_size_shift 其实就是有多少个min_chunk_size(最小内存单位)

最后就是njs_mp_page_addr函数,这个函数是计算page在blocks属于他管的内存

block->start的内存布局

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3CA3beXV-1661698484067)(./5.png)]

njs_inline u_char *
njs_mp_page_addr(njs_mp_t *mp, njs_mp_page_t *page)
{
    njs_mp_block_t  *block;
    //计算block的地址
    block = (njs_mp_block_t *)
                ((u_char *) page - page->number * sizeof(njs_mp_page_t)
                 - offsetof(njs_mp_block_t, pages));
    //block->start + page->number * mp->page_size_shift
    return block->start + (page->number << mp->page_size_shift);
}

这个函数很好理解,njs_mp_block_t的内存是这样子分配的

cluster = njs_zalloc(sizeof(njs_mp_block_t) + n * sizeof(njs_mp_page_t));

这段内存是线性的。首先获得pages的地址

(u_char *) page - page->number * sizeof(njs_mp_page_t)

然后减去njs_mp_block_t首地址到pages成员的地址距离

((u_char *) page - page->number * sizeof(njs_mp_page_t)
                 - offsetof(njs_mp_block_t, pages))

这样子就得到了block的地址

最后返回page对应的管理的内存地址

block->start + (page->number << mp->page_size_shift);

到这里 mp->page_size / 2 < size <= mp->page_size内存分配就走完了

4.3 size<= mp->page_size / 2

这个是内存分配的最后一个分支

在这个分支中终于用到了njs_mp_slot_t结构,这个结构管理不同内存大小的块,初始化时已经给定了值

njs_mp_slot_t->size 所管理的内存大小(16,32…)

njs_mp_slot_t->chunks 最大块的数量 (page_size/njs_mp_slot_t->size - 1)

//查找匹配size区间的slot
for (slot = mp->slots; slot->size < size; slot++) { /* void */ }

size = slot->size;
//slot的page不为空
if (njs_fast_path(!njs_queue_is_empty(&slot->pages))) {

    link = njs_queue_first(&slot->pages);
    page = njs_queue_link_data(link, njs_mp_page_t, link);

    p = njs_mp_page_addr(mp, page);
    p += njs_mp_alloc_chunk(page->map, size);
    //剩余数组元素数量减一
    page->chunks--;
    //没有剩余直接移除
    if (page->chunks == 0) {
        njs_queue_remove(&page->link);
    }

} else {
    //从mp->free_pagrs获取到一个page,如果没用则创建一个block
    page = njs_mp_alloc_page(mp);

    if (njs_fast_path(page != NULL)) {

        njs_queue_insert_head(&slot->pages, &page->link);

        //把位图 map 最高位置为1
        page->map[0] = 0x80;
        page->map[1] = 0;
        page->map[2] = 0;
        page->map[3] = 0;

        // chunks 是数组元素剩余的个数
        page->chunks = slot->chunks;
        // size 存的是最小元素的个数
        page->size = size >> mp->chunk_size_shift;

        p = njs_mp_page_addr(mp, page);
    }
}

程序一开始先查找当前申请内存大小合适的槽,确认内存分配的实际大小(比如你想分配2字节,实际可能分配了16字节)

//查找匹配size区间的slot
for (slot = mp->slots; slot->size < size; slot++) { /* void */ }
//真正分配的内存大小
size = slot->size;

后面时判断这个槽有没有真在使用的page,当然一开始是没有的,我们来看没有的情况

page = njs_mp_alloc_page(mp);

if (njs_fast_path(page != NULL)) {

    njs_queue_insert_head(&slot->pages, &page->link);

    //把位图 map 最高位置为1
    page->map[0] = 0x80;
    page->map[1] = 0;
    page->map[2] = 0;
    page->map[3] = 0;

    // chunks 是数组元素剩余的个数
    page->chunks = slot->chunks;
    // size 存的是最小元素的多少被
    page->size = size >> mp->chunk_size_shift;

    p = njs_mp_page_addr(mp, page);
}

这个和第二种内存分配差不多,这里多了njs_mp_alloc_page后把page放到slot的队列中

page->map是用来标记内存的使用情况,这里最大标记32个内存块,这个细节在创建内存池的时候判断过

//参数校验
if (njs_slow_path(page_size < 64
                  || page_size < page_alignment
                  || page_size < min_chunk_size
                  || min_chunk_size * 32 < page_size		//这里有个细节就是njs_mp_page_t的map最多表示32位位图
                  || cluster_size < page_size
                  || cluster_size / page_size > 256
                  || cluster_size % page_size != 0))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AuUOtZCT-1661698484068)(./6.png)]

最后一块我们来看slot->pages不为空的情况

static njs_uint_t
njs_mp_alloc_chunk(uint8_t *map, njs_uint_t size)
{
    uint8_t     mask;
    njs_uint_t  n, offset;
    offset = 0;
    n = 0;

    for ( ;; ) {
        if (map[n] != 0xff) {                       //map[n]还没有填满
            mask = 0x80;
            //遍历map[n]
            do {
                if ((map[n] & mask) == 0) {         //如果这一位没有被使用
                    /* A free chunk is found. */
                    map[n] |= mask;                 //设置为已使用
                    return offset;                  //返回偏移地址
                }

                offset += size;
                mask >>= 1;                         //map[n]的下一位

            } while (mask != 0);                    //map[n]遍历完成

        } else {
            /* Fast-forward: all 8 chunks are occupied. */
            offset += size * 8;                     //如果map[n]已经填满,直接设置这一个map的偏移
        }
        n++;                                        //指向下一个map
    }
}


link = njs_queue_first(&slot->pages);                   //拿第一个节点
page = njs_queue_link_data(link, njs_mp_page_t, link);  //拿njs_mp_page_t的首地址

p = njs_mp_page_addr(mp, page);                         //拿njs_mp_page_t管理的内存地址
p += njs_mp_alloc_chunk(page->map, size);				//拿一个没有使用的size大小的内存地址
//剩余数组元素数量减一
page->chunks--;
//没有剩余直接移除
if (page->chunks == 0) {
    njs_queue_remove(&page->link);                      //移出队列
}

可以看到这个逻辑也很简单,主要是查找map的使用情况,然后返回地址

到这里整个内存分配函数已经走完

njs还提供了一些别的内存分配的接口

NJS_EXPORT void *njs_mp_alloc(njs_mp_t *mp, size_t size)		//正常内存分配
    NJS_MALLOC_LIKE;
NJS_EXPORT void *njs_mp_zalloc(njs_mp_t *mp, size_t size)		//njs_mp_alloc完后内存初始化0
    NJS_MALLOC_LIKE;
NJS_EXPORT void *njs_mp_align(njs_mp_t *mp, size_t alignment, size_t size) //内存对齐分配
    NJS_MALLOC_LIKE;
NJS_EXPORT void *njs_mp_zalign(njs_mp_t *mp,					//njs_mp_align完后内存初始化0
    size_t alignment, size_t size)

5. 内存释放

NJS_EXPORT void njs_mp_free(njs_mp_t *mp, void *p);


void
njs_mp_free(njs_mp_t *mp, void *p)
{
    const char      *err;
    njs_mp_block_t  *block;

    njs_debug_alloc("mp free: @%p\n", p);
    //查找p 所在的block
    block = njs_mp_find_block(&mp->blocks, p);

    if (njs_fast_path(block != NULL)) {

        if (block->type == NJS_MP_CLUSTER_BLOCK) {
            err = njs_mp_chunk_free(mp, block, p);

            if (njs_fast_path(err == NULL)) {
                return;
            }

        } else if (njs_fast_path(p == block->start)) {
            njs_rbtree_delete(&mp->blocks, &block->node);

            if (block->type == NJS_MP_DISCRETE_BLOCK) {
                njs_free(block);
            }

            njs_free(p);

            return;

        } else {
            err = "freed pointer points to middle of block: %p\n";
        }

    } else {
        err = "freed pointer is out of mp: %p\n";
    }

    njs_debug_alloc(err, p);
}

释放首先做的事情是去查找p在那个block里面。每一个block都挂在mp->blocks这个红黑树中,查找也是用的红黑树查找

static njs_mp_block_t *
njs_mp_find_block(njs_rbtree_t *tree, u_char *p)
{
    njs_mp_block_t     *block;
    njs_rbtree_node_t  *node, *sentinel;

    node = njs_rbtree_root(tree);
    sentinel = njs_rbtree_sentinel(tree);

    while (node != sentinel) {

        block = (njs_mp_block_t *) node;

        if (p < block->start) {
            node = node->left;

        } else if (p >= block->start + block->size) {
            node = node->right;

        } else {
            return block;
        }
    }

    return NULL;
}

这里不细讲njs红黑树的设计,初始化红黑树的时候会要使用者添加比较函数,block的比较函数是按照地址去比较的

static intptr_t
njs_mp_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2)
{
    njs_mp_block_t  *block1, *block2;

    block1 = (njs_mp_block_t *) node1;
    block2 = (njs_mp_block_t *) node2;

    return (uintptr_t) block1->start - (uintptr_t) block2->start;
}

查找完成后有两种释放的方式

if (block->type == NJS_MP_CLUSTER_BLOCK) {
    err = njs_mp_chunk_free(mp, block, p);

    if (njs_fast_path(err == NULL)) {
        return;
    }

} else if (njs_fast_path(p == block->start)) {
    njs_rbtree_delete(&mp->blocks, &block->node);

    if (block->type == NJS_MP_DISCRETE_BLOCK) {
        njs_free(block);
    }

    njs_free(p);

    return;
}

5.1 大块内存释放

根据type类型去觉得释放的方式NJS_MP_CLUSTER_BLOCK是默认的方式也就是0,也就是内存在**<= mp->page_size / 2**的申请方式

其他两种方式相对简单,还记得大块内存的两种申请方式吗

if (njs_is_power_of_two(size)) {
    block = njs_malloc(sizeof(njs_mp_block_t));
    if (njs_slow_path(block == NULL)) {
        return NULL;
    }

    p = njs_memalign(alignment, size);
    if (njs_slow_path(p == NULL)) {
        njs_free(block);
        return NULL;
    }

    type = NJS_MP_DISCRETE_BLOCK;

} else {
    //使用字节对齐(32位是4字节对齐,64位是8字节对齐)
    aligned_size = njs_align_size(size, sizeof(uintptr_t));

    p = njs_memalign(alignment, aligned_size + sizeof(njs_mp_block_t));
    if (njs_slow_path(p == NULL)) {
        return NULL;
    }

    block = (njs_mp_block_t *) (p + aligned_size);
    type = NJS_MP_EMBEDDED_BLOCK;
}

这两种方式区别在于一次性申请和分两次申请,所以NJS_MP_DISCRETE_BLOCK这种申请方式需要释放两段内存

njs_rbtree_delete(&mp->blocks, &block->node);

if (block->type == NJS_MP_DISCRETE_BLOCK) {
    njs_free(block);
}

njs_free(p);

5.2 小块内存释放

static const char *
njs_mp_chunk_free(njs_mp_t *mp, njs_mp_block_t *cluster,
    u_char *p)
{
    u_char         *start;
    uintptr_t      offset;
    njs_uint_t     n, size, chunk;
    njs_mp_page_t  *page;
    njs_mp_slot_t  *slot;
    //获取地址p在block的第几个page,向下取整
    n = (p - cluster->start) >> mp->page_size_shift;
    //拿到这个page资源的起始地址
    start = cluster->start + (n << mp->page_size_shift);
    //拿到这个page结构地址
    page = &cluster->pages[n];

    if (page->size == 0) {
        return "freed pointer points to already free page: %p";
    }
	//p的内存大小
    size = page->size << mp->chunk_size_shift;

    if (size != mp->page_size) {
        //获取数组的下标
        offset = (uintptr_t) (p - start) & (mp->page_size - 1);
        chunk = offset / size;

        if (njs_slow_path(offset != chunk * size)) {
            return "freed pointer points to wrong chunk: %p";
        }
        //已经释放或者没有被使用
        if (njs_slow_path(njs_mp_chunk_is_free(page->map, chunk))) {
            return "freed pointer points to already free chunk: %p";
        }
        //把对应map置为0
        njs_mp_chunk_set_free(page->map, chunk);

        /* Find a slot with appropriate chunk size. */
        for (slot = mp->slots; slot->size < size; slot++) { /* void */ }

        if (page->chunks != slot->chunks) {
            page->chunks++; //可使用数组加一
            //如果刚好为1 重新添加到slot的使用队列
            if (page->chunks == 1) {
                njs_queue_insert_head(&slot->pages, &page->link);
            }

            njs_mp_free_junk(p, size);

            return NULL;

        } else {
            //page全部释放完毕
            njs_queue_remove(&page->link);
        }

    } else if (njs_slow_path(p != start)) {
        return "invalid pointer to chunk: %p";
    }

    //添加到释放的队列
    page->size = 0;
    njs_queue_insert_head(&mp->free_pages, &page->link);

    njs_mp_free_junk(p, size);

    page = cluster->pages;
    n = mp->cluster_size >> mp->page_size_shift;
    //如果这个block的page还有被使用的直接返回
    do {
         if (page->size != 0) {
             return NULL;
         }

         page++;
         n--;
    } while (n != 0);

    //如果这个block的size都为0
    page = cluster->pages;
    n = mp->cluster_size >> mp->page_size_shift;
    //移出队列
    do {
         njs_queue_remove(&page->link);
         page++;
         n--;
    } while (n != 0);
    //移出红黑树
    njs_rbtree_delete(&mp->blocks, &cluster->node);

    p = cluster->start;
    //释放内存
    njs_free(cluster);
    njs_free(p);

    return NULL;
}

if (block->type == NJS_MP_CLUSTER_BLOCK) {
    err = njs_mp_chunk_free(mp, block, p);

    if (njs_fast_path(err == NULL)) {
        return;
    }
}

对于小于mp->page_size需要把page->map对应的使用位置为0

njs封装了对应的宏操作

#define njs_mp_chunk_is_free(map, chunk)                                      \
    ((map[chunk / 8] & (0x80 >> (chunk & 7))) == 0)


#define njs_mp_chunk_set_free(map, chunk)                                     \
    map[chunk / 8] &= ~(0x80 >> (chunk & 7))

一个是判断这块内存有没有被释放,一个是对应的位设置位0

设置完成后有一个page的内存是否释放完成的判断

if (page->chunks != slot->chunks) {
    page->chunks++; //可使用数组加一
    //如果刚好为1 重新添加到slot的使用队列
    if (page->chunks == 1) {
        njs_queue_insert_head(&slot->pages, &page->link);
    }

    njs_mp_free_junk(p, size);

    return NULL;

} else {
    //page全部释放完毕
    njs_queue_remove(&page->link);
}

page->chunks == slot->chunks代表着这个page的内存块已经全部释放,把这个pageslot中移出出去

page->chunks == 1的判断,代表之前这个page内存已经全部使用完,当前释放了一个把这个page重新给slot使用,然后直接返回

可以看到page->chunks == slot->chunks的状态函数没有返回,后续的逻辑会对整个page处理,包括申请内存块大小为mp->page_size的page

//添加到释放的队列
page->size = 0;
njs_queue_insert_head(&mp->free_pages, &page->link);

njs_mp_free_junk(p, size);

page = cluster->pages;
n = mp->cluster_size >> mp->page_size_shift;
//如果这个block的page还有被使用的直接返回
do {
    if (page->size != 0) {
        return NULL;
    }
    page++;
    n--;
} while (n != 0);

//如果这个block的size都为0
page = cluster->pages;
n = mp->cluster_size >> mp->page_size_shift;
//移出队列
do {
    njs_queue_remove(&page->link);
    page++;
    n--;
} while (n != 0);
//移出红黑树
njs_rbtree_delete(&mp->blocks, &cluster->node);

p = cluster->start;
//释放内存
njs_free(cluster);
njs_free(p);

首先是把page放回到mp->free_pages可以在下次需要page的时候使用

后续会判断,这个page对应的block的内存是否全部被释放,判断的方式就是把blockpage全部遍历看看有没有使用的page,如果没有被使用的page则释放整个block

6.内存池销毁

注册销毁回调函数

struct njs_mp_cleanup_s {
        njs_mp_cleanup_pt   handler;
        void                *data;
        njs_mp_cleanup_t    *next;
};

njs_mp_cleanup_t *
njs_mp_cleanup_add(njs_mp_t *mp, size_t size)
{
    njs_mp_cleanup_t  *c;

    c = njs_mp_alloc(mp, sizeof(njs_mp_cleanup_t));
    if (njs_slow_path(c == NULL)) {
        return NULL;
    }

    if (size) {
        c->data = njs_mp_alloc(mp, size);
        if (njs_slow_path(c->data == NULL)) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }
    //头插
    c->handler = NULL;
    c->next = mp->cleanup;

    mp->cleanup = c;

    njs_debug_alloc("mp add cleanup: @%p\n", c);

    return c;
}

注册销毁函数链(mp->cleanup)销毁时统一调用

void
njs_mp_destroy(njs_mp_t *mp)
{
    void               *p;
    njs_mp_block_t     *block;
    njs_mp_cleanup_t   *c;
    njs_rbtree_node_t  *node, *next;

    njs_debug_alloc("mp destroy\n");
	//销毁函数
    for (c = mp->cleanup; c != NULL; c = c->next) {
        if (c->handler != NULL) {
            njs_debug_alloc("mp run cleanup: @%p\n", c);
            c->handler(c->data);
        }
    }

    next = njs_rbtree_root(&mp->blocks);
    //释放block
    while (next != njs_rbtree_sentinel(&mp->blocks)) {

        node = njs_rbtree_destroy_next(&mp->blocks, &next);
        block = (njs_mp_block_t *) node;

        p = block->start;

        if (block->type != NJS_MP_EMBEDDED_BLOCK) {
            njs_free(block);
        }

        njs_free(p);
    }
    //释放内存池结构
    njs_free(mp);
}

销毁函数非常简单,就是销毁所有blocks和自己mp

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值