前言
Swoole
中為了更好的進行記憶體管理,減少頻繁分配釋放記憶體空間造成的損耗和記憶體碎片,程式設計並實現了三種不同功能的記憶體池:FixedPool
,RingBuffer
和 MemoryGlobal
。
其中 MemoryGlobal
用於全域性變數 SwooleG.memory_pool
,RingBuffer
用於 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
- 建立的
MemoryGlobal
的current_offset
被初始化為swMemoryGlobal
與swMemoryPool
的大小之和 -
返回的
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_offset
與collect_offset
,alloc_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
記憶體的回收
- 當
RingBuffer
的free_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_new
與 swFixedPool_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);
}
}