资讯专栏INFORMATION COLUMN

Swoole 源码分析——内存模块之共享内存

diabloneo / 2560人阅读

摘要:前言我们知道,由于没有多线程模型,所以更多的使用多进程模型,因此代码相对来说更加简洁,减少了各种线程锁的阻塞与同步,但是也带来了新的问题数据同步。相比多线程之前可以直接共享进程的内存,进程之间数据的相互同步依赖于共享内存。

前言

我们知道,由于 PHP 没有多线程模型,所以 swoole 更多的使用多进程模型,因此代码相对来说更加简洁,减少了各种线程锁的阻塞与同步,但是也带来了新的问题:数据同步。相比多线程之前可以直接共享进程的内存,进程之间数据的相互同步依赖于共享内存。本文将会讲解 swoole 中共享内存的源码。

前置知识:

mmap 函数的使用: APUE 学习笔记——高级 IO

共享内存: APUE 学习笔记——进程间通信

共享内存数据结构
typedef struct _swShareMemory_mmap
{
    size_t size;
    char mapfile[SW_SHM_MMAP_FILE_LEN];
    int tmpfd;
    int key;
    int shmid;
    void *mem;
} swShareMemory;

注意 mem 是一个 void 类型的指针,用于存放共享内存的首地址。这个成员变量相当于面向对象中的 this 指针,通过它就可以访问到 swShareMemory 的各个成员。

size 代表共享内存的大小(不包括 swShareMemory 结构体大小), mapfile[] 代表共享内存使用的内存映射文件的文件名, tmpfd 为内存映射文件的描述符。key 代表使用 System Vshm 系列函数创建的共享内存的 key 值, shmidshm 系列函数创建的共享内存的 id(类似于fd),这两个由于不是 POSIX 标准定义的 api,用途有限。

共享内存的申请与创建

swoole 在申请共享内存时常常调用的函数是 sw_shm_malloc,这个函数可以为进程匿名申请一大块连续的共享内存:

void* sw_shm_malloc(size_t size)
{
    swShareMemory object;
    void *mem;
    size += sizeof(swShareMemory);
    mem = swShareMemory_mmap_create(&object, size, NULL);
    if (mem == NULL)
    {
        return NULL;
    }
    else
    {
        memcpy(mem, &object, sizeof(swShareMemory));
        return mem + sizeof(swShareMemory);
    }
}

sw_shm_malloc 函数可以看出,虽然我们申请的是 size,但是实际申请的内存是要略大的,因为还要加上 swShareMemory 这个结构体。当函数返回时,也不会直接返回申请的内存首地址,而是复制了 object 各个成员变量的值后,在申请的首地址上加上 swShareMemory 的大小。

void *swShareMemory_mmap_create(swShareMemory *object, size_t 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;
    }
#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(%ld) failed. Error: %s[%d]", size, strerror(errno), errno);
        return NULL;
    }
    else
    {
        object->size = size;
        object->mem = mem;
        return mem;
    }
}

由于 swoole 的各个进程都是由 master 进程所建立,也就是各个进程之间存在亲戚关系, 因此swShareMemory_mmap_create 函数直接以 匿名映射 、(/dev/zero 设备) 的方式利用 mmap 建立共享内存,并没有 open 具体的共享内存文件,或者调用 shm_open 打开 POSIX IPC 名字。

值得注意的是 MAP_HUGETLB,这个是 linux 内核 2.6.32 引入的一个 flags,用于使用大页面分配共享内存。大页是相对传统 4K 小页而言的,一般来说常见的体系架构都会提供2种大页大小,比如常见的 2M 大页和 1G 大页。使用大页可以减少页表项数量,从而减少 TLB Miss 的概率,提升系统访存性能。当然有利必有弊,使用大页降低了内存管理的粒度和灵活性,如果程序并不是对内存的使用量特别大,使用大页还可能造成内存的浪费。

共享内存的 calloc

callocmalloc 大同小异,无非多了一个 num 参数

void* sw_shm_calloc(size_t num, size_t _size)
{
    swShareMemory object;
    void *mem;
    void *ret_mem;
    int size = sizeof(swShareMemory) + (num * _size);
    mem = swShareMemory_mmap_create(&object, size, NULL);
    if (mem == NULL)
    {
        return NULL;
    }
    else
    {
        memcpy(mem, &object, sizeof(swShareMemory));
        ret_mem = mem + sizeof(swShareMemory);
        bzero(ret_mem, size - sizeof(swShareMemory));
        return ret_mem;
    }
}
共享内存的 realloc

realloc 函数用于修改已申请的内存大小,逻辑非常简单,先申请新的内存,进行复制后,再释放旧的内存:

void* sw_shm_realloc(void *ptr, size_t new_size)
{
    swShareMemory *object = ptr - sizeof(swShareMemory);
    void *new_ptr;
    new_ptr = sw_shm_malloc(new_size);
    if (new_ptr == NULL)
    {
        return NULL;
    }
    else
    {
        memcpy(new_ptr, ptr, object->size);
        sw_shm_free(ptr);
        return new_ptr;
    }
}
修改共享内存的权限

在内存映射完成后,由标记读、写、执行权限的 PROT_READPROT_WRITEPROT_EXEC 等权限仍可以被 mprotect 系统调用所修改。

int sw_shm_protect(void *addr, int flags)
{
    swShareMemory *object = (swShareMemory *) (addr - sizeof(swShareMemory));
    return mprotect(object, object->size, flags);
}
共享内存的释放
void sw_shm_free(void *ptr)
{
    swShareMemory *object = ptr - sizeof(swShareMemory);
    swShareMemory_mmap_free(object);
}

int swShareMemory_mmap_free(swShareMemory *object)
{
    return munmap(object->mem, object->size);
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/29198.html

相关文章

  • Swoole 源码分析——基础模块 Channel 队列

    摘要:前言内存数据结构,类似于的通道,底层基于共享内存互斥锁实现,可实现用户态的高性能内存队列。是当前队列占用的内存大小,用来指定是否使用共享内存是否使用锁是否使用通知。 前言 内存数据结构 Channel,类似于 Go 的 chan 通道,底层基于 共享内存 + Mutex 互斥锁实现,可实现用户态的高性能内存队列。Channel 可用于多进程环境下,底层在读取写入时会自动加锁,应用层不需...

    txgcwm 评论0 收藏0
  • Swoole 源码分析——内存模块共享内存swoole_table

    摘要:如果互斥锁的持有者死亡了,或者持有这样的互斥锁的进程了互斥锁所在的共享内存或者持有这样的互斥锁的进程执行了调用,则会解除锁定该互斥锁。互斥锁的下一个持有者将获取该互斥锁并返回错误。 前言 swoole_table 一个基于共享内存和锁实现的超高性能,并发数据结构。用于解决多进程/多线程数据共享和同步加锁问题。 swoole_table 的数据结构 swoole_table 实际上...

    Invoker 评论0 收藏0
  • Swoole 源码分析——内存模块内存

    摘要:前言中为了更好的进行内存管理,减少频繁分配释放内存空间造成的损耗和内存碎片,程序设计并实现了三种不同功能的内存池,和。比较特殊的是单链表内存池的内存只能增加不会减少。 前言 Swoole 中为了更好的进行内存管理,减少频繁分配释放内存空间造成的损耗和内存碎片,程序设计并实现了三种不同功能的内存池:FixedPool,RingBuffer 和 MemoryGlobal。 其中 Memor...

    stormzhang 评论0 收藏0
  • Swoole 源码分析——内存模块swBuffer

    摘要:的数据结构数据结构中是链表元素的个数,是缓冲区创建时,链表元素约定的大小实际大小不一定是这个值,是实际上缓冲区占用的内存总大小。中的有三种,分别应用于缓存数据发送文件提醒连接关闭三种情景。指的是元素的内存大小。 前言 swoole 中数据的接受与发送(例如 reactor 线程接受客户端消息、发送给客户端的消息、接受到的来自 worker 的消息、要发送给 worker 的消息等等)都...

    fyber 评论0 收藏0
  • Swoole 源码分析——Server模块初始化

    摘要:如果在调用之前我们设置了,但是不在第二个进程启动前这个套接字,那么第二个进程仍然会在调用函数的时候出错。 前言 本节主要介绍 server 模块进行初始化的代码,关于初始化过程中,各个属性的意义,可以参考官方文档: SERVER 配置选项 关于初始化过程中,用于监听的 socket 绑定问题,可以参考: UNP 学习笔记——基本 TCP 套接字编程 UNP 学习笔记——套接字选项 构造...

    Half 评论0 收藏0

发表评论

0条评论

diabloneo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<