Sword nginx slab源码解析四(slot块分配)
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, m, mask, *bitmap; ngx_uint_t i, n, slot, shift, map; ngx_slab_page_t* page, * prev, * slots; if (size > ngx_slab_max_size) { // 如果申请的共享内存大于2048字节 /* 设计说明: 因为申请的字节大于2048字节,因此直接分配一页(4096字节) */ ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab alloc: %uz", 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; } if (size > pool->min_size) { // 申请的字节大于pool->min_size(8字节) shift = 1; // 计算出偏移量 for (s = size - 1; s >>= 1; shift++) { /* void */ } // 获取slot下标 slot = shift - pool->min_shift; } else { // 如果申请的字节数小于8字节,按8字节算 shift = pool->min_shift; // slots下标为0 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) { // 已经初始化过slots if (shift < ngx_slab_exact_shift) { // size 小于 128字节 // 提取当前页的 bitmap bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page); // 计算bitmap数组的大小 /* 设计说明: 假设ngx_pagesize=4096字节,shift = 3 那么一页可以拆分512个8字节的slot块,即需要512bit来标记每个slot的使用情况(已使用为1,未使用为0) 512bit即64字节,每个bitmap单元需要4个字节,因此需要16个bitmap单元才能表示512个slot map = 16 */ map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t)); // 遍历bitmap数组 for (n = 0; n < map; n++) { if (bitmap[n] != NGX_SLAB_BUSY) { // 当前bitmap[n]还有可用的slot for (m = 1, i = 0; m; m <<= 1, i++) { if (bitmap[n] & m) { // 当前bit对应的slot块已经被使用,跳过 continue; } // 找到一个未使用的bit // 设置当前bit为1,表示被占用 bitmap[n] |= m; // 计算偏移量 /* 设计说明: n表示bitmap的下标,每个bitmap可以表示 8 * sizeof(uintptr_t) 个slot i 为当前 bitmap[n] 的偏移量 (n * 8 * sizeof(uintptr_t) + i) 表示使用到当前页的第几个slot (n * 8 * sizeof(uintptr_t) + i) << shift 可以计算出相对于 bitmap 的偏移量 */ i = (n * 8 * sizeof(uintptr_t) + i) << shift; // 定位到当前slot /* 设计说明: i 是偏移的字节数 bitmap 是当前页起始位置 (uintptr_t) bitmap 将指针类型转成整数类型,方便进行偏移量计算 或者 p = (char*)bitmap + i 也是可以的 */ p = (uintptr_t) bitmap + i; // 记录使用 pool->stats[slot].used++; if (bitmap[n] == NGX_SLAB_BUSY) { // 当前bitmap[n]管理的slot已经完全被使用完了 for (n = n + 1; n < map; n++) { // 当前页还有可以分配的slot 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) { // 申请slot为128字节 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 { // 申请内存块大于128字节 /* 设计说明: shift = 8(256字节),mask=0xFFFF0000 shift = 9(512字节),mask=0xFF0000 shift = 10(1024字节),mask=0xF0000 */ 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++) { // 查找可用的slot if (page->slab & m) { continue; } page->slab |= m; if ((page->slab & NGX_SLAB_MAP_MASK) == mask) { // 表示当前页上slot已经全部用完 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(); } // 申请一页 page = ngx_slab_alloc_pages(pool, 1); if (page) { if (shift < ngx_slab_exact_shift) { // 申请内存块小于128字节 // 页的开头内存作为bitmap /* 设计说明: 每页的前面的n个slot不能用来分配给用户使用,他们是用来记录当前页上slot的使用情况的 每页上slot单元越多,需要记录slot的bitmap单元也就越多 目前最小的slot是8字节,一页可以分配512个slot,但是为了记录512个slot,那么需要512bit来记录,对应的字节是64字节 那么需要消耗8个slot单元来记录当前页上slot信息 每个bitmap单元是4个字节,即需要16个bitmap单元来记录slots信息 */ bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page); // 计算一页中可以划分多少slot /* 设计说明: ngx_pagesize >> shift 计算每页可以划分多少个slot 每个slot的大小是 1 << shift 8bit 等于 1字节 该页上slot信息需要n个slot单元才能记录 */ n = (ngx_pagesize >> shift) / ((1 << shift) * 8); if (n == 0) { // 至少使用1个slot n = 1; } /* "n" elements for bitmap, plus one requested */ /* 设计说明: n + 1 表示需要使用n+1个slot单元来记录slot信息 为啥是n+1而不是n呢? 因为本次分配就要占据一个slot,所以是n+1 (8 * sizeof(uintptr_t) 表示每个 bitmap 可以记录32个slot信息 (n + 1) / (8 * sizeof(uintptr_t)) 表示记录slot需要占据bitmap单元的个数 即使是slot=8,那么当前页以记录信息为目的消耗的slot数目是8,那么一个bitmap也足够了 */ for (i = 0; i < (n + 1) / (8 * sizeof(uintptr_t)); i++) { bitmap[i] = NGX_SLAB_BUSY; } /* 设计说明: 这里m的值就是为了将bitmap[i]前面 (n+1) bit全部设置为1 这是一种常用的位移操作,当物理公式记住就行,不需要理解 */ m = ((uintptr_t) 1 << ((n + 1) % (8 * sizeof(uintptr_t)))) - 1; // 更新bitmap单元 bitmap[i] = m; // 计算bitmap单元的个数 map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t)); for (i = i + 1; i < map; i++) { // 初始化为0 bitmap[i] = 0; } // 设置当前页的偏移量 page->slab = shift; page->next = &slots[slot]; // 标记当前页是用于分配小于128字节的slot page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL; slots[slot].next = page; // 新页可以为pool->stats[slot].total增加多少可用slot单元 pool->stats[slot].total += (ngx_pagesize >> shift) - n; // 获取可用的内存节点 p = ngx_slab_page_addr(pool, page) + (n << shift); // 更新userd pool->stats[slot].used++; goto done; } else if (shift == ngx_slab_exact_shift) { // 申请内存块等于128字节 /* 设计说明: 当申请内存块大小等于128字节的时候,使用page->slab来代替page 因为一页4096字节,至多可以划分出32个128字节大小的slot块 那么可以使用page->slab来记录slot的使用情况 对内存的使用很精细 */ // 标记第一块slot已经被占用 page->slab = 1; page->next = &slots[slot]; page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT; slots[slot].next = page; // 设置总数量增加32个slot pool->stats[slot].total += 8 * sizeof(uintptr_t); p = ngx_slab_page_addr(pool, page); // 已使用数量自增 pool->stats[slot].used++; goto done; } else { // 申请内存块大于128字节 /* 设计说明: 当申请内存块大小大于128字节的时候,使用page->slab来代替page 因为一页4096字节,至多可以划分出16个大于128字节大小的slot块 那么可以使用page->slab的[高16位]来记录slot的使用情况 ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) 高16位记录使用情况 低16位用来存储shift信息 备注: 32位机器上,NGX_SLAB_MAP_SHIFT=16,uintptr_t即unsigned int */ page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift; page->next = &slots[slot]; page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG; slots[slot].next = page; pool->stats[slot].total += ngx_pagesize >> shift; p = ngx_slab_page_addr(pool, page); pool->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; } void* ngx_slab_calloc(ngx_slab_pool_t* pool, size_t size) { void *p; ngx_shmtx_lock(&pool->mutex); p = ngx_slab_calloc_locked(pool, size); ngx_shmtx_unlock(&pool->mutex); return p; } void* ngx_slab_calloc_locked(ngx_slab_pool_t* pool, size_t size) { void *p; p = ngx_slab_alloc_locked(pool, size); if (p) { ngx_memzero(p, size); } return p; }