swoole記憶體管理分析
共享記憶體
swoole由於採用多程式模型,可以避免多執行緒鎖開銷。不過,多程式需要程式間通訊,swoole採用了共享記憶體,共享記憶體的結構體如下:
//共享記憶體
typedef struct _swShareMemory_mmap
{
int size; //共享記憶體的大小
char mapfile[SW_SHM_MMAP_FILE_LEN]; //用來儲存共享記憶體使用的記憶體對映檔案的檔名
int tmpfd; //記憶體對映檔案的描述符
int key; //shm系列函式建立的共享記憶體的key值
int shmid; //shm系列函式建立的共享記憶體的id
void *mem; //用來儲存所用記憶體
} swShareMemory;
swoole天生支援mmap和sysv兩種方式共享記憶體。
建立共享記憶體:
void *swShareMemory_mmap_create(swShareMemory *object, int size, char *mapfile)
{
void *mem;
int tmpfd = -1;
int flag = MAP_SHARED;
bzero(object, sizeof(swShareMemory));
#ifdef MAP_ANONYMOUS
flag |= MAP_ANONYMOUS; //使用匿名對映
#else
if (mapfile == NULL)
{
mapfile = "/dev/zero"; //呵呵,又是你!
}
if ((tmpfd = open(mapfile, O_RDWR)) < 0)
{
return NULL;
}
strncpy(object->mapfile, mapfile, SW_SHM_MMAP_FILE_LEN);
object->tmpfd = tmpfd;
#endif
#if defined(SW_USE_HUGEPAGE) && defined(MAP_HUGETLB)
if (size > 2 * 1024 * 1024)
{
flag |= MAP_HUGETLB; //hugepage
}
#endif
mem = mmap(NULL, size, PROT_READ | PROT_WRITE, flag, tmpfd, 0);
#ifdef MAP_FAILED
if (mem == MAP_FAILED)
#else
if (!mem)
#endif
{
swWarn("mmap() failed. Error: %s[%d]", strerror(errno), errno);
return NULL;
}
else
{
object->size = size;
object->mem = mem;
return mem;
}
}
記憶體池
下面是記憶體池的結構:
//-------------------memory manager-------------------------
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;
上面可以說是一個基類,這類似於libevent,利用函式指標來實現多型。
FixedPool
swoole.h中定義了FixedPool的結構體,首先來看swFixedPool_slice,這是FixedPool記憶體池小塊的結構宣告:
typedef struct _swFixedPool_slice
{
uint8_t lock; //標記是否被佔用,0代表空閒,1代表佔用
struct _swFixedPool_slice *next; //後繼指標
struct _swFixedPool_slice *pre; //前驅指標
char data[0]; //記憶體空間指標,柔性陣列
} swFixedPool_slice;
swFixedPool是真正的記憶體池結構體:
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;
剖析完了結構體,那就來從記憶體分配的new函式來分析:
/**
* create new FixedPool, random alloc/free fixed size memory
*/
swMemoryPool* swFixedPool_new(uint32_t slice_num, uint32_t slice_size, uint8_t shared)
{
//計算記憶體池的大小:slice大小*slice數量+MemoryPool頭部大小+FixedPool頭部大小
size_t size = slice_size * slice_num + slice_num * sizeof(swFixedPool_slice);
size_t alloc_size = size + sizeof(swFixedPool) + sizeof(swMemoryPool);
//如果使用共享記憶體,則使用sw_shm_malloc(alloc_size)使用mmap分配共享記憶體
void *memory = (shared == 1) ? sw_shm_malloc(alloc_size) : sw_malloc(alloc_size);
swFixedPool *object = memory;
memory += sizeof(swFixedPool); //實際記憶體空間起始於swFixedPool的memory中
bzero(object, sizeof(swFixedPool));
object->shared = shared;
object->slice_num = slice_num;
object->slice_size = slice_size;
object->size = size;
swMemoryPool *pool = memory;
memory += sizeof(swMemoryPool);
//填充幾個回撥函式
pool->object = object;
pool->alloc = swFixedPool_alloc; //這樣以後分配記憶體就是用swFixedPool記憶體池分配記憶體了
pool->free = swFixedPool_free;
pool->destroy = swFixedPool_destroy;
object->memory = memory;
/**
* init linked list
*/
swFixedPool_init(object);
return pool;
}
我們申請記憶體可以就可以用下面的方式來做:
SwooleGS = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(swServerGS));
FixedPool有四個操作函式:
static void swFixedPool_init(swFixedPool *object);
static void* swFixedPool_alloc(swMemoryPool *pool,uint32_t size);
static void swFixedPool_free(swMemoryPool *pool, void*ptr);
static void swFixedPool_destroy(swMemoryPool *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);
}
下面是swFixedPool_alloc函式。swFixwd記憶體塊的大小是固定的,所以第二個引數只是一個佔位符:
static void* swFixedPool_alloc(swMemoryPool *pool, uint32_t size)
{
swFixedPool *object = pool->object;
swFixedPool_slice *slice;
slice = object->head;
if (slice->lock == 0) //判斷該結點是否被佔用,如果被佔用,說明記憶體池已滿,返回NULL(因為所有被佔用的節點都都會被放到尾部)
{
//未被佔用,則將該結點的下一個節點移到首部,並將該結點移動到尾部,標記該結點為佔用狀態,返回該結點資料域。
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;
}
}
下面是示範記憶體函式,第二個引數是需要釋放的資料域:
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;
}
釋放整個記憶體池:
static void swFixedPool_destroy(swMemoryPool *pool)
{
swFixedPool *object = pool->object;
if (object->shared)
{
sw_shm_free(object);
}
else
{
sw_free(object);
}
}
RingBuffer
FixedPool是採用連結串列作為記憶體池的管理結構,而RingBuffer則是採用迴圈陣列來管理記憶體,並且RingBuffer的每塊記憶體可以是不等長的。蝦米那是RingBuffer的結構體,在RingBuffer.c中:
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; //有多少記憶體待回收,由於RingBuffer採用連續分配,可能存在一些已經被free的記憶體塊夾在兩個沒有free的記憶體塊中間
//沒有立即回收,需要一個變數通知記憶體池回收這些記憶體
void *memory; //記憶體池的起始地址
} swRingBuffer;
其中RingBuffer的memory每次分配、收集和釋放都是按照swRingBuffer_item為基本單位來進行的。
typedef struct
{
uint16_t lock;
uint16_t index;
uint32_t length; //每個記憶體塊記錄長度的變數
char data[0];
} swRingBuffer_item;
RingBuffer同樣定義了四個函式,不過著重關注swRingBuffer_collect()和swRingBuffer_alloc()函式即可。
下面是swRingBuffer_alloc()函式剖析:
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); //沒錯,分配記憶體就是一個item加真實記憶體的組合
if (object->free_count > 0) //如果有空閒記憶體,走到這裡的時候順便回收空閒記憶體
swRingBuffer_collect(object);
}
if (object->status == 0)
{
//特殊情況,記憶體在末尾,有記憶體但不夠用
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (object->alloc_offset + alloc_size >= (object->size - sizeof(swRingBuffer_item))) //如果最末尾空閒記憶體不足alloc_size
{
uint32_t skip_n = object->size - object->alloc_offset; //末尾可分配記憶體
if (skip_n >= sizeof(swRingBuffer_item)) //大於item所用的記憶體,我們先單獨分配一塊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); //已分配塊數+1
}
object->alloc_offset = 0; //調整偏移量從頭重新開始
object->status = 1; //
capacity = object->collect_offset - object->alloc_offset; //由於從頭開始了,容量就等於collect_offset-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; //更新alloc_offset,表示分配出去一塊記憶體
object->alloc_count ++;
swDebug("alloc: ptr=%d", (void *)item->data - object->memory);
return item->data;
}
接下來是swRingBuffer_collect()函式:
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; //從collect_offset開始回收
if (item->lock == 0)
{
n_size = item->length + sizeof(swRingBuffer_item); //每一塊記憶體由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); //原子操作,free_count-1
}
else
{
break;
}
}
}
還有一個記憶體池是MemoryGlobal,暫時沒有剖析,下次再說。
相關文章
- Swoole 原始碼分析——記憶體模組之記憶體池原始碼記憶體
- Go記憶體管理逃逸分析Go記憶體
- Windows記憶體管理分析(一)Windows記憶體
- Windows記憶體管理分析(二)Windows記憶體
- 記憶體管理 記憶體管理概述記憶體
- Swoole 核心開發備忘:記憶體管理優化(swString)記憶體優化
- Linux記憶體洩露案例分析和記憶體管理分享Linux記憶體洩露
- Memcached記憶體管理原始碼分析記憶體原始碼
- 記憶體管理篇——實體記憶體的管理記憶體
- 【記憶體管理】記憶體佈局記憶體
- 記憶體管理兩部曲之實體記憶體管理記憶體
- Go:記憶體管理與記憶體清理Go記憶體
- Java的記憶體 -JVM 記憶體管理Java記憶體JVM
- 【記憶體管理】Oracle AMM自動記憶體管理詳解記憶體Oracle
- 記憶體管理兩部曲之虛擬記憶體管理記憶體
- Linux堆記憶體管理深入分析(下半部)Linux記憶體
- Flink記憶體管理記憶體
- 記憶體管理-swMemoryGlobal記憶體
- MySQL記憶體管理MySql記憶體
- JavaScript 記憶體管理JavaScript記憶體
- iOS 記憶體管理iOS記憶體
- Android記憶體管理Android記憶體
- OC記憶體管理記憶體
- Ubuntu記憶體分析Ubuntu記憶體
- JVM記憶體分析JVM記憶體
- 【記憶體管理】Oracle如何使用ASMM自動共享記憶體管理記憶體OracleASM
- linux記憶體管理(一)實體記憶體的組織和記憶體分配Linux記憶體
- 關於redis記憶體分析,記憶體優化Redis記憶體優化
- Linux實體記憶體管理Linux記憶體
- Java堆疊的深度分析及記憶體管理技巧Java記憶體
- spark 原始碼分析之十五 -- Spark記憶體管理剖析Spark原始碼記憶體
- C++記憶體管理C++記憶體
- Windows記憶體管理-分段Windows記憶體
- JavaScript的記憶體管理JavaScript記憶體
- CF的記憶體管理。記憶體
- HotSpot JVM 記憶體管理HotSpotJVM記憶體
- iOS 記憶體管理MRCiOS記憶體
- “理解”iOS記憶體管理iOS記憶體
- iOS 記憶體管理研究iOS記憶體