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的分析。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章