Swoole 原始碼分析——記憶體模組之記憶體池

leoyang發表於2018-08-03

前言

Swoole 中為了更好的進行記憶體管理,減少頻繁分配釋放記憶體空間造成的損耗和記憶體碎片,程式設計並實現了三種不同功能的記憶體池:FixedPoolRingBufferMemoryGlobal

其中 MemoryGlobal 用於全域性變數 SwooleG.memory_poolRingBuffer 用於 reactor 執行緒的緩衝區,FixedPool 用於 swoole_table 共享記憶體表。

swMemoryPool 記憶體池資料結構

無論是哪種記憶體池,它的基礎資料結構都是 swMemoryPool:

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;

可以看出來, swMemoryPool 更加類似於介面,規定了記憶體池需要定義的函式。

MemoryGlobal 記憶體池實現

MemoryGlobal 資料結構

首先看一下 MemoryGlobal 的資料結構:

typedef struct _swMemoryGlobal_page
{
    struct _swMemoryGlobal_page *next;
    char memory[0];
} swMemoryGlobal_page;

typedef struct _swMemoryGlobal
{
    uint8_t shared;
    uint32_t pagesize;
    swLock lock;
    swMemoryGlobal_page *root_page;
    swMemoryGlobal_page *current_page;
    uint32_t current_offset;
} swMemoryGlobal;

可以很明顯的看出,MemoryGlobal 實際上就是一個單連結串列,root_page 是連結串列的頭,current_page 就是連結串列的尾,current_offset 指的是最後一個連結串列元素的偏移量。

比較特殊的是 MemoryGlobal 單連結串列記憶體池的記憶體只能增加不會減少。

MemoryGlobal 的建立

#define SW_MIN_PAGE_SIZE  4096

swMemoryPool* swMemoryGlobal_new(uint32_t pagesize, uint8_t shared)
{
    swMemoryGlobal gm, *gm_ptr;
    assert(pagesize >= SW_MIN_PAGE_SIZE);
    bzero(&gm, sizeof(swMemoryGlobal));

    gm.shared = shared;
    gm.pagesize = pagesize;

    swMemoryGlobal_page *page = swMemoryGlobal_new_page(&gm);
    if (page == NULL)
    {
        return NULL;
    }
    if (swMutex_create(&gm.lock, shared) < 0)
    {
        return NULL;
    }

    gm.root_page = page;

    gm_ptr = (swMemoryGlobal *) page->memory;
    gm.current_offset += sizeof(swMemoryGlobal);

    swMemoryPool *allocator = (swMemoryPool *) (page->memory + gm.current_offset);
    gm.current_offset += sizeof(swMemoryPool);

    allocator->object = gm_ptr;
    allocator->alloc = swMemoryGlobal_alloc;
    allocator->destroy = swMemoryGlobal_destroy;
    allocator->free = swMemoryGlobal_free;

    memcpy(gm_ptr, &gm, sizeof(gm));
    return allocator;
}
  • 可以看到,每次申請建立 MemoryGlobal 記憶體不得小於 2k
  • 建立的 MemoryGlobalcurrent_offset 被初始化為 swMemoryGlobalswMemoryPool 的大小之和
  • 返回的 allocator 型別是 swMemoryPool,其記憶體結構為:

static swMemoryGlobal_page* swMemoryGlobal_new_page(swMemoryGlobal *gm)
{
    swMemoryGlobal_page *page = (gm->shared == 1) ? sw_shm_malloc(gm->pagesize) : sw_malloc(gm->pagesize);
    if (page == NULL)
    {
        return NULL;
    }
    bzero(page, gm->pagesize);
    page->next = NULL;

    if (gm->current_page != NULL)
    {
        gm->current_page->next = page;
    }

    gm->current_page = page;
    gm->current_offset = 0;

    return page;
}

連結串列元素的建立比較簡單,就是申請記憶體,初始化單連結串列的各個變數。

MemoryGlobal 記憶體的申請

static void *swMemoryGlobal_alloc(swMemoryPool *pool, uint32_t size)
{
    swMemoryGlobal *gm = pool->object;
    gm->lock.lock(&gm->lock);
    if (size > gm->pagesize - sizeof(swMemoryGlobal_page))
    {
        swWarn("failed to alloc %d bytes, exceed the maximum size[%d].", size, gm->pagesize - (int) sizeof(swMemoryGlobal_page));
        gm->lock.unlock(&gm->lock);
        return NULL;
    }
    if (gm->current_offset + size > gm->pagesize - sizeof(swMemoryGlobal_page))
    {
        swMemoryGlobal_page *page = swMemoryGlobal_new_page(gm);
        if (page == NULL)
        {
            swWarn("swMemoryGlobal_alloc alloc memory error.");
            gm->lock.unlock(&gm->lock);
            return NULL;
        }
        gm->current_page = page;
    }
    void *mem = gm->current_page->memory + gm->current_offset;
    gm->current_offset += size;
    gm->lock.unlock(&gm->lock);
    return mem;
}
  • 申請記憶體之前需要先將互斥鎖加鎖以防多個執行緒或多個程式同時申請記憶體,導致資料混亂。
  • 如果申請的記憶體大於單個連結串列元素的 pagesize,直接返回錯誤。
  • 如果當前連結串列元素剩餘的記憶體不足,那麼就會重新申請一個新的連結串列元素
  • 設定 current_offset,解鎖互斥鎖,返回記憶體地址。

MemoryGlobal 記憶體的釋放與銷燬

static void swMemoryGlobal_free(swMemoryPool *pool, void *ptr)
{
    swWarn("swMemoryGlobal Allocator don't need to release.");
}

static void swMemoryGlobal_destroy(swMemoryPool *poll)
{
    swMemoryGlobal *gm = poll->object;
    swMemoryGlobal_page *page = gm->root_page;
    swMemoryGlobal_page *next;

    do
    {
        next = page->next;
        sw_shm_free(page);
        page = next;
    } while (page);
}
  • MemoryGlobal 不需要進行記憶體的釋放
  • MemoryGlobal 的銷燬就是迴圈單連結串列,然後釋放記憶體

RingBuffer 記憶體池實現

RingBuffer 的資料結構

RingBuffer 類似於一個迴圈陣列,每一次申請的一塊記憶體在該陣列中佔據一個位置,這些記憶體塊是可以不等長的,因此每個記憶體塊需要有一個記錄其長度的變數。

typedef struct
{
    uint16_t lock;
    uint16_t index;
    uint32_t length;
    char data[0];
} swRingBuffer_item;

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;
    void *memory;
} swRingBuffer;
  • swRingBuffer 中非常重要的成員變數是 alloc_offsetcollect_offsetalloc_offset 是當前迴圈陣列中的起始地址,collect_offset 代表當前迴圈陣列中可以被回收的記憶體地址。
  • free_count 是當前迴圈陣列中可以被回收的個數。
  • status 為 0 代表迴圈陣列當前佔用的記憶體空間並沒有越過陣列的結尾,也就是其地址是連續的,為 1 代表迴圈陣列當前佔用的記憶體空間一部分在迴圈陣列的尾部,一部分在陣列的頭部。

RingBuffer 的建立

RingBuffer 的建立類似於 MemoryGlobal

swMemoryPool *swRingBuffer_new(uint32_t size, uint8_t shared)
{
    void *mem = (shared == 1) ? sw_shm_malloc(size) : sw_malloc(size);
    if (mem == NULL)
    {
        swWarn("malloc(%d) failed.", size);
        return NULL;
    }

    swRingBuffer *object = mem;
    mem += sizeof(swRingBuffer);
    bzero(object, sizeof(swRingBuffer));

    object->size = (size - sizeof(swRingBuffer) - sizeof(swMemoryPool));
    object->shared = shared;

    swMemoryPool *pool = mem;
    mem += sizeof(swMemoryPool);

    pool->object = object;
    pool->destroy = swRingBuffer_destory;
    pool->free = swRingBuffer_free;
    pool->alloc = swRingBuffer_alloc;

    object->memory = mem;

    swDebug("memory: ptr=%p", mem);

    return pool;
}

RingBuffer 記憶體的申請

  • free_count 大於 0,說明此時陣列中有待回收的記憶體,需要進行記憶體回收
  • 若當前佔用的記憶體不是連續的,那麼當前記憶體池剩餘的容量就是 collect_offset - alloc_offset
  • 若當前佔用的記憶體是連續的,
    • 而且陣列當前 collect_offset 距離尾部的記憶體大於申請的記憶體數,那麼剩餘的容量就是 size - alloc_offset
    • 陣列當前記憶體位置距離尾部容量不足,那麼就將當前記憶體到陣列尾部打包成為一個 swRingBuffer_item 陣列元素,並標誌為待回收元素,設定 status 為 1,設定 alloc_offset 為陣列首地址,此時剩餘的容量就是 collect_offset 的地址
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);

    if (object->free_count > 0)
    {
        swRingBuffer_collect(object);
    }

    if (object->status == 0)
    {
        if (object->alloc_offset + alloc_size >= (object->size - sizeof(swRingBuffer_item)))
        {
            uint32_t skip_n = object->size - object->alloc_offset;
            if (skip_n >= sizeof(swRingBuffer_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);
            }
            object->alloc_offset = 0;
            object->status = 1;
            capacity = object->collect_offset - object->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;
    object->alloc_count ++;

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

    return item->data;
}

RingBuffer 記憶體的回收

  • RingBufferfree_count 大於 0 的時候,就說明當前記憶體池存在需要回收的元素,每次在申請新的記憶體時,都會呼叫這個函式來回收記憶體。
  • 回收記憶體時,本函式只會回收連續的多個空餘的記憶體元素,若多個待回收的記憶體元素之間相互隔離,那麼這些記憶體元素不會被回收。
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;
        if (item->lock == 0)
        {
            n_size = item->length + sizeof(swRingBuffer_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);
        }
        else
        {
            break;
        }
    }
}

RingBuffer 記憶體的釋放

記憶體的釋放很簡單,只需要設定 lock 為 0,並且增加 free_count 的數量即可:

static void swRingBuffer_free(swMemoryPool *pool, void *ptr)
{
    swRingBuffer *object = pool->object;
    swRingBuffer_item *item = ptr - sizeof(swRingBuffer_item);

    assert(ptr >= object->memory);
    assert(ptr <= object->memory + object->size);
    assert(item->lock == 1);

    if (item->lock != 1)
    {
        swDebug("invalid free: index=%d, ptr=%p", item->index,  (void * )((void * )item->data - object->memory));
    }
    else
    {
        item->lock = 0;
    }

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

    sw_atomic_t *free_count = &object->free_count;
    sw_atomic_fetch_add(free_count, 1);
}

RingBuffer 記憶體的銷燬

static void swRingBuffer_destory(swMemoryPool *pool)
{
    swRingBuffer *object = pool->object;
    if (object->shared)
    {
        sw_shm_free(object);
    }
    else
    {
        sw_free(object);
    }
}
  • 值得注意的是,RingBuffer 除了原子鎖之外就沒有任何鎖了,在申請與釋放過程的程式碼中也沒有看出來是執行緒安全的無鎖資料結構,個人認為 RingBuffer 並非是執行緒安全/程式安全的資料結構,因此利用這個記憶體池申請共享記憶體時,需要自己進行加鎖。

FixedPool 記憶體池實現

FixedPool 資料結構

FixedPool 是隨機分配記憶體池,將一整塊記憶體空間切分成等大小的一個個小塊,每次分配其中的一個小塊作為要使用的記憶體,這些小塊以雙向連結串列的形式儲存。

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

} swFixedPool_slice;

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;

FixedPool 記憶體池的建立

FixedPool 記憶體池的建立有兩個函式 swFixedPool_newswFixedPool_new2,其中 swFixedPool_new2 是利用已有的記憶體基礎上來構建記憶體池,這個也是 table 共享記憶體表建立的方法。

swMemoryPool* swFixedPool_new2(uint32_t slice_size, void *memory, size_t size)
{
    swFixedPool *object = memory;
    memory += sizeof(swFixedPool);
    bzero(object, sizeof(swFixedPool));

    object->slice_size = slice_size;
    object->size = size - sizeof(swMemoryPool) - sizeof(swFixedPool);
    object->slice_num = object->size / (slice_size + sizeof(swFixedPool_slice));

    swMemoryPool *pool = memory;
    memory += sizeof(swMemoryPool);
    bzero(pool, sizeof(swMemoryPool));

    pool->object = object;
    pool->alloc = swFixedPool_alloc;
    pool->free = swFixedPool_free;
    pool->destroy = swFixedPool_destroy;

    object->memory = memory;

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

    return 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);
}

可以看出來,程式從記憶體空間的首部開始,每次初始化一個 slice 大小的空間,並插入到連結串列的頭部,因此整個連結串列的記憶體地址和 memory 的地址是相反的。

FixedPool 記憶體池的申請

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

    slice = object->head;

    if (slice->lock == 0)
    {
        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;
    }
}
  • 首先獲取記憶體池連結串列首部的節點,並判斷該節點是否被佔用,如果被佔用,說明記憶體池已滿,返回null(因為所有被佔用的節點都會被放到尾部);如果未被佔用,則將該節點的下一個節點移到首部,並將該節點移動到尾部,標記該節點為佔用狀態,返回該節點的資料域。

FixedPool 記憶體池的釋放

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;
}
  • 首先通過移動 ptr 指標獲得 slice 物件,並將佔用標記 lock 置為 0。如果該節點為頭節點,則直接返回。如果不是頭節點,則將該節點移動到連結串列頭部。

FixedPool 記憶體池的銷燬

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

相關文章