Source code analysis of spool — memory pool of memory module

Time:2021-1-18

preface

SwooleIn order to better manage the memory and reduce the loss and memory fragmentation caused by frequent allocation and release of memory space, three memory pools with different functions are designed and implementedFixedPoolRingBufferandMemoryGlobal

amongMemoryGlobalFor global variablesSwooleG.memory_poolRingBufferbe used forreactorThe buffer of the thread,FixedPoolbe used forswoole_tableShared memory table.

swMemoryPoolMemory pool data structure

No matter what kind of memory pool, its basic data structure isswMemoryPool:

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;

You can see that,swMemoryPoolMore similar to the interface, it specifies the functions that the memory pool needs to define.

MemoryGlobalMemory pool implementation

MemoryGlobaldata structure

Let’s look at it firstMemoryGlobalData structure of the system:

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;

It’s obvious that,MemoryGlobalIt’s actually a single linked list,root_pageIt’s the head of the list,current_pageIt’s the end of the list,current_offsetRefers to the offset of the last linked list element.

What’s special is thatMemoryGlobalThe memory of single linked list memory pool can only be increased but not decreased.

MemoryGlobalCreation of

#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;
}
  • As you can see, each application is createdMemoryGlobalMemory must not be less than2k
  • CreatedMemoryGlobalOfcurrent_offsetIs initialized toswMemoryGlobalAndswMemoryPoolThe sum of the sizes of
  • ReturnedallocatorThe type isswMemoryPoolIts memory structure is as follows:

    swMemoryGlobal swMemoryPool memory
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;
}

The creation of linked list elements is relatively simple, which is to apply for memory and initialize the variables of single linked list.

MemoryGlobalMemory application

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;
}
  • Before applying for memory, mutex should be locked to prevent multiple threads or processes from applying for memory at the same time, resulting in data confusion.
  • If the requested memory is larger than the memory of a single linked list elementpagesize, return the error directly.
  • If the remaining memory of the current linked list element is insufficient, a new linked list element will be applied for
  • set upcurrent_offsetTo unlock the mutex and return the memory address.

MemoryGlobalRelease and destroy of memory

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);
}
  • MemoryGlobalThere is no need to release memory
  • MemoryGlobalThe destruction of single linked list is to cycle single linked list, and then release memory

RingBufferMemory pool implementation

RingBufferData structure of

RingBufferSimilar to a circular array, each memory block applied occupies a position in the array. These memory blocks can be unequal in length, so each memory block needs a variable to record its length.

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;
  • swRingBufferThe most important member variables in arealloc_offsetAndcollect_offsetalloc_offsetIs the starting address in the current loop array,collect_offsetRepresents the memory address in the current loop array that can be reclaimed.
  • free_countIs the number that can be recycled in the current loop array.
  • statusA value of 0 means that the memory space currently occupied by the circular array does not cross the end of the array, that is, its address is continuous. A value of 1 means that the memory space currently occupied by the circular array is partly at the end of the circular array and partly at the head of the array.

RingBufferCreation of

RingBufferThe creation of is similar toMemoryGlobal

RingBuffer swMemoryPool memory
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;
}

RingBufferMemory application

  • iffree_countGreater than 0 indicates that the memory to be reclaimed in the array needs to be reclaimed
  • If the memory currently occupied is not continuous, the remaining capacity of the current memory pool iscollect_offset - alloc_offset
  • If the memory currently occupied is continuous,

    • And the array is currentlycollect_offsetIf the memory at the end of the distance is greater than the number of memory requested, then the remaining capacity issize - alloc_offset
    • If the distance between the current memory location and the tail of the array is insufficient, then the current memory location and the tail of the array will be packed into oneswRingBuffer_itemArray elements, and marked as the elements to be recycledstatusSet to 1alloc_offsetFor the first address of the array, the remaining capacity iscollect_offsetYour address
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;
}

RingBufferMemory recovery

  • WhenRingBufferOffree_countWhen it is greater than 0, it means that there are elements in the current memory pool that need to be recycled. This function will be called to recycle memory every time when applying for new memory.
  • When reclaiming memory, this function will only reclaim consecutive multiple spare memory elements. If multiple memory elements to be reclaimed are isolated from each other, these memory elements will not be reclaimed.
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;
        }
    }
}

RingBufferMemory release

Memory release is very simple, just need to setlock0, and increasefree_countThe number of samples is as follows:

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

RingBufferDestruction of memory

static void swRingBuffer_destory(swMemoryPool *pool)
{
    swRingBuffer *object = pool->object;
    if (object->shared)
    {
        sw_shm_free(object);
    }
    else
    {
        sw_free(object);
    }
}
  • It’s worth noting that,RingBufferIn addition to the atomic lock, there is no lock. In the code of the application and release process, there is no thread safe lock free data structure, I thinkRingBufferIt is not a thread safe / process safe data structure, so when using this memory pool to apply for shared memory, you need to lock it yourself.

FixedPoolMemory pool implementation

FixedPooldata structure

FixedPoolIs a random allocation of memory pool, a block of memory space will be divided into small pieces of equal size, each time one of the small pieces is allocated as the memory to be used, these small pieces are stored in the form of two-way linked list.

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;

FixedPoolCreation of memory pool

FixedPoolThere are two functions to create a memory poolswFixedPool_newAndswFixedPool_new2Of whichswFixedPool_new2It is to build a memory pool based on the existing memorytableHow to create a shared memory 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;
}

The creation of memory pool is similar to the first two, but this time there are moreswFixedPool_initThis is the process of building a two-way linked list

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

As you can see, the program starts from the beginning of the memory space, initializing one at a timesliceThe size of the space and insert it into the head of the linked list, so the memory address and address of the whole linked listmemoryThe address of is the opposite.

FixedPoolApplication of memory pool

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;
    }
}
  • First, get the node at the head of the memory pool chain table, and judge whether the node is occupied. If it is occupied, the memory pool is full, and return null (because all occupied nodes will be put to the tail). If it is not occupied, move the next node to the head, and move the node to the tail, mark the node as occupied, and return the number of the node According to the domain.

FixedPoolRelease of memory pool

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;
}
  • First, by movingptrPointer acquisitionsliceObject, and will occupy the taglockSet to 0. If the node is the head node, it is returned directly. If it is not the head node, the node is moved to the head of the linked list.

FixedPoolDestruction of memory pool

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