说明:本系列的文章基于Nginx-1.5.0版本代码。
在上一篇”基于块的内存释放“中,我们已经见过一个函数:
static void
ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page, ngx_uint_t pages);
单从名字应该就已经能够猜到这个函数的作用了,没错,就是本篇的主题 — ”基于页的内存释放“,当释放的内存类型为”NGX_SLAB_PAGE“,或者与待释放的内存块所对应的页已经完全释放时,就到了这个函数大显身手的时候了,但它的内容却只有短短的十几行代码:
static void
ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page,
ngx_uint_t pages)
{
ngx_slab_page_t *prev;
page->slab = pages--;
/*如果待释放的内存空间不止一页,则需要将后续的页管理单元恢复为初始化状态*/
if (pages) {
ngx_memzero(&page[1], pages * sizeof(ngx_slab_page_t));
}
/*根据前面几篇的内容可以知道,当页内存管理单元挂接在slot分级链表下时,page->next是不为空的,这时需要先将其从链表中摘除*/
if (page->next) {
prev = (ngx_slab_page_t *) (page->prev & ~NGX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;
}
/*将待释放的空闲页管理单元挂接到free链表的首部,但这里并没有做额外的合并动作*/
page->prev = (uintptr_t) &pool->free;
page->next = pool->free.next;
page->next->prev = (uintptr_t) page;
pool->free.next = page;
}
这里我们以第二篇“基于页的内存分配”中的最后一幅图所描述的场景为例,也就是说在初始化之后相继分配了m0页,1页,m1页,1页,然后我们来释放掉中间的m1页,这时的内存布局就变成了如下的形式:
. 这里需要指出的是,ngx_slab_free_pages()函数只是将待释放的内存页管理单元重新挂接到了free链表的首部,而没有尝试进行合并, <在1.93中已经加入join的部分代码> 所以当程序运行一段时间后你可能会发现空闲的内存明明还有很多,但大块的内存申请总是会返回错误,就是因为内存被碎片化了,没有满足要求的连续内存段可以分配了。这个问题在更新的版本中不知道有没有改进。
. 除此之外还有一种情况就是:当待释放的内存页是用于小块内存分配时,调用ngx_slab_free_pages()函数时对应的页内存管理单元还挂接在slot分级链表下,这时需要先将页内存管理单元从slot分级链表中摘除,然后再挂接到free空闲页链表中。
. 如果你看到这里了,那么Nginx slab内存管理相关内容的学习应该也告一段落了,这个系列的文章中加入了不少内存布局图,目的就在于更加清楚直观地展现出内存分配和释放这些动态过程中的各个细节,方便大家理解。
希望这个系列的文章能够对你有帮助。