swoole記憶體管理分析

FreeeLinux發表於2017-04-03

共享記憶體

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

相關文章