个人博客地址: https://cxx001.gitee.io
基础
在linux系统开发当中,时常需要在多个进程之间交换数据,在多个进程之间交换数据,有很多方法,但最高效的方法莫过于共享内存。
linux共享内存是通过tmpfs这个文件系统来实现的,tmpfs文件系的目录为/dev/shm,/dev/shm是驻留在内存 RAM 当中的,因此读写速度与读写内存速度一样,/dev/shm的容量默认尺寸为系统内存大小的一半大小,使用df -h命令可以看到。但实际上它并不会真正的占用这块内存,如果/dev/shm/下没有任何文件,它占用的内存实际上就是0字节,仅在使用shm_open文件时,/dev/shm才会真正占用内存。
在Linux系统使用共享内存,一般用到以下几个函数:
// 用于创建或者打开共享内存文件
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
#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
//#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等相关函数不认识 !
#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
#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目录下新增对应两个共享内存文件