本篇主要针对nginx共享内存抽象逻辑进行描述,具体代码网络上较多,这里不再赘述。

nginx版本

1.22.0

nginx共享内存整体架构

首先,nginx共享内存整体架构分为两个模块,一个是共享内存模块,另外一个就是slab模块;前者就是直接基于系统创建共享内存块,后者是对创建出来的共享内存块内存空间的管理数据结构。

共享内存模块

共享内存模块是基于mmap机制进行实现的

注册与初始化

要创建一块共享内存,nginx需要使用ngx_shared_memory_add()接口,核心代码为:

shm_zone = ngx_list_push(&cf->cycle->shared_memory);

这个函数就是对ngx_cycle_t->shared_memory共享内存链表进行了一个注册操作,并不是直接申请共享内存,申请共享内存的操作nginx是在初始化的时候进行实现的,就是在ngx_init_cycle()函数中,核心代码为:

/* create shared memory */
part = &cycle->shared_memory.part;
shm_zone = part->elts;

for (i = 0; /* void */ ; i++) 
{
    if (i >= part->nelts) 
    {
        if (part->next == NULL) 
        {
            break;
        }
        part = part->next;
        shm_zone = part->elts;
        i = 0;
    }

    if (shm_zone[i].shm.size == 0) 
    {
        ngx_log_error(NGX_LOG_EMERG, log, 0,
                        "zero size shared memory zone \"%V\"",
                        &shm_zone[i].shm.name);
        goto failed;
    }

    shm_zone[i].shm.log = cycle->log;

    opart = &old_cycle->shared_memory.part;
    oshm_zone = opart->elts;
    for (n = 0; /* void */ ; n++) 
    {
        if (n >= opart->nelts) 
        {
            if (opart->next == NULL) 
            {
                break;
            }
            opart = opart->next;
            oshm_zone = opart->elts;
            n = 0;
        }

        if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) 
        {
            continue;
        }

        if (ngx_strncmp(shm_zone[i].shm.name.data,
                        oshm_zone[n].shm.name.data,
                        shm_zone[i].shm.name.len)
            != 0)
        {
            continue;
        }

        if (shm_zone[i].tag == oshm_zone[n].tag
            && shm_zone[i].shm.size == oshm_zone[n].shm.size
            && !shm_zone[i].noreuse)
        {
            shm_zone[i].shm.addr = oshm_zone[n].shm.addr;
        #if (NGX_WIN32)
            shm_zone[i].shm.handle = oshm_zone[n].shm.handle;
        #endif

            if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data) != NGX_OK)
            {
                goto failed;
            }

            goto shm_zone_found;
        }

        break;
    }

    if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) 
    {
        goto failed;
    }

    if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) 
    {
        goto failed;
    }

    if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) 
    {
        goto failed;
    }
    
    shm_zone_found:

    continue;
}

精简一下,对于其中的一个共享内存配置来说,其代码为:

ngx_shm_alloc()
ngx_init_zone_pool()
shm_zone[i].init(()

ngx_shm_alloc()中就是创建了共享内存,ngx_init_zone_pool()就是基于共享内存块创建了slab管理数据结构;

slab模型抽象理解

整个slab主要涉及到两个部分,和内存池差不多,一种是申请大内存的方式,一种是申请小内存的方式:
在了解这两种方式的之前,需要明白另一个点,就是nginx对内存的基础分配,nginx对内存的基础管理就是页,对于申请大内存,nginx直接向上取整然后返回页空间;对于小内存,则是先返回一个页,然后通过slot机制在这个页上再次进行空间管理分配。
然后对页的管理当然也有对应的数据结构,但是这个数据结构和页的空间放置需要提一下,就是两者是分离的,即所有的管理数据结构存放在共享内存的一块连续区域,而实际使用的共享内存页是存放在一块连续的内存区域中,两者的联系就是通过index下标完成关联。而这也提供了分配联系大内存的基础支持。

slab初始化完成的数据排布

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsTv7RnL-1657284936262)(./images/nginxSharedMemoryInit.png “Magic Gardens”)]

申请大内存的逻辑

申请大内存,也就是以页为单位进行空间申请,首先聊一下这些页在使用过程中的形式:
首先,这些页是连续排布的,他们的组织结构就是链表,使用过程中可能会出现数组拆解,在不连续的时候内存释放时他们就会以链表的形式组装在一起(初始情况下这个链表只有一个元素,就是整片连续的页空间数组)。
在链表组织结构的前提下,申请大内存时,遍历链表上的页数组,如果页数组可以提供对应的页大小的话,那么就进行页数组拆分,拆分后将未使用的页数组继续保存在空闲页数组链表中。

申请小内存

申请小内存的逻辑是建立在大内存的基础之上的,首先他需要得到一个页空间,然后基于这个页空间进行slot管理,slot本质上就是一个内存页拆分为 2^n 大小的内存块然后进行管理的机制,nginx是通过位图进行管理的,通过位图信息就可以知道内存页拆分的小的内存块使用状态并进行分配与释放。有个点就是:这些slot数据结构在ngx_slab_pool_t数据结构中并没有显式的描述出来,他放置在整个的共享内存块中ngx_slab_pool_t数据结构之后,使用过程中他是通过内存运算得到的。