热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

swoole内存管理分析

共享内存swoole由于采用多进程模型,可以避免多线程锁开销。不过,多进程需要进程间通信,swoole采用了共享内存,共享内存的结构体如下:共享内存typedefstruct_swShareM

共享内存

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 {
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 {
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


推荐阅读
  • STM32 IO口模拟串口通讯
    转自:http:ziye334.blog.163.comblogstatic224306191201452833850647前阵子,调项目时需要用到低波 ... [详细]
  • 该楼层疑似违规已被系统折叠隐藏此楼查看此楼*madebyebhrz*#include#include#include#include#include#include#include ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • #define_CRT_SECURE_NO_WARNINGS#includelist.h#includevoidSListInit(PNode*pHead ... [详细]
  • 本文介绍了使用C++Builder实现获取USB优盘序列号的方法,包括相关的代码和说明。通过该方法,可以获取指定盘符的USB优盘序列号,并将其存放在缓冲中。该方法可以在Windows系统中有效地获取USB优盘序列号,并且适用于C++Builder开发环境。 ... [详细]
  • 设备模型三(潜谈sysfs)
    前言引出一个问题:假设sysaxx,xx是kobja的属性文件,当对xx进行写操作时,即echo‘1’sysaxx实际上,调用了kobja的ktype中定义的接口函 ... [详细]
author-avatar
周家华099_359
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有