最近传输音视频流数据用到了共享内存进行多线程通信,遇到了一些bug。就想着自己实现一个比较可靠的多线程共享数据通信。搞了一天,其中也遇到了一点坑,坑了我很久,记录一下,避免日后再踩坑。
首先,共享内存属于多进程的共享资源,必须保证读写是安全的,要确保一个进程在写的时候不能被读,在一个进程读的时候,其他进程不能写。所以可以选择信号量做同步,也可以选择互斥量做同步。这里我选择了互斥量,因为互斥量时间开销比信号量小,而且后续可以结合条件变量做成堵塞的FIFO (First In First Out)。
关于互斥量与信号量的性能比较可以参考这篇文章:
https://www.cnblogs.com/webber1992/p/6550667.html
实际使用中,往往是一个进程写,一个进程读。
思路:用一个结构体来存储共享内存的相关信息
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <errno.h>
struct SHARE_BUF_MSG
{
unsigned int magic_key; // buf key
unsigned int buffer_size; // total buffer_size
unsigned int valid_size; //valid data count
unsigned int writeptr; // write pointer
unsigned int readptr; // write pointer
pthread_mutex_t rw_mutex; //read write lock
//unsigned char * buffer; // buffer to save;
};
struct SHARE_BUFFER
{
struct SHARE_BUF_MSG *msg;
unsigned char * buffer; // buffer to save;
};
typedef struct
{
unsigned int magic; //SHBUF_MAGIC_NUM用于数据包检验
unsigned int packlen; // pakage data size
}SHBUF_mnghead;
这里我犯了个错误,就是把数据buffer也放在了结构体SHARE_BUF_MSG 里面,搞了半天,总是出现segment fault,两个读写进程不能同时存在。。
初始化,申请两块共享内存,一块是SHARE_BUF_MSG,一块是存数据用的buffer。一开始我是打算申请一大块共享内存,前面少量字节作为SHARE_BUF_MSG用,剩下的作为buffer用(后来发现也是可以的)。就是因为刚开始我把buffer这个指针成员也放进了SHARE_BUF_MSG,存放着共享内存的地址,这个指针占的空间本身也在共享内存里,导致第一个进程运行之后,再运行第二个进程,初始化就出现segment fault了。因为第二个进程申请的buffer地址是逻辑地址,和第一个进程的逻辑地址不一样,buffer指针的值被第二个进程修改了,所以第一个进程访问的buffer就不对了,所以就segment fault了。
void *SHARE_BUFFER_Init(int size,int infoid,int bufid)
{
struct SHARE_BUFFER *buffer_share=NULL;
struct SHARE_BUF_MSG *buffer_msg = NULL;
if(size <=0)
return NULL;
buffer_share = calloc(sizeof(struct SHARE_BUFFER),1);
if(buffer_share == NULL)
return NULL;
int mid = shmget(infoid, sizeof(struct SHARE_BUF_MSG) , IPC_CREAT | 0660);
if(mid < 0){
printf("memory already create\n");
mid = shmget(infoid, 0, 0);
if(mid < 0)
return NULL;
}
buffer_share->msg = (struct SHARE_BUF_MSG *)shmat(mid,NULL,0);
if(buffer_share->msg == NULL)
{
perror("shmat addr error") ;
printf("buf = NULL\n");
return NULL;
}
else
{
mid = shmget(bufid, size , IPC_CREAT | 0660 );
if(mid < 0)
{
printf("memory already create\n");
mid = shmget(bufid, 0, 0);
if(mid < 0)
{
perror("shmget error") ;
return NULL;
}
}
buffer_share->buffer = shmat(mid,NULL,0);
if(buffer_share->buffer == NULL)
{
perror("shmat addr error") ;
return NULL;
}
if(buffer_share->msg->magic_key != bufid)
{
memset(buffer_share->buffer,0,size);
buffer_share->msg->magic_key = bufid;
buffer_share->msg->buffer_size = size;
pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr); // 初始化 mutex 属性
pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED); // 修改属性为进程间共享
pthread_mutex_init(&buffer_share->msg->rw_mutex,&mutexattr); // 初始化一把 mutex 锁
printf("init!\n");
}
//sem_init(SEM_KEY1);
}
return (void *)buffer_share;
}
共享内存读函数
//内部buffer操作函数,主要是注意处理buffer边界问题,读指针偏移
void ShareBufferRead(unsigned char *destbuf, int len, struct SHARE_BUF_MSG *buffer_msg, unsigned char *shmbuffer)
{
if(destbuf == NULL || buffer_msg == NULL)
return -1;
if((buffer_msg->buffer_size - buffer_msg->readptr) >= len)
{
memcpy(destbuf, shmbuffer+ buffer_msg->readptr,len);
}
else
{
memcpy(destbuf, shmbuffer+buffer_msg->readptr, buffer_msg->buffer_size - buffer_msg->readptr);
memcpy(destbuf+buffer_msg->buffer_size - buffer_msg->readptr, shmbuffer, len-(buffer_msg->buffer_size - buffer_msg->readptr));
}
}
//读函数
int SHARE_BUFFER_read_API(struct SHARE_BUF_MSG *buffer_msg, unsigned char *shmbuffer, unsigned char *data,int datasize) //,unsigned int registerID)
{
unsigned int headsize,len;
SHBUF_mnghead head;
//struct readcortrol *readusr;
headsize = sizeof(SHBUF_mnghead);
if((buffer_msg == NULL)||(data == NULL)||(datasize <=0))
{
printf("buffer_msg == 0x%08x, data == 0x%08x, datasize = %d\n",(unsigned int)buffer_msg,(unsigned int)data,datasize);
return -1;
}
//取锁
//sem_p();
pthread_mutex_lock(&buffer_msg->rw_mutex);
//取共享内存头
ShareBufferRead((unsigned char *)&head, headsize, buffer_msg, shmbuffer);
len = head.packlen;
//读取的数据如果小于一包的数据,不读
if(datasize < len || buffer_msg->valid_size==0 || head.magic != SHBUF_MAGIC_NUM)
{
printf("magic %x, datasize =%d len =%d \n",head.magic,datasize,len);
//sem_v();
pthread_mutex_unlock(&buffer_msg->rw_mutex);
return -1;
}
//更新读指针
buffer_msg->readptr = (buffer_msg->readptr + headsize)% buffer_msg->buffer_size;
ShareBufferRead(data, len, buffer_msg, shmbuffer);
//更新读指针
buffer_msg->readptr = (buffer_msg->readptr + len)% buffer_msg->buffer_size;
//更新可读数量
buffer_msg->valid_size = buffer_msg->valid_size - headsize - len ;
printf("valid_size=%d\n",buffer_msg->valid_size);
//释放锁
//sem_v();
pthread_mutex_unlock(&buffer_msg->rw_mutex);
return len;
}
共享内存写函数
int SHARE_BUFFER_write_API(struct SHARE_BUF_MSG *buffer_msg,unsigned char *shmbuffer,unsigned char *data,unsigned int datasize)
{
unsigned int mngheadsize;
SHBUF_mnghead mnghead;
mngheadsize = sizeof(SHBUF_mnghead);
if((buffer_msg == NULL)||((data == NULL)&&(datasize>0)))
return -1;
//sem_p();
pthread_mutex_lock(&buffer_msg->rw_mutex);
printf("getwrite lock!\n");
if((buffer_msg->valid_size +datasize + mngheadsize) >= buffer_msg->buffer_size)
{
//sem_v();
pthread_mutex_unlock(&buffer_msg->rw_mutex);
printf("release write lock!\n");
return -1;
}
//用于校验共享的数据头
mnghead.magic = SHBUF_MAGIC_NUM;
//一包数据的长度
mnghead.packlen = datasize;
{
//写入共享内存头
if(mngheadsize > (buffer_msg->buffer_size - buffer_msg->writeptr))
{
memcpy(shmbuffer+buffer_msg->writeptr,&mnghead,buffer_msg->buffer_size - buffer_msg->writeptr);
memcpy(shmbuffer,(char *)&mnghead+(buffer_msg->buffer_size - buffer_msg->writeptr),mngheadsize - (buffer_msg->buffer_size - buffer_msg->writeptr));
}
else
{
memcpy(shmbuffer+buffer_msg->writeptr,&mnghead,mngheadsize);
}
//更新写指针
buffer_msg->writeptr = (buffer_msg->writeptr + mngheadsize)% buffer_msg->buffer_size;
}
//拷贝数据
if(datasize!=0)
{
if(datasize>(buffer_msg->buffer_size - buffer_msg->writeptr))
{
memcpy(shmbuffer+buffer_msg->writeptr,data,buffer_msg->buffer_size - buffer_msg->writeptr);
memcpy(shmbuffer,data+(buffer_msg->buffer_size - buffer_msg->writeptr),datasize - (buffer_msg->buffer_size - buffer_msg->writeptr));
}
else
{
memcpy(shmbuffer+buffer_msg->writeptr,data,datasize);
}
buffer_msg->writeptr= (buffer_msg->writeptr + datasize)% buffer_msg->buffer_size;
}
buffer_msg->valid_size = buffer_msg->valid_size + datasize + mngheadsize;
//释放锁
//sem_v();
pthread_mutex_unlock(&buffer_msg->rw_mutex);
printf("write done!\n");
return 0;
}
测试写 write.c
int main()
{
int ret=0;
struct SHARE_BUFFER *shareBuffer = SHARE_BUFFER_Init(BUFFER_SIZE, SHARE_BUF_INFO_KEY, SHARE_BUF_KEY);
if(shareBuffer==NULL)
{
printf("malloc error!\n");
return -1;
}
printf("mallac success!\n");
char data[50]="Never say nothing to do, something worth to try!\n";
while(1)
{
sleep(1);
ret = SHARE_BUFFER_write_API(shareBuffer->msg,shareBuffer->buffer,data,50);
printf("write ret=%d\n",ret);
}
free(shareBuffer);
return 0;
}
测试读 read.c
int main()
{
int ret=0;
struct SHARE_BUFFER *shareBuffer = SHARE_BUFFER_Init(BUFFER_SIZE, SHARE_BUF_INFO_KEY, SHARE_BUF_KEY);
if(shareBuffer==NULL)
{
printf("malloc error!\n");
return -1;
}
printf("mallac success!\n");
char data[50]={0};//"Never say nothing to do,something worth to do!\n";
while(1)
{
sleep(2);
memset(data,0,50);
ret = SHARE_BUFFER_read_API(shareBuffer->msg, shareBuffer->buffer, data,50) ;
printf("read ret=%d\n",ret);
printf("read string:%s\n",data);
}
return 0;
}
先运行读进程,再运行写进程,运行结果:
init!
mallac success!
magic 0, datasize =50 len =0
read ret=-1
read string:
magic 0, datasize =50 len =0
read ret=-1
read string:
magic 0, datasize =50 len =0
read ret=-1
read string:
mallac success!
magic 0, datasize =50 len =0
read ret=-1
read string:
getwrite lock!
write done!
write ret=0
getwrite lock!
write done!
write ret=0
valid_size=58
read ret=50
read string:Never say nothing to do, something worth to try!
getwrite lock!
write done!
write ret=0
getwrite lock!
write done!
write ret=0
valid_size=116
read ret=50
read string:Never say nothing to do, something worth to try!
至此,利用共享内存的非阻塞FIFO已实现。
代码已打包,感兴趣的话可以下载试一下,<^-^>~~
https://download.csdn.net/download/qq_35378417/13090417