#内存管理的艺术# 之 Nginx slab的实现 --- 第二篇“基于页的内存分配”

梦想的初衷 提交于 2020-03-02 14:04:28

访问这里,获取更多原创内容。    

    说明:本系列的文章基于Nginx-1.5.0版本代码。

    在上一篇中已经介绍了Nginx slab分配器的基本原理和内存空间布局,现在我们将在此基础上引入“基于页的内存分配”的相关内容。之所以这样安排是因为它的实现相对于“基于块的内存分配”要简单许多,同时它又是“基于块的内存分配”的基础,以它为突破口怎么看都是最好的选择:)

    在”基于页的内存分配“流程中只需要用到”page页内存管理单元“,而不涉及”分级内存管理单元“,为了方便讨论,我们将初始化后的内存布局图简化如下:


 

一、内存的分配

    Nginx slab本身没有对外提供专门的按页分配内存的接口,具体采用哪种分配方式是在内部根据传入的size值并结合一定的算法来进行决策的。以ngx_slab_alloc为例:

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, n, m, mask, *bitmap;
    ngx_uint_t        i, slot, shift, map;
    ngx_slab_page_t  *page, *prev, *slots;

    /*若要求分配的内存大小超过1/2页,则采用“按页分配”的方式*/
    /*注意,这里的判断在最新的版本里好像已经改为">"了,因为1/2页本身就是一个管理分级*/
    if (size >= ngx_slab_max_size) {

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

        /*将size向上取整到页大小的整数倍上*/
        page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift)
                                          + ((size % ngx_pagesize) ? 1 : 0));
        if (page) {
            p = (page - pool->pages) << ngx_pagesize_shift;
            p += (uintptr_t) pool->start;

        } else {
            p = 0;
        }

        goto done;
    }
    ...
    ...
done:

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

    return (void *) p;
}

/*“基于页的内存分配”函数实现,参数为要求分配的页数*/
static ngx_slab_page_t *
ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages)
{
    ngx_slab_page_t  *page, *p;

    /*“free”是空闲页管理单元链表头*/
    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 {
                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;
}

    下面这两幅图非常直观地说明了当初始化完成之后(假设此时共有N个空闲页),分别申请m(< N)页和N页内存时的情形。

    下面再进一步来看一下当进行过若干次页分配后的内存空间布局,这里我们假设分配的顺序为:m0页、1页、m1页、1页:

    有了上面的几幅图,再结合ngx_slab_alloc_pages()的源码,就很容易理解“基于页的内存分配”流程和实现机制了。

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!