swoole内存管理分析

共享内存

swoole由于采用多进程模型,可以避免多线程锁开销。不过,多进程需要进程间通信,swoole采用了共享内存,共享内存的结构体如下:

//共享内存
typedef struct _swShareMemory_mmap
{
    int size;  //共享内存的大小
    char mapfile[SW_SHM_MMAP_FILE_LEN];   //用来存储共享内存使用的内存映射文件的文件名
    int tmpfd;  //内存映射文件的描述符
    int key;    //shm系列函数创建的共享内存的key值
    int shmid;   //shm系列函数创建的共享内存的id
    void *mem;   //用来存储所用内存
} swShareMemory;

swoole天生支持mmap和sysv两种方式共享内存。

创建共享内存:

void *swShareMemory_mmap_create(swShareMemory *object, int size, char *mapfile)
{
    void *mem;
    int tmpfd = -1;
    int flag = MAP_SHARED;
    bzero(object, sizeof(swShareMemory));

#ifdef MAP_ANONYMOUS
    flag |= MAP_ANONYMOUS;   //使用匿名映射
#else
    if (mapfile == NULL)
    {
        mapfile = "/dev/zero";  //呵呵,又是你!
    }
    if ((tmpfd = open(mapfile, O_RDWR)) < 0)
    {
        return NULL;
    }
    strncpy(object->mapfile, mapfile, SW_SHM_MMAP_FILE_LEN);
    object->tmpfd = tmpfd;
#endif

#if defined(SW_USE_HUGEPAGE) && defined(MAP_HUGETLB)
    if (size > 2 * 1024 * 1024)
    {
        flag |= MAP_HUGETLB;   //hugepage
    }
#endif

    mem = mmap(NULL, size, PROT_READ | PROT_WRITE, flag, tmpfd, 0);
#ifdef MAP_FAILED
    if (mem == MAP_FAILED)
#else
    if (!mem)
#endif
    {
        swWarn("mmap() failed. Error: %s[%d]", strerror(errno), errno);
        return NULL;
    }
    else
    {
        object->size = size;
        object->mem = mem;
        return mem;
    }
}

内存池

下面是内存池的结构:

//-------------------memory manager-------------------------
typedef struct _swMemoryPool
{
    void *object;
    void* (*alloc)(struct _swMemoryPool *pool, uint32_t size);
    void (*free)(struct _swMemoryPool *pool, void *ptr);
    void (*destroy)(struct _swMemoryPool *pool);
} swMemoryPool;

上面可以说是一个基类,这类似于libevent,利用函数指针来实现多态。

FixedPool

swoole.h中定义了FixedPool的结构体,首先来看swFixedPool_slice,这是FixedPool内存池小块的结构声明:

typedef struct _swFixedPool_slice
{
    uint8_t lock;   //标记是否被占用,0代表空闲,1代表占用
    struct _swFixedPool_slice *next;  //后继指针
    struct _swFixedPool_slice *pre;   //前驱指针
    char data[0];   //内存空间指针,柔性数组
} swFixedPool_slice;

swFixedPool是真正的内存池结构体:

typedef struct _swFixedPool
{
    void *memory;   //内存指针,指向一片内存空间
    size_t size;    //内存空间大小

    swFixedPool_slice *head;    //链表头部节点
    swFixedPool_slice *tail;     //链表尾部节点,两个指针用于快速访问和移动节点

    /**
     * total memory size
     */
    uint32_t slice_num;   //节点数目

    /**
     * memory usage
     */
    uint32_t slice_use;   //已经使用的内存节点

    /**
     * Fixed slice size, not include the memory used by swFixedPool_slice
     */
    uint32_t slice_size;   //内存节点大小

    /**
     * use shared memory
     */
    uint8_t shared;   //是否共享内存

} swFixedPool;

剖析完了结构体,那就来从内存分配的new函数来分析:

/**
 * create new FixedPool, random alloc/free fixed size memory
 */
swMemoryPool* swFixedPool_new(uint32_t slice_num, uint32_t slice_size, uint8_t shared)
{
    //计算内存池的大小:slice大小*slice数量+MemoryPool头部大小+FixedPool头部大小
    size_t size = slice_size * slice_num + slice_num * sizeof(swFixedPool_slice);
    size_t alloc_size = size + sizeof(swFixedPool) + sizeof(swMemoryPool);
    //如果使用共享内存,则使用sw_shm_malloc(alloc_size)使用mmap分配共享内存
    void *memory = (shared == 1) ? sw_shm_malloc(alloc_size) : sw_malloc(alloc_size);

    swFixedPool *object = memory;
    memory += sizeof(swFixedPool);  //实际内存空间起始于swFixedPool的memory中
    bzero(object, sizeof(swFixedPool));

    object->shared = shared;
    object->slice_num = slice_num;
    object->slice_size = slice_size;
    object->size = size;

    swMemoryPool *pool = memory;
    memory += sizeof(swMemoryPool);
    //填充几个回调函数
    pool->object = object;
    pool->alloc = swFixedPool_alloc; //这样以后分配内存就是用swFixedPool内存池分配内存了
    pool->free = swFixedPool_free;
    pool->destroy = swFixedPool_destroy;

    object->memory = memory;

    /**
     * init linked list
     */
    swFixedPool_init(object); 

    return pool;
}

我们申请内存可以就可以用下面的方式来做:

SwooleGS = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(swServerGS));

FixedPool有四个操作函数:

static void swFixedPool_init(swFixedPool *object);  
static void* swFixedPool_alloc(swMemoryPool *pool,uint32_t size);  
static void swFixedPool_free(swMemoryPool *pool, void*ptr);  
static void swFixedPool_destroy(swMemoryPool *pool);  

swFixedPool_init函数如下,是用来初始化一个内存池结构体:

static void swFixedPool_init(swFixedPool *object)
{
    swFixedPool_slice *slice;
    void *cur = object->memory;
    void *max = object->memory + object->size;
    do
    {
        slice = (swFixedPool_slice *) cur;
        bzero(slice, sizeof(swFixedPool_slice));

        if (object->head != NULL)
        {
            object->head->pre = slice;
            slice->next = object->head;
        }
        else
        {
            object->tail = slice;
        }

        object->head = slice;
        cur += (sizeof(swFixedPool_slice) + object->slice_size);

        if (cur < max)
        {
            slice->pre = (swFixedPool_slice *) cur;
        }
        else
        {
            slice->pre = NULL;
            break;
        }

    } while (1);
}

下面是swFixedPool_alloc函数。swFixwd内存块的大小是固定的,所以第二个参数只是一个占位符:

static void* swFixedPool_alloc(swMemoryPool *pool, uint32_t size)
{
    swFixedPool *object = pool->object;
    swFixedPool_slice *slice;

    slice = object->head;

    if (slice->lock == 0)  //判断该结点是否被占用,如果被占用,说明内存池已满,返回NULL(因为所有被占用的节点都都会被放到尾部)
    {
        //未被占用,则将该结点的下一个节点移到首部,并将该结点移动到尾部,标记该结点为占用状态,返回该结点数据域。
        slice->lock = 1;
        object->slice_use ++;
        /**
         * move next slice to head (idle list)
         */
        object->head = slice->next;
        slice->next->pre = NULL;

        /*
         * move this slice to tail (busy list)
         */
        object->tail->next = slice;
        slice->next = NULL;
        slice->pre = object->tail;
        object->tail = slice;

        return slice->data;
    }
    else   //已被占用,内存池已满
    {
        return NULL;
    }
}

下面是示范内存函数,第二个参数是需要释放的数据域:

static void swFixedPool_free(swMemoryPool *pool, void *ptr)
{
    swFixedPool *object = pool->object;
    swFixedPool_slice *slice;

    assert(ptr > object->memory && ptr < object->memory + object->size);

    slice = ptr - sizeof(swFixedPool_slice);

    if (slice->lock)
    {
        object->slice_use--;  //减少数目
    }

    slice->lock = 0;  //标记未占用

    //list head, AB
    if (slice->pre == NULL)
    {
        return;
    }
    //list tail, DE
    if (slice->next == NULL)
    {
        slice->pre->next = NULL;
        object->tail = slice->pre;
    }
    //middle BCD
    else
    {
        slice->pre->next = slice->next;
        slice->next->pre = slice->pre;
    }

    slice->pre = NULL;
    slice->next = object->head;
    object->head->pre = slice;
    object->head = slice;
}

释放整个内存池:

static void swFixedPool_destroy(swMemoryPool *pool)
{
    swFixedPool *object = pool->object;
    if (object->shared)
    {
        sw_shm_free(object);
    }
    else
    {
        sw_free(object);
    }
}

RingBuffer

FixedPool是采用链表作为内存池的管理结构,而RingBuffer则是采用循环数组来管理内存,并且RingBuffer的每块内存可以是不等长的。虾米那是RingBuffer的结构体,在RingBuffer.c中:

typedef struct
{
    uint8_t shared;   //可共享
    uint8_t status;    
    uint32_t size;      //内存池大小
    uint32_t alloc_offset;    //可分配内存的起始长度,队头
    uint32_t collect_offset;   //可用内存的终止长度,队尾,从队尾开始回收内存
    uint32_t alloc_count;    
    sw_atomic_t free_count;   //有多少内存待回收,由于RingBuffer采用连续分配,可能存在一些已经被free的内存块夹在两个没有free的内存块中间
                              //没有立即回收,需要一个变量通知内存池回收这些内存
    void *memory;  //内存池的起始地址
} swRingBuffer;

其中RingBuffer的memory每次分配、收集和释放都是按照swRingBuffer_item为基本单位来进行的。

typedef struct
{
    uint16_t lock;
    uint16_t index;
    uint32_t length;   //每个内存块记录长度的变量
    char data[0];
} swRingBuffer_item;

RingBuffer同样定义了四个函数,不过着重关注swRingBuffer_collect()和swRingBuffer_alloc()函数即可。
下面是swRingBuffer_alloc()函数剖析:

static void* swRingBuffer_alloc(swMemoryPool *pool, uint32_t size)
{
    assert(size > 0);  //噗,很少见到源码用断言,不过我喜欢

    swRingBuffer *object = pool->object;
    swRingBuffer_item *item;
    uint32_t capacity;

    uint32_t alloc_size = size + sizeof(swRingBuffer_item);  //没错,分配内存就是一个item加真实内存的组合

    if (object->free_count > 0)   //如果有空闲内存,走到这里的时候顺便回收空闲内存
        swRingBuffer_collect(object);
    }

    if (object->status == 0)
    {
        //特殊情况,内存在末尾,有内存但不够用
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        if (object->alloc_offset + alloc_size >= (object->size - sizeof(swRingBuffer_item))) //如果最末尾空闲内存不足alloc_size
        {
            uint32_t skip_n = object->size - object->alloc_offset;  //末尾可分配内存
            if (skip_n >= sizeof(swRingBuffer_item))   //大于item所用的内存,我们先单独分配一块item
            {
                item = object->memory + object->alloc_offset;  //内存池首地址+偏移量得到可分配内存首地址
                item->lock = 0;  
                item->length = skip_n - sizeof(swRingBuffer_item);  //剩下需要分配的内存
                sw_atomic_t *free_count = &object->free_count; 
                sw_atomic_fetch_add(free_count, 1);  //已分配块数+1
            }
            object->alloc_offset = 0;  //调整偏移量从头重新开始
            object->status = 1;   //
            capacity = object->collect_offset - object->alloc_offset;   //由于从头开始了,容量就等于collect_offset-alloc_offset
        }
        else
        {
            capacity = object->size - object->alloc_offset;
        }
    }
    else
    {
        capacity = object->collect_offset - object->alloc_offset;
    }

    if (capacity < alloc_size)
    {
        return NULL;
    }

    item = object->memory + object->alloc_offset;
    item->lock = 1;
    item->length = size;   
    item->index = object->alloc_count;   

    object->alloc_offset += alloc_size;   //更新alloc_offset,表示分配出去一块内存
    object->alloc_count ++;

    swDebug("alloc: ptr=%d", (void *)item->data - object->memory);

    return item->data;  
}

接下来是swRingBuffer_collect()函数:

static void swRingBuffer_collect(swRingBuffer *object)
{
    swRingBuffer_item *item;
    sw_atomic_t *free_count = &object->free_count;   //获取带货收内存数目

    int count = object->free_count;
    int i;
    uint32_t n_size;

    for (i = 0; i < count; i++)
    {
        item = object->memory + object->collect_offset;   //从collect_offset开始回收
        if (item->lock == 0)
        {
            n_size = item->length + sizeof(swRingBuffer_item);  //每一块内存由item结构体加真实内存组成,在这里获取总长度

            object->collect_offset += n_size;

            if (object->collect_offset + sizeof(swRingBuffer_item) >object->size || object->collect_offset >= object->size)
            {
                object->collect_offset = 0;
                object->status = 0;
            }
            sw_atomic_fetch_sub(free_count, 1);   //原子操作,free_count-1
        }
        else
        {
            break;
        }
    }
}

还有一个内存池是MemoryGlobal,暂时没有剖析,下次再说。

参考: Swoole源码学习记录(三)——三种MemoryPool

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值