Swoole 源码分析——内存模块之内存池
前言
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 4096swMemoryPool* 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,其內存結構為:
鏈表元素的創建比較簡單,就是申請內存,初始化單鏈表的各個變量。
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 的地址
RingBuffer 內存的回收
- 當 RingBuffer 的 free_count 大于 0 的時候,就說明當前內存池存在需要回收的元素,每次在申請新的內存時,都會調用這個函數來回收內存。
- 回收內存時,本函數只會回收連續的多個空余的內存元素,若多個待回收的內存元素之間相互隔離,那么這些內存元素不會被回收。
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, ABif (slice->pre == NULL){return;}//list tail, DEif (slice->next == NULL){slice->pre->next = NULL;object->tail = slice->pre;}//middle BCDelse{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);} }總結
以上是生活随笔為你收集整理的Swoole 源码分析——内存模块之内存池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 网络知识必知必会
- 下一篇: 中国航天将与人工智能技术携手 未来可期