李子的博客

想想写写

Nginx中slab分配大内存的陷阱

nginx | Comments

我们在开发nginx模块时,需要很小心,nginx里面有很多陷阱是我们需要注意的。之前有人提到过slab分配器在使用时,不适合大内存分配,否则会出现分配不出内存的现象。

nginx一般使用slab来管理共享内存,在程序启动时,很分配好需要共享的内存,然后使用slab来进行初始化,之后就交给slab来管理这段内存。slab的源码分析与合适,在我之前的博客里面有分析过。这次我们针对性的看看,为什么会出现分配不出内存的现象。

分配内存时,会调用ngx_slab_alloc_locked,在这个函数里面会先判断size是否大于ngx_slab_max_size,代码如下。

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

    /* 判断大小 */
    if (size >= ngx_slab_max_size) {

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

        /* 直接分配页 */
        page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1)
                >> ngx_pagesize_shift);
        if (page) {
            p = (page - pool->pages) << ngx_pagesize_shift;
            p += (uintptr_t) pool->start;

        } else {
            p = 0;
        }

        goto done;
    }

    ...

}

ngx_slab_max_size在nginx调用ngx_slab_init的时候初始化为ngx_pagesize / 2。我们知道,slab会将整块的内存分成pages,每个pages大小为ngx_pagesize,slab在分配小内存时,会将一个page拆分成多个小块进行分配,而如果我们分配的内存大于ngx_pagesize / 2时,slab是没办法进行拆分的,所以当我们分配的内存大于ngx_slab_max_size时,直接分配页内存就可以了(因为不需要进行拆分)。所以这里直接调用ngx_slab_alloc_pages来分配内存。ngx_slab_alloc_pages的代码如下:

static ngx_slab_page_t *
ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages)
{
    ngx_slab_page_t  *page, *p;

    for (page = pool->free.next; page != &pool->free; page = page->next) {

        /* 判断当前页还能分配多少连续的页 */
        if (page->slab >= pages) {

            if (page->slab > pages) {
                /* 重新设置剩下还能分配的连续空间 */
                page[pages].slab = page->slab - pages;
                page[pages].next = page->next;
                page[pages].prev = page->prev;

                p = (ngx_slab_page_t *) page->prev;
                p->next = &page[pages];
                page->next->prev = (uintptr_t) &page[pages];

            } else {
                /* 剩下连续的pages正好够用 */
                p = (ngx_slab_page_t *) page->prev;
                p->next = page->next;
                page->next->prev = page->prev;
            }

            page->slab = pages | NGX_SLAB_PAGE_START;
            page->next = NULL;
            page->prev = NGX_SLAB_PAGE;

            /* 如果只需要分配一个页,则直接返回 */
            if (--pages == 0) {
                return page;
            }

            /* 否则将剩下所需要的页设置占用标记 */
            for (p = page + 1; pages; pages--) {
                p->slab = NGX_SLAB_PAGE_BUSY;
                p->next = NULL;
                p->prev = NGX_SLAB_PAGE;
                p++;
            }

            return page;
        }
    }

    ngx_slab_error(pool, NGX_LOG_CRIT, "ngx_slab_alloc() failed: no memory");

    return NULL;
}

从上面的代码中我们可以看到,在空闲页中p->slab用于标记剩下连续,连接页的第一个页会设置这个值。所以在slab初始化之后,第一个页的slab被赋值为所有页的数量。在使用过程中,由于经常alloc与free,会造成连续空闲页变得断断续续,当没有连续的所需要的空闲页进行分配时,就会出现内存无法分配的问题。所以,使用slab进行大内存分配时,就会出现内存无法分配的现象。所以,我们在使用中,应该避免使用slab进行大内存的分配。

Comments