访问这里,获取更多原创内容。
说明:本系列的文章基于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()的源码,就很容易理解“基于页的内存分配”流程和实现机制了。