Swoole源码学习记录(二)——三种MemoryPool(上)

swoole版本:1.7.4-stable

Swoole中为了更好的进行内存管理,减少频繁分配释放内存空间造成的损耗和内存碎片,Rango设计并实现了三种不同功能的MemoryPool:FixedPoolRingBufferMemoryGlobal

Rango声明了一个swMemoryPool结构体来表示一个内存池,该结构体在swoole.h头文件中501-507行声明,结构如下:

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;

(PS:虽然原来也知道结构体中可以通过存放函数指针模拟一个类,但是现在才学到可以通过传入一个指针参数来模拟this指针,使结构体更像一个类)

这里看到,首先是一个void*型指针,用于标记内存池的首地址。另外三个函数大家都不会陌生,分别用于从内存池中拿到一块内存、释放一块内存、销毁整个内存池。

    基类介绍完了,下面来看具体的子类实现。

首先是FixedPool。FixedPool是随机分配内存池(random alloc/free),将一整块内存空间切分成等大小的一个个小块,每次分配其中的一个小块作为要使用的内存,这些小块以链表的形式存储。

    FixedPool的全部定义声明均在src/memory/FixedPool.c文件内。Rango声明了两个结构体swFixedPool_slice和swFixedPool来实现管理内存池。

    swFixedPool_slice为内存池中每个小块的结构声明,其定义如下:

typedef struct _swFixedPool_slice
{
    uint8_t lock;
    struct_swFixedPool_slice *next;
    struct_swFixedPool_slice *pre;
    char data[0];
 
} swFixedPool_slice;

    可以看出,每个slice为一个链表节点(双向链表),unit8_t lock用于标记该节点是否被占用(0代表空闲,1代表已占用),next和pre为该节点的后继、前驱指针,char data[0]为内存空间指针(关于0长度数组的解释,请看http://blog.csdn.net/liuaigui/article/details/3680404

    swFixedPool为内存池实体,包括了内存池相关信息。定义如下:

typedef struct _swFixedPool
{
    void *memory;   // 内存指针,指向一片内存空间
    size_t size;    // 内存空间大小
 
    swFixedPool_slice*head;    // 链表头部节点
    swFixedPool_slice*tail;    // 链表尾部节点,两个指针用于快速访问和移动节点
 
    /**
     * total memory size,节点数目
     */
    uint32_tslice_num;
 
    /**
     * memory usage ,已经使用的节点
     */
    uint32_tslice_use;
 
    /**
     * Fixed slice size
     */
    uint32_tslice_size; 节点大小
 
    /**
     * use shared memory
     */
    uint8_t shared;是否共享内存
 
} swFixedPool;

(参数太多就不文字叙述,直接在变量名后以注释格式解释)

这里先看swFixedPool_new的具体定义。其声明如下:

/**
* FixedPool,random alloc/free fixed size memory
* Location:swoole.h 512L
*/
swMemoryPool * swFixedPool_new(uint32_t size, uint32_ttrunk_size, uint8_t shared);

第一个参数size为每一个小块的大小,第二个参数trunk_size为小块的总数,第三个参数shared代表该内存池是否为共享内存,返回的是被创建的内存池指针。下面贴出代码:

size_t size = slice_size * slice_num;
size_t alloc_size = size +sizeof(swFixedPool) + sizeof(swMemoryPool);
void *memory = (shared == 1) ?sw_shm_malloc(alloc_size) : sw_malloc(alloc_size);
 
swFixedPool *object = memory;
memory += sizeof(swFixedPool);
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;    // 指定三个操作函数
pool->free = swFixedPool_free;
pool->destroy = swFixedPool_destroy;
 
object->memory = memory;
 
swFixedPool_init(object);

前两行是计算出内存池实际大小:slice大小*slice数量+MemoryPool头部大小+FixedPool头部大小。接下来根据是否是共享内存来判定使用哪种分配内存的方法(其中sw_shm_malloc方法将在上一章补充)。接下来依次填充swFixedPool、swMemoryPool的相关属性,实际内存空间起始地址存放于swFixedPool的memory中。接下来调用swFixedPool_init方法初始化内存池。

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函数。该函数用于初始化一个内存池结构体,其核心代码如下:

    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);
        slice->pre= (swFixedPool_slice *) cur;
    } while (cur< max);


    源码解释:从内存空间的首部开始,每次初始化一个slice大小的空间,并插入到链表的头部。至于为什么使用链表这个结构会稍后说明。

    内存分配好了,如何使用呢?这里再看swFixedPool_alloc函数。这里要注意,因为FixedPool的内存块大小是固定的,所以函数中第二个参数size只是为了符合swMemoryPool中的声明,不具有实际作用,可以随便填一个数。其核心代码如下:

slice = object->head;
if (slice->lock == 0)
    {
        slice->lock= 1;
        /**
         * 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;
 
        returnslice->data;
    }
    else
    {
        returnNULL;
    }

    源码解释:首先获取内存池链表首部的节点,并判断该节点是否被占用,如果被占用,说明内存池已满,返回null(因为所有被占用的节点都会被放到尾部);如果未被占用,则将该节点的下一个节点移到首部,并将该节点移动到尾部,标记该节点为占用状态,返回该节点的数据域。

    当一个内存块用完,需要释放内存,则调用swFixedPool_free方法。该函数第二个参数为需要释放的数据域。其核心代码如下:

slice = ptr - sizeof(swFixedPool_slice);
    slice->lock= 0;
 
    //list head, AB
    if(slice->pre == NULL)
    {
        return;
    }
    //list tail, DE
    if(slice->next == NULL)
    {
        slice->pre->next= NULL;
    }
    //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;

    源码解释:首先通过移动ptr指针获得slice对象,并将占用标记lock置为0。如果该节点为头节点,则直接返回。如果不是头节点,则将该节点移动到链表头部。

    swFixedPool_destroy为直接释放整片内存池,在此不再详解。

(PS:目前先放出FixedPool的源码分析,今晚再放出RingBuffer和MmemoyGlobal的分析。)

展开阅读全文

没有更多推荐了,返回首页