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,利用函数指针来实现多态。
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);
}
}
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源码学习记录(三)——三种MemoryPool