前言
大家应该知道,swoole使用的是多进程模型,那么多进程之间怎么共享数据呢?
今天我就来给大家讲讲swoole多进程之间使用共享内存来共享数据。
这篇教程我参考了以下资料:
你如果有深厚的*unix环境编程经验,可以不用看我提供的参考列表。
请注意,接下来的文章中,如果知识点能简单的说清楚,我会在本篇中概括清楚,如果知识量比较大的,我会加上链接,大家可以跳转过去看完再回来继续。当然,你如果已经明白了这个知识点,那请继续。
源码解析
以下代码基于 swoole 4.4.16
共享内存的数据结构
1
2
3
4
5
6
7
8
9
10
11
#define SW_SHM_MMAP_FILE_LEN 64
typedef struct _swShareMemory_mmap
{
size_t size;
char mapfile[SW_SHM_MMAP_FILE_LEN];
int tmpfd;
int key;
int shmid;
void *mem;
} swShareMemory;
size
: 共享内存的大小(包含swShareMemory结构体的大小)
mapfile
: 共享内存使用的内存映射文件的文件名,最长64个字节。
tmpfd
: 内存映射文件的描述符。
key
: System V的shm系列函数创建共享内存的key的值。
shmid
: System V的shm系列函数创建的共享内存的id(类似于fd)。
mem
: void 类型的指针,这个变量里面存的是申请到的共享内存首地址,可以理解为相当于面向对象中的this指针。可以很方便的访问到swShareMemory结构体的各个变量
源文件 include/swoole.h
共享内存的创建
我们先来看看swoole是怎么创建共享内存的,下面是mmap的封装。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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_HUGE_PAGE)
if ( size > 2 * 1024 * 1024 )
{
#if defined(MAP_HUGETLD)
flag |= MAP_HUGETLB ;
#elif defined(MAP_ALIGNED_SUPER)
flag &= ~ MAP_ANONYMOUS ;
flag |= MAP_ALIGNED_SUPER ;
#endif
}
#endif
mem = mmap ( NULL , size , PROT_READ | PROT_WRITE , flag , tmpfd , 0 );
#ifdef MAP_FAILED
if ( mem == MAP_FAILED )
#else
if ( ! mem )
#endif
{
swSysWarn ( "mmap(%ld) failed" , size );
return NULL ;
}
else
{
object -> size = size ;
object -> mem = mem ;
return mem ;
}
}
通过以上代码,可以看到swoole是怎么封装mmap函数的。不得不说还是挺精妙的,那么我现在给大家一一解释各个代码的含义。
关于共享内存是怎么回事,mmap函数的详情,大家可以看一下我的另外一篇教程mmap函数详解
先给大家解释下几个常量的含义:
MAP_SHARED 与其它所有映射这个对象的进程共享映射空间。
MAP_ANONYMOUS 匿名映射,映射区不与任何文件关联。
MAP_HUGE_PAGE 是否支持大页模式分配内存
MAP_HUGETLB 使用大页模式
7-20行:
使用了宏判断,是否支持匿名映射,注意MAP_ANONYMOUS这个宏定义在-std=c99模式下是不支持的,但是-std=gnu99是支持的。如果使用了MAP_ANONYMOUS,那么会忽视fd参数,在不支持MAP_ANONYMOUS这个宏的系统中,使用操作系统提供的/dev/zero
文件提供匿名映射支持。
22-33行:
这里要注意的是MAP_HUGE_PAGE这个宏定义,这个是linux内核在2.6.32 引入的一个flags,确定是否使用大页分配共享内存。内核传统使用的是4k
小页,常见的体系架构都会提供两种大页模式,一般是2M和1G这两个size。使用大页可以减少页表的数量,从而减少TLB MISS的概率,提高系统访问性能,但是也会降低内存管理的粒度和灵活性,在使用小内存的时候造成内存浪费。
swoole在这里是判断要申请的内存size大于2M,就使用大页模式。
MAP_ALIGNED_SUPER 这个参数是FreeBSD中的特有参数,使用大页模式。
34-50
mmap函数的调用,PROT_READ|PROT_WRITE 表示申请的映射区可读可写,最后把申请的的内存映射区地址赋值给传入的object->mem。以后就可以通过swShareMemory来使用了。
swoole的进程模型是通过master进程fork出其它的工作进程,各个进程之间都有亲缘关系,因此swShareMemory_mmap_create函数可以直接使用匿名映射或(/dev/zero设备)的方式创建共享内存映射,不需要使用open
具体的共享内存文件或者使用shm_open
函数来打开POSIX IPC
名字。
swoole申请共享内存
swoole 申请共享内存使用sw_shm_malloc
,sw_shm_calloc
这两个函数,这两个函数都是为进程匿名申请一大块连续的内存.sw_shm_malloc是指定固定长度,sw_shm_calloc申请n个指定长度的内存,实际长度是size=_size*num。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void* sw_shm_malloc(size_t size)
{
size = SW_MEM_ALIGNED_SIZE(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 (char *) mem + sizeof(swShareMemory);
}
}
void* sw_shm_calloc(size_t num, size_t _size)
{
swShareMemory object;
void *mem;
void *ret_mem;
size_t size = sizeof(swShareMemory) + (num * _size);
size = SW_MEM_ALIGNED_SIZE(size);
mem = swShareMemory_mmap_create(&object, size, NULL);
if (mem == NULL)
{
return NULL;
}
else
{
memcpy(mem, &object, sizeof(swShareMemory));
ret_mem = (char *) mem + sizeof(swShareMemory);
bzero(ret_mem, size - sizeof(swShareMemory));
return ret_mem;
}
}
申请内存的大小有个小技巧,首先通过SW_MEM_ALIGNED_SIZE
宏做一次内存对齐,使得size永远是8的倍数。
再加上swShareMemory结构的长度,就是申请的共享内存的中长度了.申请共享内存成功后,把swShareMemory这个结构的object复制到内存的头部,再返回真正可用的共享内存首地址供调用方使用。
如果大家了解过redis的字符串底层实现,就知道这种做法跟sds结构是相似的。这样做的好处也是显而易见的,能够很方便的管理这段共享内存。
重新申请内存 realloc
如果我们申请了一段共享内存,使用的时候发现不够用了怎么办?这个时候就要使用到sw_shm_realloc
这个函数,
这个函数很简单,就是新申请一段共享内存,把旧的数据复制到新的内存中,然后再把旧的内存释放。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void* sw_shm_realloc(void *ptr, size_t new_size)
{
swShareMemory *object = (swShareMemory *) ((char *) 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;
}
}
这段代码 swShareMemory *object = (swShareMemory *) ((char *) ptr - sizeof(swShareMemory));
大家注意到了没,怎么样使用swShareMemory这个对象?只需要把传入的内存对象指针往前移动sizeof(swShareMemory)
长度,就能得到真正的共享内存首地址。也就能很方便的使用这个对象。接下来的代码大家都能看到这个用法。
修改共享内存权限
共享内存映射完成以后,有port
标记的PROT_READ
,PROT_WRITE
,PROT_EXEC
,PROT_NONE
权限仍然可以被mprotect
系统调用修改。具体用法参见 mmap详解
1
2
3
4
5
int sw_shm_protect(void *addr, int flags)
{
swShareMemory *object = (swShareMemory *) ((char *) addr - sizeof(swShareMemory));
return mprotect(object, object->size, flags);
}
释放共享内存
1
2
3
4
5
void sw_shm_free(void *ptr)
{
swShareMemory *object = (swShareMemory *) ((char *) ptr - sizeof(swShareMemory));
swShareMemory_mmap_free(object);
}
1
2
3
4
int swShareMemory_mmap_free(swShareMemory *object)
{
return munmap(object->mem, object->size);
}
System V共享内存
System V是Unix操作系统的一个分支,是一些Unix共同特性的源头,linux实现了System V一套应用于系统的接口协议,
POSIX相对于System V可以说是比较新的标准,语法也相对简单。所以swoole使用的基于POSIX协议实现的接口。
以下代码目前应该没有被使用,以后就说不准了。关于System V共享内存的知识,可以查看我的另外一篇文章 System V
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void *swShareMemory_sysv_create(swShareMemory *object, size_t size, int key)
{
int shmid;
void *mem;
bzero(object, sizeof(swShareMemory));
if (key == 0)
{
key = IPC_PRIVATE;
}
//SHM_R | SHM_W
if ((shmid = shmget(key, size, IPC_CREAT)) < 0)
{
swSysWarn("shmget(%d, %ld) failed", key, size);
return NULL;
}
if ((mem = shmat(shmid, NULL, 0)) == (void *) -1)
{
swSysWarn("shmat() failed");
return NULL;
}
else
{
object->key = key;
object->shmid = shmid;
object->size = size;
object->mem = mem;
return mem;
}
}
int swShareMemory_sysv_free(swShareMemory *object, int rm)
{
int shmid = object->shmid;
int ret = shmdt(object->mem);
if (rm == 1)
{
shmctl(shmid, IPC_RMID, NULL);
}
return ret;
}
使用情况
写到这里,swoole的共享内存代码讲解完毕。我们来讲一下swoole到底哪里使用了它们吧!
server的session_list
server的task_result
connection_list
swoole table
chanal