nginx的请求分布在不同的进程,如果进程间需要交互各种不同大小的对象, 需要共享一些复杂的数据结构, 如链表、 树、 图等, nginx实现了一套高效的slab内存管理机制, 可以帮助我们快速实现多种对象间的跨Nginx worker进程通信

slab内存池的操作接口

void ngx_slab_sizes_init(void);
void ngx_slab_init(ngx_slab_pool_t *pool);
void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);
void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);
void *ngx_slab_calloc(ngx_slab_pool_t *pool, size_t size);
void *ngx_slab_calloc_locked(ngx_slab_pool_t *pool, size_t size);
void ngx_slab_free(ngx_slab_pool_t *pool, void *p);
void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);

如何获取pool

其入参都需要一个ngx_slab_pool_t*的池对象

其中,我们要使用到的一个接口

ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag);

其放回的结构

struct ngx_shm_zone_s {void                     *data;ngx_shm_t                 shm;ngx_shm_zone_init_pt      init;void                     *tag;void                     *sync;ngx_uint_t                noreuse;  /* unsigned  noreuse:1; */
};

其中init函数是一定会执行

static ngx_int_t ngx_init_zone_pool(ngx_cycle_t *cycle, ngx_shm_zone_t *zn)

而ngx_slab_pool_t* pool 对象就是通过这个函数初始化的

slab的实现原理

有两种内存分配算法:

first-fit:first-fit将从头遍历空闲内存块构成的链表, 当找到的第1块空间大于请求size的内存块时, 就把它返回给申请者.效率高但浪费

best-fit:遍历空闲链表, 但如果一块空闲内存的空间远大于请求size, 为了避免浪费, 它会继续向后遍历, 看看有没有恰好适合申请大小的空闲内存块, 这个算法将试图返回最适合(例如内存块大小等于或者略大于申请size) 的内存块.效率相对较低但合适

nginx 采用了best-fit,nginx假定所有需要使用slab内存的模块请求分配的内存都是比较小的(绝大部分小于4KB) 。 有了这个假定, 就有了一种快速找到最合适内存块的方法。

先上代码,代码带注释:

slab初始化

void ngx_slab_init(ngx_slab_pool_t *pool)
{u_char           *p;size_t            size;ngx_int_t         m;ngx_uint_t        i, n, pages;ngx_slab_page_t  *slots, *page;// 最小的大小为8 = 1<<3pool->min_size = (size_t) 1 << pool->min_shift;// 获取槽的首地址slots = ngx_slab_slots(pool);p = (u_char *) slots;// size = slots 到内存池结尾的大小size = pool->end - p;ngx_slab_junk(p, size);// pagesize_shift 有12个bit - 3个bit 为 9,为什么这么设计?// 其实n个槽的数量是一页内存对应的块的数量 8 16 32 64 128 256 512 1024 2048n = ngx_pagesize_shift - pool->min_shift;for (i = 0; i < n; i++) {/* only "next" is used in list head */slots[i].slab = 0;slots[i].next = &slots[i];slots[i].prev = 0;}// 偏移内存页信息,9个slotsp += n * sizeof(ngx_slab_page_t);// 偏移页状态,9个pool->stats = (ngx_slab_stat_t *) p;ngx_memzero(pool->stats, n * sizeof(ngx_slab_stat_t));p += n * sizeof(ngx_slab_stat_t);// 剩下去除pool_t, slots, stats 后的大小size -= n * (sizeof(ngx_slab_page_t) + sizeof(ngx_slab_stat_t));// 计算剩下还可以分配多少个内存页 每个页大小ngx_pagesize, 页信息sizeof(ngx_slab_page_t)pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));// 前面的初始化pages个页信息pool->pages = (ngx_slab_page_t *) p;ngx_memzero(pool->pages, pages * sizeof(ngx_slab_page_t));// page为可分配的页信息初始地址page = pool->pages;/* only "next" is used in list head */pool->free.slab = 0;pool->free.next = page;pool->free.prev = 0;// slab 赋值为页数page->slab = pages;page->next = &pool->free;page->prev = (uintptr_t) &pool->free;// start 为真正的内存页数据的地址,其需要进行页对齐 所以page 与start之间是可能有空隙的pool->start = ngx_align_ptr(p + pages * sizeof(ngx_slab_page_t),ngx_pagesize);// 重新根据对齐的地址,计算真正可分配的页数m = pages - (pool->end - pool->start) / ngx_pagesize;if (m > 0) {pages -= m;page->slab = pages;}// 最后一个页信息的地址pool->last = pool->pages + pages;// free为现在的空闲的页数pool->pfree = pages;// 日志没有指向\0pool->log_nomem = 1;pool->log_ctx = &pool->zero;pool->zero = '\0';
}

其结构为   

slab分配内存

其中,slot 和 page之间是如何关联

开始slot指向空指针,这时候,如果调用了分配size=10的内存,这时候会发生什么?

则从pageData获取数据,有两种情况:假定页大小为4K(linux就是4k)

一、若要分配的size>=2048,则直接获取页 NGX_SLAB_PAGE

二、如果分配的size<2048,则有以下三种情况。

1、待分配数据大小等于128:NGX_SLAB_EXACT

size=128,1页可分32块, shift=7 ,slot=4 此时slab为位图,直接表示对应的块

2、待分配数据大小大于128:NGX_SLAB_BIG

size>128  1页可分小于32块,shift>7,slot>4 此时,slab高位表示位图,低位表示块数量

3、待分配数据大小小于128:NGX_SLAB_SMALL

size<128  1页可分大于32块,shift<7 slot<4 此时,slab不起作用,因为不够表示位图。

此时位图在pageData区,先计算需要多少个位图,对应的数据位就表示接下来的块是否被使用。

代码实现:代码已经带上完整注释

其中,

1、要注意的是指针有时候会转换为数值做加减,不要被陷入指针的加减

2、>>n 可以理解为除以2^n,  <<n 可以理解为乘以 2^n

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;// ngx_slab_max_size 为页大小的一半,如果大于它,则要按页来分配if (size > ngx_slab_max_size) {ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,"slab alloc: %uz", size);// ngx_pagesize_shift 表示ngx_pagesize 最高bit位数// 判断需要多少个页面来分配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;}// 如果分配的大小大于最小的分配大小8//      则:获取对应的槽的位置比如9 对应的shift=4 slot就是1 0-7对应slot0 8-15对应slot1 类推// 小于最小分配8//      则:shift = 3 slot=0if (size > pool->min_size) {shift = 1;for (s = size - 1; s >>= 1; shift++) { /* void */ }slot = shift - pool->min_shift;} else {shift = pool->min_shift;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) {if (shift < ngx_slab_exact_shift) { // 这种情况slab无效// 位图地址bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);// 需要多少个int的map来存储位图map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));// 遍历位图for (n = 0; n < map; n++) {if (bitmap[n] != NGX_SLAB_BUSY) {for (m = 1, i = 0; m; m <<= 1, i++) {if (bitmap[n] & m) {continue;}bitmap[n] |= m;// 字节数i = (n * 8 * sizeof(uintptr_t) + i) << shift;// 把指针转换为数值,加上偏移,得到真正的指针地址p = (uintptr_t)bitmap + i;// 使用的块个数+1pool->stats[slot].used++;if (bitmap[n] == NGX_SLAB_BUSY) {for (n = n + 1; n < map; n++) {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) {// slab恰好就是位图,每一个数据对应一个槽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 { /* shift > ngx_slab_exact_shift */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++){if (page->slab & m) {continue;}page->slab |= m;if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {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();}// 分配一页内存到slot对应的位置page = ngx_slab_alloc_pages(pool, 1);if (page) {if (shift < ngx_slab_exact_shift) {// 分配的小块的内存小于128// 分配的数量小于128,则总的块的个数大于32// slot的前面几个字节作为位图使用。需要多少个字节根据实际情况计算mapbitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);// 计算位图需要占用多少个slot块,小于1块的,则为至少需要1块n = (ngx_pagesize >> shift) / ((1 << shift) * 8);if (n == 0) {n = 1;}// 被占用的块要赋值为1for (i = 0; i < (n + 1) / (8 * sizeof(uintptr_t)); i++) {bitmap[i] = NGX_SLAB_BUSY;}m = ((uintptr_t) 1 << ((n + 1) % (8 * sizeof(uintptr_t)))) - 1;bitmap[i] = m;// 计算出需要多少个int的mapmap = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));for (i = i + 1; i < map; i++) {bitmap[i] = 0;}page->slab = shift;page->next = &slots[slot];page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;slots[slot].next = page;pool->stats[slot].total += (ngx_pagesize >> shift) - n;p = ngx_slab_page_addr(pool, page) + (n << shift);pool->stats[slot].used++;goto done;} else if (shift == ngx_slab_exact_shift) {// 分配的中块的内存等于128// 整个slab作为位图使用,1代表第一块128 的块被使用,总共32块page->slab = 1;// 下次分配还是它,可以使用page->next = &slots[slot];// 标识为NGX_SLAB_EXACTpage->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;slots[slot].next = page;// 总的块数为32块pool->stats[slot].total += 8 * sizeof(uintptr_t);// 获取指针页对应的数据块位置pp = ngx_slab_page_addr(pool, page);// 槽对应的使用块的使用个数+1pool->stats[slot].used++;goto done;} else { /* shift > ngx_slab_exact_shift */// 分配的大块的内存大于128// 高16为存储是否使用的位图,低16位存放的是大小 2^shiftpage->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;page->next = &slots[slot];// 标识为NGX_SLAB_BIGpage->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;slots[slot].next = page;// 总的大小就是 ngx_pagesize/(2^shift)pool->stats[slot].total += ngx_pagesize >> shift;// 获取对应指针p = ngx_slab_page_addr(pool, page);// 槽个数+1pool->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;
}

slab内存释放

释放的时候,也是由四种情况,分别是NGX_SLAB_PAGE,NGX_SLAB_BIG,NGX_SLAB_EXACT,NGX_SLAB_SMALL

从分配内存我们可以知道,四种情况分别这么

1、NGX_SLAB_SMALL

要先计算出由多少位图bitmap,遍历位图,只有全部位图为0时,才释放页

2、NGX_SLAB_PAGE

直接释放页

3、NGX_SLAB_EXACT

page->slab 为零,才可以释放页

4、NGX_SLAB_BIG

page->slab的高8位为0,才可以释放页

nginx slab内存管理机制相关推荐

  1. Nginx Slab内存管理

    L38  Slub内存管理适用 ngx_http_limit_conn_module.ngx_http_limit_req_module 模块场景 我们可以用阿里第三方模块Slab_Stat模块 并且 ...

  2. Memcached内存管理机制浅析

    http://basiccoder.com/memcached-memory-mamagement.html Memcached的内存管理在网上也可以搜集到不少不错的文章,新浪的这篇<Memca ...

  3. 【Linux】内存管理机制

    Linux内存管理机制 内存的表象层次依次为 逻辑地址------>线性地址----->物理地址 逻辑地址经过段机制转换成线性地址. 线性地址经过页机制转换成物理地址. Linux将所有程 ...

  4. 什么是 Python 的 「内存管理机制」?

    什么是内存管理器(what) Python作为一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言,与大多数编程语言不同,Python中的变量无需事先申明,变量无需指定类型,程序员无需关心内存 ...

  5. python中内存管理机制一共分为多少层_python 内存管理机制

    内存管理机制 ​python中万物皆对象,python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它 ​Python的内存管理机制:引入计数.垃圾回收.内存池机制 ...

  6. JVM内存管理机制线上问题排查

    本文主要基于"深入java虚拟机"这本书总结JVM的内存管理机制,并总结了常见的线上问题分析思路.文章最后面是我对线上故障思考的ppt总结. Java内存区域 虚拟机运行时数据区如 ...

  7. 浅析java内存管理机制

    内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分.不同的编程语言有不同的内存管理机制,本文在对比C++和java语言内存管理机制的不同的基础上,浅析java中 ...

  8. 内存分段分页机制理解_深入理解虚拟机,JVM高级特性-自动内存管理机制

    什么是自动内存管理机制? 对于java程序员来说,有一点是要比C/C++程序员要方便的,那就是程序在运行时,java程序不需要为每一个对象其编写对应的释放内存的代码,JVM虚拟机将为你在合适的时间去释 ...

  9. 【Python基础】什么是Python的 “内存管理机制”

    什么是内存管理器(what) Python作为一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言,与大多数编程语言不同,Python中的变量无需事先申明,变量无需指定类型,程序员无需关心内存 ...

最新文章

  1. 李飞飞高徒新项目,一眼看穿你下届总统会选谁!
  2. 苹果被罚3.1635亿元,因不愿开放第三方支付!
  3. 4.Windows下安装ZooKeeper
  4. 【论文阅读整理】A Survey on Device-free Indoor Localization and Tracking in the Multi-resident Environment
  5. SAP Cloud for Customer Sales Lead明细页面视图的UI模型
  6. tomcat 启动速度慢背后的真相
  7. 动态调整linux分区大小,GParted 动态调整Linux分区大小
  8. javax.net.ssl.SSLException: closing inbound before receiving peer‘s close_notif---SpringCloud工作笔记111
  9. 模拟角频率和数字角频率的关系
  10. 较流行的4个开源协议
  11. Kubernetes 小白学习笔记(4)--kubernetes是什么
  12. css实现图片水平居中对齐
  13. 声音信号 dB 及 dBA 的计算方式
  14. 使用video标签时报错 Uncaught (in promise) DOMException
  15. 熵、联和熵与条件熵、交叉熵与相对熵是什么呢?来这里有详细解读!
  16. 为方便旅客,某航空公司拟开发一个机票预定系统。旅行社把预定机票的旅客信息......
  17. 关于移动Web性能的5个神话(转)
  18. 选中exchange缓存模式后 GAL不会更新
  19. 1258:【例9.2】数字金字塔
  20. 数据库MYSQL及MYSQL ODBC

热门文章

  1. 西部数码 php版本,西部数码云主机如何安装PHP版本
  2. 论文 小学生作文教学研究
  3. C++ SIGSEGV报错
  4. MySQL性能优化,索引优化
  5. Day32——122.买卖股票的最佳时机II 55. 跳跃游戏 45.跳跃游戏II +第二天复习
  6. P1506 拯救oibh总部(dfs,洛谷,java)
  7. INFO [alembic.env] No changes in schema detected.
  8. 有关代购系统搭建的那点事
  9. 旋转卡壳——凸多边形直径
  10. c语言实训的总目的意义,C语言实训总结