Sword nginx slab源码解析四(slot块分配)

void* ngx_slab_alloc(ngx_slab_pool_t* pool, size_t size)
{
    void* p;

    // 进程间加锁保护
    ngx_shmtx_lock(&pool->mutex);

    // 申请内存块
    p = ngx_slab_alloc_locked(pool, size);

    // 进程间解锁
    ngx_shmtx_unlock(&pool->mutex);

    return p;
}


void* ngx_slab_alloc_locked(ngx_slab_pool_t* pool, size_t size)
{
    size_t            s;
    uintptr_t         p, m, mask, *bitmap;
    ngx_uint_t        i, n, slot, shift, map;
    ngx_slab_page_t*  page, * prev, * slots;

    if (size > ngx_slab_max_size) 
    {
        // 如果申请的共享内存大于2048字节

        /*
        设计说明:
            因为申请的字节大于2048字节,因此直接分配一页(4096字节)
        */

        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
                       "slab alloc: %uz", size);

        // 获取空闲页
        page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift)
                                          + ((size % ngx_pagesize) ? 1 : 0));
        if (page) 
        {
            p = ngx_slab_page_addr(pool, page);

        } else 
        {
            p = 0;
        }

        goto done;
    }

    if (size > pool->min_size) 
    {
        // 申请的字节大于pool->min_size(8字节)
        shift = 1;
        // 计算出偏移量
        for (s = size - 1; s >>= 1; shift++) { /* void */ }
        // 获取slot下标
        slot = shift - pool->min_shift;

    } 
    else 
    {
        // 如果申请的字节数小于8字节,按8字节算
        shift = pool->min_shift;
        // slots下标为0
        slot = 0;
    }

    pool->stats[slot].reqs++;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
                   "slab alloc: %uz slot: %ui", size, slot);

    // 
    slots = ngx_slab_slots(pool);
    page = slots[slot].next;

    if (page->next != page) 
    {
        // 已经初始化过slots

        if (shift < ngx_slab_exact_shift) 
        {
            // size 小于 128字节

            // 提取当前页的 bitmap
            bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);

            // 计算bitmap数组的大小
            /*
            设计说明:
                假设ngx_pagesize=4096字节,shift = 3
                那么一页可以拆分512个8字节的slot块,即需要512bit来标记每个slot的使用情况(已使用为1,未使用为0)
                512bit即64字节,每个bitmap单元需要4个字节,因此需要16个bitmap单元才能表示512个slot
                map = 16
            */
            map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));

            // 遍历bitmap数组
            for (n = 0; n < map; n++) 
            {

                if (bitmap[n] != NGX_SLAB_BUSY) 
                {
                    // 当前bitmap[n]还有可用的slot
                    for (m = 1, i = 0; m; m <<= 1, i++) 
                    {
                        if (bitmap[n] & m) 
                        {
                            // 当前bit对应的slot块已经被使用,跳过
                            continue;
                        }

                        // 找到一个未使用的bit

                        // 设置当前bit为1,表示被占用
                        bitmap[n] |= m;

                        // 计算偏移量
                        /*
                        设计说明:
                            n表示bitmap的下标,每个bitmap可以表示 8 * sizeof(uintptr_t) 个slot
                            i 为当前 bitmap[n] 的偏移量
                            (n * 8 * sizeof(uintptr_t) + i) 表示使用到当前页的第几个slot
                            (n * 8 * sizeof(uintptr_t) + i) << shift 可以计算出相对于 bitmap 的偏移量
                        */
                        i = (n * 8 * sizeof(uintptr_t) + i) << shift;

                        // 定位到当前slot
                        /*
                        设计说明:
                            i 是偏移的字节数
                            bitmap 是当前页起始位置
                            (uintptr_t) bitmap 将指针类型转成整数类型,方便进行偏移量计算
                            或者 p = (char*)bitmap + i 也是可以的
                        */
                        p = (uintptr_t) bitmap + i;

                        // 记录使用
                        pool->stats[slot].used++;

                        if (bitmap[n] == NGX_SLAB_BUSY) 
                        {
                            // 当前bitmap[n]管理的slot已经完全被使用完了

                            for (n = n + 1; n < map; n++) 
                            {
                                // 当前页还有可以分配的slot
                                if (bitmap[n] != NGX_SLAB_BUSY) 
                                {
                                    goto done;
                                }
                            }

                            // 当前页上所有的slot已经被使用光了

                            // 从链表上将该页摘除
                            prev = ngx_slab_page_prev(page);
                            prev->next = page->next;
                            page->next->prev = page->prev;

                            // 标记该页属性
                            page->next = NULL;
                            page->prev = NGX_SLAB_SMALL;
                        }

                        goto done;
                    }
                }
            }

        } 
        else if (shift == ngx_slab_exact_shift) 
        {
            // 申请slot为128字节

            for (m = 1, i = 0; m; m <<= 1, i++) 
            {
                if (page->slab & m) {
                    continue;
                }

                page->slab |= m;

                if (page->slab == NGX_SLAB_BUSY) 
                {
                    // 当前页已经用光了
                    prev = ngx_slab_page_prev(page);
                    prev->next = page->next;
                    page->next->prev = page->prev;

                    page->next = NULL;
                    page->prev = NGX_SLAB_EXACT;
                }

                p = ngx_slab_page_addr(pool, page) + (i << shift);

                pool->stats[slot].used++;

                goto done;
            }

        } 
        else 
        {
            // 申请内存块大于128字节

            /*
            设计说明:
                shift = 8(256字节),mask=0xFFFF0000
                shift = 9(512字节),mask=0xFF0000
                shift = 10(1024字节),mask=0xF0000
            */
            mask = ((uintptr_t) 1 << (ngx_pagesize >> shift)) - 1;
            mask <<= NGX_SLAB_MAP_SHIFT;

            for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;
                 m & mask;
                 m <<= 1, i++)
            {
                // 查找可用的slot
                if (page->slab & m) 
                {
                    continue;
                }

                page->slab |= m;

                if ((page->slab & NGX_SLAB_MAP_MASK) == mask) 
                {
                    // 表示当前页上slot已经全部用完
                    prev = ngx_slab_page_prev(page);
                    prev->next = page->next;
                    page->next->prev = page->prev;

                    page->next = NULL;
                    page->prev = NGX_SLAB_BIG;
                }

                p = ngx_slab_page_addr(pool, page) + (i << shift);

                pool->stats[slot].used++;

                goto done;
            }
        }

        ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_alloc(): page is busy");
        ngx_debug_point();
    }

    // 申请一页
    page = ngx_slab_alloc_pages(pool, 1);

    if (page) 
    {

        if (shift < ngx_slab_exact_shift) 
        {
            // 申请内存块小于128字节

            // 页的开头内存作为bitmap
            /*
            设计说明:
                每页的前面的n个slot不能用来分配给用户使用,他们是用来记录当前页上slot的使用情况的
                每页上slot单元越多,需要记录slot的bitmap单元也就越多
                目前最小的slot是8字节,一页可以分配512个slot,但是为了记录512个slot,那么需要512bit来记录,对应的字节是64字节
                那么需要消耗8个slot单元来记录当前页上slot信息
                每个bitmap单元是4个字节,即需要16个bitmap单元来记录slots信息
            */
            bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);
            // 计算一页中可以划分多少slot
            /*
            设计说明:
                ngx_pagesize >> shift 计算每页可以划分多少个slot
                每个slot的大小是 1 << shift 
                8bit 等于 1字节
                该页上slot信息需要n个slot单元才能记录
            */
            n = (ngx_pagesize >> shift) / ((1 << shift) * 8);

            if (n == 0) 
            {
                // 至少使用1个slot
                n = 1;
            }

            /* "n" elements for bitmap, plus one requested */
            /*
            设计说明:
                n + 1 表示需要使用n+1个slot单元来记录slot信息
                    为啥是n+1而不是n呢?
                    因为本次分配就要占据一个slot,所以是n+1

                (8 * sizeof(uintptr_t) 表示每个 bitmap 可以记录32个slot信息
                (n + 1) / (8 * sizeof(uintptr_t)) 表示记录slot需要占据bitmap单元的个数
                即使是slot=8,那么当前页以记录信息为目的消耗的slot数目是8,那么一个bitmap也足够了
            */
            for (i = 0; i < (n + 1) / (8 * sizeof(uintptr_t)); i++) 
            {
                bitmap[i] = NGX_SLAB_BUSY;
            }
            /*
            设计说明:
                这里m的值就是为了将bitmap[i]前面 (n+1) bit全部设置为1
                这是一种常用的位移操作,当物理公式记住就行,不需要理解
            */
            m = ((uintptr_t) 1 << ((n + 1) % (8 * sizeof(uintptr_t)))) - 1;
            // 更新bitmap单元
            bitmap[i] = m;

            // 计算bitmap单元的个数
            map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));

            for (i = i + 1; i < map; i++) 
            {
                // 初始化为0
                bitmap[i] = 0;
            }

            // 设置当前页的偏移量
            page->slab = shift;
            page->next = &slots[slot];
            // 标记当前页是用于分配小于128字节的slot
            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;

            slots[slot].next = page;

            // 新页可以为pool->stats[slot].total增加多少可用slot单元
            pool->stats[slot].total += (ngx_pagesize >> shift) - n;

            // 获取可用的内存节点
            p = ngx_slab_page_addr(pool, page) + (n << shift);

            // 更新userd
            pool->stats[slot].used++;

            goto done;

        } 
        else if (shift == ngx_slab_exact_shift) 
        {
            // 申请内存块等于128字节

            /*
            设计说明:
                当申请内存块大小等于128字节的时候,使用page->slab来代替page
                因为一页4096字节,至多可以划分出32个128字节大小的slot块
                那么可以使用page->slab来记录slot的使用情况
                对内存的使用很精细
            */
            // 标记第一块slot已经被占用
            page->slab = 1;
            page->next = &slots[slot];
            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;

            slots[slot].next = page;

            // 设置总数量增加32个slot
            pool->stats[slot].total += 8 * sizeof(uintptr_t);

            p = ngx_slab_page_addr(pool, page);

            // 已使用数量自增
            pool->stats[slot].used++;

            goto done;

        } 
        else 
        {
            // 申请内存块大于128字节

            /*
            设计说明:
                当申请内存块大小大于128字节的时候,使用page->slab来代替page
                因为一页4096字节,至多可以划分出16个大于128字节大小的slot块
                那么可以使用page->slab的[高16位]来记录slot的使用情况
                ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT)  高16位记录使用情况
                低16位用来存储shift信息

                备注:
                    32位机器上,NGX_SLAB_MAP_SHIFT=16,uintptr_t即unsigned int
            */

            page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;
            page->next = &slots[slot];
            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;

            slots[slot].next = page;

            pool->stats[slot].total += ngx_pagesize >> shift;

            p = ngx_slab_page_addr(pool, page);

            pool->stats[slot].used++;

            goto done;
        }
    }

    p = 0;

    pool->stats[slot].fails++;

done:

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
                   "slab alloc: %p", (void *) p);

    return (void *) p;
}


void* ngx_slab_calloc(ngx_slab_pool_t* pool, size_t size)
{
    void  *p;

    ngx_shmtx_lock(&pool->mutex);

    p = ngx_slab_calloc_locked(pool, size);

    ngx_shmtx_unlock(&pool->mutex);

    return p;
}


void* ngx_slab_calloc_locked(ngx_slab_pool_t* pool, size_t size)
{
    void  *p;

    p = ngx_slab_alloc_locked(pool, size);
    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}

 

 

posted on 2022-07-23 12:35  寒魔影  阅读(150)  评论(0编辑  收藏  举报

导航