Swoole源碼學習記錄(三)——三種MemoryPool(下)

swoole版本:1.7.4-stable

Github地址:https://github.com/LinkedDestiny/swoole-src-analysis

接下來是RingBuffer。這相當於一個循環數組,每一次申請的一塊內存在該數組中佔據一個位置,這些內存塊是可以不等長的,因此每個內存塊需要有一個記錄其長度的變量。這裏貼出swRingBuffer_head結構體的代碼:

typedef struct _swRingBuffer_item
{
    volatileuint32_t lock;
    volatileuint32_t length;
} swRingBuffer_head;

每一個結構體代表一個RingBuffer中的內存塊,其中lock變量標記該內存塊是否被佔用,length變量標記該內存塊的長度。

接着是swRingBuffer結構體的聲明:

typedef struct _swRingBuffer
{
    uint8_t shared;                 //可共享
    size_t size;                    //內存池大小
    volatile off_talloc_offset;    // 分配內存的起始長度
    volatile off_tcollect_offset;  // 可用內存的終止長度
    volatileuint32_t free_n;       // 有多少個內存塊待回收
    void *memory;                   // 內存池的起始地址
} swRingBuffer;

每一個結構體代表一個RingBuffer內存池。這裏先要說明一下RingBuffer的三個變量:alloc_offset,collect_offset,free_n。

alloc_offset變量是分配內存的起始地址,代表的是RingBuffer現有的可用空間的起始地址;collect_offset變量是分配內存的終止地址,代表的是RingBuffer現有的可用空間的結束地址。爲了方便理解,大家可以想象一下循環隊列,alloc_offset和collect_offset就是標記隊頭和隊尾的標記,每一次分配內存就相當於入隊,每一次釋放內存就相當於出隊。

而free_n變量是用於標記當前還有多少個已釋放的內存塊待回收。這是因爲RingBuffer採用的是連續分配,可能會存在一些已經被free的內存塊夾在兩個沒有free的內存塊中間,沒有被立即回收,就需要一個變量去通知內存池回收這些內存。。

    RingBuffer的創建函數爲swRingBuffer_new,其聲明在swoole.h文件的514 – 517行。入下:

/**
 * RingBuffer, Inorder for malloc / free
 */
swMemoryPool *swRingBuffer_new(size_t size, uint8_tshared);

該函數的具體定義在RingBuffer.c中,創建過程與FixedPool基本類似,就不再額外分析,大家自行閱讀源碼即可。

    和FixedPool類似,RingBuffer也擁有4個函數用於操作內存池,其函數聲明如下:

static void swRingBuffer_destory(swMemoryPool *pool);
static sw_inline void swRingBuffer_collect(swRingBuffer*object);
static void* swRingBuffer_alloc(swMemoryPool *pool,uint32_t size);
static void swRingBuffer_free(swMemoryPool *pool, void*ptr);

其中alloc、destroy、free三個函數的功能很明確,swRingBuffer_collect函數用於回收已經不被佔用的內存。這裏着重分析alloc函數和collect函數。

首先是collect函數。在發現內存池剩餘不足或分配內存結束後,RingBuffer都會調用collect函數去回收已經沒有被佔用的內存。其核心代碼如下:

  

  for(i = 0;i<SW_RINGBUFFER_COLLECT_N; i++)
    {
        item =(swRingBuffer_head *) (object->memory + object->collect_offset);
 
        swTraceLog(SW_TRACE_MEMORY,"collect_offset=%d, item_length=%d, lock=%d",object->collect_offset, item->length, item->lock);
 
        //cancollect
        if(item->lock == 0)
        {
            object->collect_offset+= (sizeof(swRingBuffer_head) + item->length);
            if(object->free_n > 0)
            {
                object->free_n--;
            }
            if(object->collect_offset >= object->size)
            {
                object->collect_offset= 0;
            }
        }
        else
        {
            break;
        }
    }

源碼解釋:每一次循環,都會獲取當前collect_offset指向的地址代表的內存塊,並獲取其swRingBuffer_head結構,如果該內存塊已經被free,則將collect_offset標記後移該內存塊的長度,回收該內存。如果發現collect_offset超出了內存池大小,則將collect_offset移到內存池頭部。

alloc函數太長,在此不貼出源碼,只寫出僞代碼供分析:

start_alloc:
if( alloc_offset < collect_offset ) // 起始地址在終止地址左側
{
    head_alloc:
    計算剩餘內存大小
   
    if( 內存足夠 )
        gotodo_alloc
    else if( 內存不足且已經回收過內存)
        return NULL;
    else
    {
        try_collect = 1;
        調用collect
        goto start_alloc
    }
}
else // 起始地址在終止地址右側(終止地址被移動到了首部)
{
    計算從alloc_offset到內存池尾部的剩餘內存大小
    if( 內存足夠 )
        gotodo_alloc
    else
    {
    標記尾部剩餘內存爲可回收狀態
    將alloc_offset移動到首部
    gotohead_alloc
}
}
do_alloc:
實際分配內存塊並設置屬性,移動alloc_offset標記
如果free_n大於0,則回收內存。

最後是MemoryGlobal。MemoryGlobal是一個比較特殊的內存池。說實話我沒有看懂它的作用,所以我決定先暫時跳過MemoryGlobal,等了解其具體使用場景時再來分析這一塊。

發佈了31 篇原創文章 · 獲贊 27 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章