nginx中slab实现

slab的一些结构体:

typedef struct {  
    ngx_atomic_t      lock; // 锁,因为slab在nginx中一般配合共享内存使用  
  
    size_t            min_size; // 分配空间的最小值  
    size_t            min_shift;    // 该最小值对应的移位数  
  
    ngx_slab_page_t  *pages; // 页数组  
    ngx_slab_page_t   free; // 空闲的页  
  
    u_char           *start;    // 分配地址开始地址  
    u_char           *end;    
  
    ngx_shmtx_t       mutex;  
  
    u_char           *log_ctx;  
    u_char            zero;  
  
    void             *data;  
    void             *addr;  
} ngx_slab_pool_t;  
  
// 页结构体  
struct ngx_slab_page_s {  
    uintptr_t         slab;     // 保存当前页的一些信息  
    ngx_slab_page_t  *next;// 下一个  
    uintptr_t         prev;// 上一个  
};  

slab的函数调用:

// 初始化slab池  
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_free(ngx_slab_pool_t *pool, void *p);  
// 释放空间  
void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);  

关于slab的使用,我们在介绍nginx中共享内存的时候再去介绍吧,我们只需要知道在进程初始化时,ngx_init_cycle函数会调用ngx_init_zone_pool来初始化共享内存,然后在ngx_init_zone_pool函数中会调用ngx_slab_init来初始化slab内存池。随后,在进程中,我们就可以调用alloc与free来对共享内存进行操作了。

对于64位与32位系统,nginx里面默认的值是不一样的,我们看到数字可能会更好理解一点,所以我们就以32位来看,用实际的数字来说话!

 

// 页大小
ngx_pagesize: 4096
// 页大小对应的移位数
ngx_pagesize_shift: 12
// slab的一次最大分配空间,默认为pagesize/2
ngx_slab_max_size: 2048
// slab精确分配大小,这个是一个分界点,通常是4096/32,为什么会这样,我们后面会有介绍
ngx_slab_exact_size: 128
// slab精确分配大小对应的移位数
ngx_slab_exact_shift: 7

ngx_slab_exact_size这个时依赖slab的分配算法.它的值是这样来的.4096/32,2048是slab页大小,而32是一个int的位数,最后的值是128。
why? 我们在分配时,在一页中,我们可以将这一页分成多个块,而某个块需要标记是否被分配,而一页空间正好被分成32个128字节大小的块,于是我们可以用一个int的32位表示这块的使用情况,而此时,我们是使用ngx_slab_page_s结构体中的slab成员来表示块的使用情况的。另外,在分配大于128与小于128时,表示块的占用情况有所有同
// 初始化  
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;  
  
    /* STUB */  
    if (ngx_slab_max_size == 0) {  
        // 最大分配空间为页大小的一半  
        ngx_slab_max_size = ngx_pagesize / 2;  
        // 精确分配大小,8为一个字节的位数,sizeof(uintptr_t)为一个uintptr_t的字节,我们后面会根据这个size来判断使用不同的分配算法  
        ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));  
        // 计算出此精确分配的移位数  
        for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {  
            /* void */  
        }  
    }  
    /**/  
  
    pool->min_size = 1 << pool->min_shift;  
  
    // p 指向slot数组  
    p = (u_char *) pool + sizeof(ngx_slab_pool_t);  
    size = pool->end - p;  
  
    // 将开始的size个字节设置为0  
    ngx_slab_junk(p, size);  
  
    // 某一个大小范围内的页,放到一起,具有相同的移位数  
    slots = (ngx_slab_page_t *) p;  
    // 最大移位数,减去最小移位数,得到需要的slot数量  
    // 默认为8  
    n = ngx_pagesize_shift - pool->min_shift;  
  
    // 初始化各个slot  
    for (i = 0; i < n; i++) {  
        slots[i].slab = 0;  
        slots[i].next = &slots[i];  
        slots[i].prev = 0;  
    }  
  
    // 指向页数组  
    p += n * sizeof(ngx_slab_page_t);  
  
    // 计算出当前内存空间可以放下多少个页,此时的计算没有进行对齐,在后面会进行调整  
    pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));  
  
    ngx_memzero(p, pages * sizeof(ngx_slab_page_t));  
  
    pool->pages = (ngx_slab_page_t *) p;  
  
    pool->free.prev = 0;  
    pool->free.next = (ngx_slab_page_t *) p;  
  
    pool->pages->slab = pages;  
    pool->pages->next = &pool->free;  
    pool->pages->prev = (uintptr_t) &pool->free;  
  
    // 计算出对齐后的返回内存的地址  
    pool->start = (u_char *)  
                  ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t),  
                                 ngx_pagesize);  
  
    // 用于判断我们对齐后的空间,是否需要进行调整  
    m = pages - (pool->end - pool->start) / ngx_pagesize;  
    // 说明之前是没有对齐过的,由于对齐之后,最后那一页,有可能不够一页,所以要去掉那一块  
    if (m > 0) {  
        pages -= m;  
        pool->pages->slab = pages;  
    }  
  
    pool->log_ctx = &pool->zero;  
    pool->zero = '\0';
}  
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;  
  
    // 如果超出slab最大可分配大小,即大于2048,则我们需要计算出需要的page数,  
    // 然后从空闲页中分配出连续的几个可用页  
    if (size >= ngx_slab_max_size) {  
  
        // 计算需要的页数,然后分配指针页数  
        page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1)  
                                          >> ngx_pagesize_shift);  
        if (page) {  
            // 由返回page在页数组中的偏移量,计算出实际数组地址的偏移量  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            // 计算出实际的数据地址  
            p += (uintptr_t) pool->start;  
  
        } else {  
            p = 0;  
        }  
  
        goto done;  
    }  
  
    // 如果小于2048,则启用slab分配算法进行分配  
  
    // 计算出此size的移位数以及此size对应的slot以及移位数  
    if (size > pool->min_size) {  
        shift = 1;  
        // 计算移位数  
        for (s = size - 1; s >>= 1; shift++) { /* void */ }  
        // 由移位数得到slot  
        slot = shift - pool->min_shift;  
  
    } else {  
        // 小于最小可分配大小的都放到一个slot里面  
        size = pool->min_size;  
        shift = pool->min_shift;  
        // 因为小于最小分配的,所以就放在第一个slot里面  
        slot = 0;  
    }  
  
    slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t));  
    // 得到当前slot所占用的页  
    page = slots[slot].next;  
  
    // 找到一个可用空间  
    if (page->next != page) {  
  
        // 分配大小小于128字节时的算法,看不懂的童鞋可以先看等于128字节的情况  
        // 当分配空间小于128字节时,我们不可能用一个int来表示这些块的占用情况  
        // 此时,我们就需要几个int了,即一个bitmap数组  
        // 我们此时没有使用page->slab,而是使用页数据空间的开始几个int空间来表示了  
        // 看代码  
  
        if (shift < ngx_slab_exact_shift) {  
  
            do {  
                // 得到页数据部分  
                p = (page - pool->pages) << ngx_pagesize_shift;  
                // 页的开始几个int大小的空间来存放位图数据  
                bitmap = (uintptr_t *) (pool->start + p);  
  
                // 当前页,在当前size下可分成map*32个块  
                // 我们需要map个int来表示这些块空间  
                map = (1 << (ngx_pagesize_shift - shift))  
                          / (sizeof(uintptr_t) * 8);  
  
                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 * sizeof(uintptr_t) * 8 ) << shift)  
                                + (i << shift);  
  
                            // 如果当前bitmap所表示的空间已都被占用,就查找下一个bitmap  
                            if (bitmap[n] == NGX_SLAB_BUSY) {  
                                for (n = n + 1; n < map; n++) {  
                                    // 找到下一个还剩下空间的bitmap  
                                     if (bitmap[n] != NGX_SLAB_BUSY) {  
                                         p = (uintptr_t) bitmap + i;  
  
                                         goto done;  
                                     }  
                                }  
  
                                // 剩下所有的bitmap都被占用了,表明当前的页已完全被使用了,把当前页从链表中删除  
                                prev = (ngx_slab_page_t *)  
                                            (page->prev & ~NGX_SLAB_PAGE_MASK);  
                                prev->next = page->next;  
                                page->next->prev = page->prev;  
  
                                page->next = NULL;  
                                // 小内存分配  
                                page->prev = NGX_SLAB_SMALL;  
                            }  
  
                            p = (uintptr_t) bitmap + i;  
  
                            goto done;  
                        }  
                    }  
                }  
  
                page = page->next;  
  
            } while (page);  
  
        } else if (shift == ngx_slab_exact_shift) {  
            // 如果分配大小正好是128字节,则一页可以分成32个块,我们可以用一个int来表示这些个块的使用情况  
            // 这里我们使用page->slab来表示这些块的使用情况,当所有块被占用后,该值就变成了0xffffffff,即NGX_SLAB_BUSY  
            // 表示该块都被占用了  
  
            do {  
                // 当前页可用  
                if (page->slab != NGX_SLAB_BUSY) {  
  
                    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_t *)  
                                            (page->prev & ~NGX_SLAB_PAGE_MASK);  
                            prev->next = page->next;  
                            page->next->prev = page->prev;  
  
                            page->next = NULL;  
                            // 标识使用类型,精确  
                            page->prev = NGX_SLAB_EXACT;  
                        }  
  
                        p = (page - pool->pages) << ngx_pagesize_shift;  
                        p += i << shift;  
                        p += (uintptr_t) pool->start;  
  
                        goto done;  
                    }  
                }  
  
                // 查找下一页  
                page = page->next;  
  
            } while (page);  
  
        } else { /* shift > ngx_slab_exact_shift */  
            // 当需要分配的空间大于128时,我们可以用一个int的位来表示这些空间  
            //所以我们依然采用跟等于128时类似的情况,用page->slab来表示  
            // 但由于 大于128的情况比较多,移位数分别为8、9、10、11这些情况  
            // 对于一个页,我们如何来知道这个页的分配大小呢?  
            // 而我们知道,最小我们只需要使用16位即可表示这些空间了,即分配大小为256~512时  
            // 那么我们采用高16位来表示这些空间的占用情况  
            // 而最低位,我们也利用起来,表示此页的分配大小,即保存移位数  
            // 比如我们分配256,当分配第一个空间时,此时的page->slab位图情况是:0x0001008  
            // 那分配下一空间就是0x0003008了,当为0xffff008时,就分配完了  
            // 看代码  
  
            // page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其实就是当前页的分配大小的移位数  
            // ngx_pagesize_shift减掉后,就是在一页中标记这些块所需要的移位数,也就是块数对应的移位数  
            n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK);  
            // 得到一个页面所能放下的块数  
            n = 1 << n;  
            // 得到表示这些块数都用完的bitmap,用现在是低16位的  
            n = ((uintptr_t) 1 << n) - 1;  
            // 将低16位转换成高16位,因为我们是用高16位来表示空间地址的占用情况的  
            mask = n << NGX_SLAB_MAP_SHIFT;  
  
            do {  
                // 判断高16位是否全被占用了  
                if ((page->slab & NGX_SLAB_MAP_MASK) != mask) {  
  
                    // NGX_SLAB_MAP_SHIFT 为移位偏移, 得到0x10000  
                    for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;  
                         m & mask;  
                         m <<= 1, i++)  
                    {  
                        // 当前块是否被占用  
                        if ((page->slab & m)) {  
                            continue;  
                        }  
  
                        // 将当前位设置成1  
                        page->slab |= m;  
  
                        // 当前页是否完全被占用完  
                        if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {  
                            prev = (ngx_slab_page_t *)  
                                            (page->prev & ~NGX_SLAB_PAGE_MASK);  
                            prev->next = page->next;  
                            page->next->prev = page->prev;  
  
                            page->next = NULL;  
                            page->prev = NGX_SLAB_BIG;  
                        }  
  
                        p = (page - pool->pages) << ngx_pagesize_shift;  
                        p += i << shift;  
                        p += (uintptr_t) pool->start;  
  
                        goto done;  
                    }  
                }  
  
                page = page->next;  
  
            } while (page);  
        }  
    }  
  
    // 如果当前slab对应的page中没有空间可分配了,则重新从空闲page中分配一个页  
    page = ngx_slab_alloc_pages(pool, 1);  
  
    if (page) {  
        if (shift < ngx_slab_exact_shift) {  
            // 小于128时  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            bitmap = (uintptr_t *) (pool->start + p);  
  
            // 需要的空间大小  
            s = 1 << shift;  
            n = (1 << (ngx_pagesize_shift - shift)) / 8 / s;  
  
            if (n == 0) {  
                n = 1;  
            }  
  
            bitmap[0] = (2 << n) - 1;  
  
            // 需要使用的  
            map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8);  
  
            for (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;  
  
            p = ((page - pool->pages) << ngx_pagesize_shift) + s * n;  
            p += (uintptr_t) pool->start;  
  
            goto done;  
  
        } else if (shift == ngx_slab_exact_shift) {  
  
            // 第一块空间被占用  
            page->slab = 1;  
            page->next = &slots[slot];  
            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;  
  
            slots[slot].next = page;  
  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            p += (uintptr_t) pool->start;  
  
            goto done;  
  
        } else { /* shift > ngx_slab_exact_shift */  
  
            // 低位表示存放数据的大小  
            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;  
  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            p += (uintptr_t) pool->start;  
  
            goto done;  
        }  
    }  
  
    p = 0;  
  
done:  
  
    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;  
  
    for (page = pool->free.next; page != &pool->free; page = page->next) {  
  
        if (page->slab >= pages) {  
  
            // 当前可以分配  
            if (page->slab > pages) {  
                // 空闲的pages大于需要分配的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 {  
                // slab == pages  
                // 正好分配完  
  
                // 设置free链表  
                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;  
        }  
    }  
  
    return NULL;  
}  

 

 

posted @ 2012-11-26 20:26  风去无痕  阅读(4115)  评论(0编辑  收藏  举报