logo头像
Snippet 博客主题

Linux共享内存

基础

在linux系统开发当中,时常需要在多个进程之间交换数据,在多个进程之间交换数据,有很多方法,但最高效的方法莫过于共享内存。

linux共享内存是通过tmpfs这个文件系统来实现的,tmpfs文件系的目录为/dev/shm,/dev/shm是驻留在内存 RAM 当中的,因此读写速度与读写内存速度一样,/dev/shm的容量默认尺寸为系统内存大小的一半大小,使用df -h命令可以看到。但实际上它并不会真正的占用这块内存,如果/dev/shm/下没有任何文件,它占用的内存实际上就是0字节,仅在使用shm_open文件时,/dev/shm才会真正占用内存。

在Linux系统使用共享内存,一般用到以下几个函数:

1
2
3
4
5
6
7
8
9
10
// 用于创建或者打开共享内存文件
int shm_open(const char *name, int oflag, mode_t mode);
// 将打开的文件映射到内存
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
// 取消内存映射
int munmap(void *addr, size_t length);
// 删除/dev/shm目录的文件
int shm_unlink(const char *name);
// 重置文件大小
int ftruncate(int fd, off_t length);

示例

1. 共享内存相关操作封装

SimuShareMem.h

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
#ifndef __SIMU_SHARE_MEM_
#define __SIMU_SHARE_MEM_

enum {
SIMU_MAX_SHM_BLOCK_QUEUE_LEN = 1024, // 队列长度
SIMU_MAX_SHM_BLOCK_BUFF_LEN = 2048 // 缓冲区数据长度
};

// 共享内存块
typedef struct TagSimuShareMemBlock
{
int ReadDataPtr; // 读下标
int WriteDataptr; // 写下标
unsigned long nCoverCount; // 写覆盖次数(好像不准确)
unsigned long nRepeatCount; // 读重复次数(没用到)
unsigned long nDataType[SIMU_MAX_SHM_BLOCK_QUEUE_LEN]; // 数据类型
unsigned long nDataLen[SIMU_MAX_SHM_BLOCK_QUEUE_LEN]; // 数据长度
char szData[SIMU_MAX_SHM_BLOCK_QUEUE_LEN][SIMU_MAX_SHM_BLOCK_BUFF_LEN]; // 数据区
}SimuShareMemBlock_t;

// 共享全双工节点
typedef struct TagSimuShareMemNode
{
int nReadShmfd; // 读共享内存文件句柄
int nWriteShmfd; // 写共享内存文件句柄
SimuShareMemBlock_t* pReadShm; // 读共享内存区块
SimuShareMemBlock_t* pWriteShm; // 写共享内存区块
char szReadShmName[128]; // 读共享内存块名称
char szWriteShmName[128]; // 写共享内存块名称
}SimuShareMemNode_t;

// 共享内存数据
typedef struct TagSimuShareMemData
{
int nDataType;
unsigned long ulDataLen;
char* pData;
unsigned long ulTsl;
unsigned long ulTs2;
}SimuShareMemData_t;

int CreateShareMemNode(SimuShareMemNode_t* pNode, const char* szWriteShmName, const char* szReadShmName);
int OpenShareMemNode(SimuShareMemNode_t* pNode, const char* szWriteShmName, const char* szReadShmName);
int CloseShareMemNode(SimuShareMemNode_t* pNode); // 取消内存映射
int UnlinkShareMem(const char* szWriteShmName, const char* szReadShmName); // 删除/dev/shm目录的文件
int IsNewShareMemData(SimuShareMemNode_t* pNode);
int ReadShareMemData(SimuShareMemNode_t* pNode, SimuShareMemData_t* pShmData);
int WriteShareMemData(SimuShareMemNode_t* pNode, int nDataType, const char* pData, unsigned long ulDataLen);

#endif

SimuShareMem.cpp

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//#ifdef LINUX_PLATFORM
#include "SimuShareMem.h"
#include <stdbool.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

static SimuShareMemBlock_t* CreateShareMemBlock(int* pFd, const char* szBlockName);
static SimuShareMemBlock_t* OpenShareMemBlock(int* pFd, const char* szBlockName);

SimuShareMemBlock_t* CreateShareMemBlock(int* pFd, const char* szBlockName)
{
SimuShareMemBlock_t* pBlock = NULL;
int hFd = 0;

if (pFd == NULL || szBlockName == NULL) {
return NULL;
}

// 打开文件如果没有就创建, 存在则打开失败返回-1
hFd = shm_open(szBlockName, O_CREAT|O_EXCL|O_RDWR, S_IRWXU|S_IRWXG);
if (hFd == -1) {
if (errno == EEXIST) {
hFd = shm_open(szBlockName, O_RDWR, S_IRWXU);
if (hFd == -1) {
return NULL;
}
} else {
return NULL;
}
}

// 重置文件大小
if (ftruncate(hFd, sizeof(SimuShareMemBlock_t)) == -1) {
close(hFd);
return NULL;
}

// 将打开的文件映射到内存
pBlock = (SimuShareMemBlock_t*)mmap(NULL, sizeof(SimuShareMemBlock_t), PROT_READ|PROT_WRITE, MAP_SHARED, hFd, 0);
if (pBlock == NULL) {
close(hFd);
return NULL;
}
*pFd = hFd;

return pBlock; // 共享内存地址
}

SimuShareMemBlock_t* OpenShareMemBlock(int* pFd, const char* szBlockName)
{
SimuShareMemBlock_t* pBlock = NULL;
int hFd = shm_open(szBlockName, O_RDWR, S_IRWXU);
if (hFd == -1) {
return NULL;
}

pBlock = (SimuShareMemBlock_t*)mmap(NULL, sizeof(SimuShareMemBlock_t), PROT_READ|PROT_WRITE, MAP_SHARED, hFd, 0);
if (pBlock == NULL) {
close(hFd);
return NULL;
}
*pFd = hFd;

return pBlock;
}

int CreateShareMemNode(SimuShareMemNode_t* pNode, const char* szWriteName, const char* szReadName)
{
bool bError = false;
if (pNode == NULL || szWriteName == NULL || szReadName == NULL) {
return -1;
}
strcpy(pNode->szWriteShmName, szWriteName);
strcpy(pNode->szReadShmName, szReadName);
pNode->nReadShmfd = 0;
pNode->nWriteShmfd = 0;

do {
pNode->pReadShm = CreateShareMemBlock(&pNode->nReadShmfd, pNode->szReadShmName);
if (pNode->pReadShm == NULL) {
bError = true;
break;
}
pNode->pReadShm->nCoverCount = 0;
pNode->pReadShm->nRepeatCount = 0;
pNode->pReadShm->ReadDataPtr = 0;
pNode->pReadShm->WriteDataptr = 0;

pNode->pWriteShm = CreateShareMemBlock(&pNode->nWriteShmfd, pNode->szWriteShmName);
if (pNode->pWriteShm == NULL) {
bError = true;
break;
}
pNode->pWriteShm->nCoverCount = 0;
pNode->pWriteShm->nRepeatCount = 0;
pNode->pWriteShm->ReadDataPtr = 0;
pNode->pWriteShm->WriteDataptr = 0;
} while(0);

if(bError) {
CloseShareMemNode(pNode);
return -1;
}
return 0;
}

int OpenShareMemNode(SimuShareMemNode_t* pNode, const char* szWriteName, const char* szReadName)
{
bool bError = false;
if (pNode == NULL || szWriteName == NULL || szReadName == NULL) {
return -1;
}
strcpy(pNode->szWriteShmName, szWriteName);
strcpy(pNode->szReadShmName, szReadName);
pNode->nReadShmfd = 0;
pNode->nWriteShmfd = 0;

do {
pNode->pReadShm = OpenShareMemBlock(&pNode->nReadShmfd, pNode->szReadShmName);
if (pNode->pReadShm == NULL) {
bError = true;
break;
}

// 这里注释是因为写的测试程序起来就写数据了, 所以读的测试程序获取这块空间时不能重置了.
// 正常程序这里不要注释,所有进程都要启动了,才能往共享内存里读写数据.
/*
pNode->pReadShm->nCoverCount = 0;
pNode->pReadShm->nRepeatCount = 0;
pNode->pReadShm->ReadDataPtr = 0;
pNode->pReadShm->WriteDataptr = 0;
*/

pNode->pWriteShm = OpenShareMemBlock(&pNode->nWriteShmfd, pNode->szWriteShmName);
if (pNode->pWriteShm == NULL) {
bError = true;
break;
}
/*
pNode->pWriteShm->nCoverCount = 0;
pNode->pWriteShm->nRepeatCount = 0;
pNode->pWriteShm->ReadDataPtr = 0;
pNode->pWriteShm->WriteDataptr = 0;
*/

} while(0);

if(bError) {
CloseShareMemNode(pNode);
return -1;
}
return 0;
}

int CloseShareMemNode(SimuShareMemNode_t* pNode)
{
if (pNode == NULL) {
return -1;
}

if (pNode->pReadShm != NULL) {
// 取消内存映射
munmap(pNode->pReadShm, sizeof(SimuShareMemBlock_t));
pNode->pReadShm = NULL;
close(pNode->nReadShmfd);
}

if (pNode->pWriteShm != NULL) {
// 取消内存映射
munmap(pNode->pWriteShm, sizeof(SimuShareMemBlock_t));
pNode->pWriteShm = NULL;
close(pNode->nWriteShmfd);
}

return 0;
}

int UnlinkShareMem(const char* szWriteName, const char* szReadName)
{
// 删除/dev/shm目录的文件
shm_unlink(szWriteName);
shm_unlink(szReadName);
return 0;
}

int IsNewShareMemData(SimuShareMemNode_t* pNode)
{
SimuShareMemBlock_t* pShm = pNode->pReadShm;
if (pShm->ReadDataPtr % SIMU_MAX_SHM_BLOCK_QUEUE_LEN == pShm->WriteDataptr % SIMU_MAX_SHM_BLOCK_QUEUE_LEN) {
return -1;
}
return 0;
}

int ReadShareMemData(SimuShareMemNode_t* pNode, SimuShareMemData_t* pShmData)
{
SimuShareMemBlock_t* pShm = pNode->pReadShm;
unsigned long nReadIdx = 0;

if (pShm->ReadDataPtr % SIMU_MAX_SHM_BLOCK_QUEUE_LEN == pShm->WriteDataptr % SIMU_MAX_SHM_BLOCK_QUEUE_LEN) {
return -1;
}

nReadIdx = pShm->ReadDataPtr % SIMU_MAX_SHM_BLOCK_QUEUE_LEN;
pShmData->nDataType = pShm->nDataType[nReadIdx];
pShmData->ulDataLen = pShm->nDataLen[nReadIdx];
pShmData->pData = (char*)malloc(pShmData->ulDataLen);
memcpy((void*)pShmData->pData, pShm->szData[nReadIdx], pShmData->ulDataLen);
pShm->ReadDataPtr += 1;
pShm->ReadDataPtr %= SIMU_MAX_SHM_BLOCK_QUEUE_LEN;
pShmData->ulTsl = time(NULL);
return 0;
}

int WriteShareMemData(SimuShareMemNode_t* pNode, int nDataType, const char* pData, unsigned long ulDataLen)
{
SimuShareMemBlock_t* pShm = pNode->pWriteShm;
unsigned long nWriteIdx = 0;

if (pShm->ReadDataPtr == (pShm->WriteDataptr + 1) % SIMU_MAX_SHM_BLOCK_QUEUE_LEN) {
pShm->nCoverCount++; // 这里不知道啥意思
}

nWriteIdx = pShm->WriteDataptr % SIMU_MAX_SHM_BLOCK_QUEUE_LEN;
memcpy(pShm->szData[nWriteIdx], pData, ulDataLen);
pShm->nDataLen[nWriteIdx] = ulDataLen;
pShm->nDataType[nWriteIdx] = nDataType;
pShm->WriteDataptr++;
pShm->WriteDataptr %= SIMU_MAX_SHM_BLOCK_QUEUE_LEN;

return 0;
}

//#endif

2. 使用示例

共享内存写数据: writer.c

编译命令: g++ writer.c SimuShareMem.h SimuShareMem.cpp -o writer -lrt

注意最后的 -lrt链接库不能少,不然shm_open等相关函数不认识 !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include "SimuShareMem.h"

int main(int argc,char * argv[])
{
SimuShareMemNode_t pNode;
const char* szWriteShmName = "shm_writer";
const char* szReadShmName = "shm_reader";
CreateShareMemNode(&pNode, szWriteShmName, szReadShmName);

char writeData[] = "test share memory writer.";
int result = WriteShareMemData(&pNode, 1, writeData, sizeof(writeData));
printf("result:%d, data:%s, len:%ld", result, writeData, sizeof(writeData));

getchar();

CloseShareMemNode(&pNode);
UnlinkShareMem(szWriteShmName, szReadShmName);

return 0;
}

共享内存读数据:reader.c

编译命令: g++ reader.c SimuShareMem.h SimuShareMem.cpp -o reader -lrt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include "SimuShareMem.h"

int main(int argc,char * argv[])
{
SimuShareMemNode_t pNode;
const char* szWriteShmName = "shm_writer";
const char* szReadShmName = "shm_reader";
OpenShareMemNode(&pNode, szReadShmName, szWriteShmName); // 这里的读是对面的写

SimuShareMemData_t srData;
int result = ReadShareMemData(&pNode, &srData);
printf("result: %d, data: %s, len: %ld", result, srData.pData, srData.ulDataLen);

getchar();
return 0;
}

3. 测试结果

  • 测试机器

  • 分别启动writer/reader程序读写结果

  • /dev/shm目录下新增对应两个共享内存文件