Linux进程间的循环队列内存共享

上一篇文章我们已经介绍了进程间的内存共享。因为篇幅关系,只是做了简单实现,并不适用于实际应用。因此本篇以实际应用为目的,介绍以循环队列实现的内存共享机制。

该机制可以快速实现数据的先入先出,方便控制内存的大小,减少数据的拷贝次数,且可以方便的选择是数据完整性优先还是实时性优先

Linux进程间的循环队列内存共享_第1张图片

实现的原理非常简单,以数据完整性优先为例。我们可以创建如上所示的共享内存,将前面的几个字节定义为信息交流的空间,写进程会在这边填入共享内存的信息,而读进程会根据这些信息来读取数据。

填入信息之后,写进程一有数据需要写入,便去内存中找到紧挨着的一块还没写入数据的内存(这里以数据的第一位为0表示还没有写入数据,当然也可以是其他的值这个随便定义,而且本人也建议将数据块前面的几个字节作为保留位以方便后面开发会比较妥当)进行写入,把标志位置1然后把指针移动到下一位。直到内存被写满为止,也就是下一位内存的标志位1。

读进程则看到自己指针所指的数据块的标志位为1则去读取数据,读取完成后把标志位置0然后移动到下一位。

移动到数据末尾的时候则回到数据的开头即可。

代码的实现如下:

shmobject.h

/**
**进程间共享内存
**内存的前INFOLEN个数据为信息记录
**需要先打开写内存进程再打开读内存进程
**weixinhum
**2019/04/17
**/

#define INFOLEN 10
class shmobject
{
public:
    int shmid;//共享内存ID
    void *p;//共享内存首地址
    unsigned int *pp;//数据区首地址
    unsigned int *blksize;//块大小
    unsigned int *blocks;//总块数
    unsigned int *rd_index;//读索引
    unsigned int *wr_index;//写索引
    unsigned int *quit;//退出标志位
    unsigned int *exist;//内存存在标志位
    unsigned char *databegin;//数据区开始位置
    unsigned char **shmdata;//共享内存数据块
 
    shmobject(int datalen,int datanum,int shmkey);//写进程构造函数
    shmobject(int shmkey);//读进程构造函数
 
    void writerelease();//写数据对象释放
    void readrelease();//读数据对象释放a
 
    unsigned char* requiredata();//内存要求函数,成功则返回可写内存块指针
    void updatashm(unsigned char *getdata);//数据更新,等待被读程序读入
 
    unsigned char* getdataforread();//内存要求,成功则返回可读内存指针
};
 
//内存要求,成功则返回可读内存指针
unsigned char* shmobject::getdataforread()
{
    unsigned char *readaddr;
    if (*rd_index!=*blocks && shmdata[*rd_index][0]==1)
    {
        readaddr=shmdata[*rd_index];
        *rd_index=*rd_index+1;
    }   
    else if(*rd_index==*blocks && shmdata[0][0]==1)
    {
        *rd_index=0;
        readaddr=shmdata[*rd_index];
        *rd_index=*rd_index+1;
    }
    else
    {
        return NULL;
    }
    return readaddr;
}
 
// 读成员构造函数
shmobject::shmobject(int shmkey)
{
    // 生成一个key
    key_t key = ftok("./", shmkey);
    // 获取共享内存,返回一个id
    shmid = shmget(key, 0, 0);
    if(-1 == shmid)
    {
        perror("shmget failed");
        exit(1);
    }
    // 映射共享内存,得到虚拟地址
    p = shmat(shmid, 0, 0);
    if((void*)-1 == p)
    {
        perror("shmat failed");
        exit(2);
    }
    // 读共享内存
    pp = (unsigned int *)p;
    blksize=pp;//块大小
    blocks=pp+1;//总块数
    rd_index=pp+2;//读索引
    wr_index=pp+3;//写索引
    quit=pp+4;//退出标志位
    exist=pp+5;//内存存在标志位
    printf("块大小%d\n", *blksize);
    printf("总块数%d\n", *blocks);
    printf("读索引%d\n", *rd_index);
    printf("写索引%d\n", *wr_index);
    printf("退出标志位%d\n", *quit);
    printf("内存存在标志位%d\n", *exist);
    databegin=(unsigned char *)(pp+INFOLEN);//数据区开始位置
    shmdata=new unsigned char*[*wr_index];
    for(int i=0;i<*blocks;i++)
    {
        shmdata[i]=databegin+i*(*blksize);
    }
}
 
// 更新写索引
void shmobject::updatashm(unsigned char *getdata)
{
    getdata[0]=1;//可被读取标志位
    *wr_index=*wr_index+1;//更新写入索引
}
 
// 内存要求函数,成功则返回可写内存块指针
unsigned char* shmobject::requiredata()
{
    if(*quit==1)//退出标志位
    {
        printf("已收到销毁命令,不再写内存,请及时销毁\n");
        return NULL;
    }
    //已经写到末尾,等待读线程读完之后往开头写
    if(*wr_index==*blocks)
    {
        if(shmdata[0][0]==0)
        {
            *wr_index=0;
        }
        else
        {
            printf("共享内存已满,请稍候\n");
            return NULL;
        }
    }
    //已经追上读进程,等待读线程读完之后再写
    if(shmdata[*wr_index][0]==1)
    {
        printf("共享内存已满,请稍候\n");
        return NULL;
    }
    return shmdata[*wr_index];
}
 
// 写成员构造函数
shmobject::shmobject(int datalen,int datanum,int shmkey)
{
    // 生成一个key
    key_t key = ftok("./", shmkey);
    // 创建共享内存,返回一个id
    // 第二个参数为共享内存大小,前面四个值分别记录共享内存循环队列的块大小,总块数,写指针索引,读指针索引与退出标志
    while(true)
    {
        shmid = shmget(key, sizeof(int)*INFOLEN+datalen*datanum, IPC_CREAT|0666);//0666是文件权限,不写只有超级用户才能打开
        if(-1 == shmid)
        {
            perror("shmget failed");
            exit(1);
        }
        // 映射共享内存,得到虚拟地址
        p = shmat(shmid, 0, 0);
        if((void*)-1 == p)
        {
            perror("shmat failed");
            exit(2);
        }
        shmdata=new unsigned char*[datanum];//指针数据
        // 共享内存对象映射
        pp = (unsigned int *)p;
        blksize=pp;//块大小
        blocks=pp+1;//总块数
        rd_index=pp+2;//读索引
        wr_index=pp+3;//写索引
        quit=pp+4;//写索引
        exist=pp+5;//内存存在标志位

        if (*exist==99)//原先内存没有销毁,先销毁掉再创建
        {
            printf("原先内存没有销毁,先销毁掉再创建\n");
            if(-1 == shmctl(shmid, IPC_RMID, NULL))
            {
                perror("shmctl failed");
                exit(4);
            }
        }
        else
        {
            break;
        } 
    }

    databegin=(unsigned char *)(pp+INFOLEN);//数据区开始位置
    for(int i=0;i

writedata.cpp

#include
#include
#include
#include
#include
#include
#include
#include
#include"shmobject.h"

int main()
{
    shmobject shmobj(10,10,66);//共享内存对象
    //循环写入数据,并更新索引
    int i=0;
    unsigned char* shmdata=NULL;
    for(int i=0;i<30;i++)
    {
        shmdata=shmobj.requiredata();//要求数据
        if(shmdata!=NULL)
        {
            printf("写入标志%d\n",i);
            shmdata[1]=i;
            shmobj.updatashm(shmdata);//更新数据,可被读取
        }
        usleep(1000000);//休眠500
    }
    shmobj.writerelease();
    return 0;
}

readdata.cpp

#include
#include
#include
#include
#include
#include
#include"shmobject.h"
int main()
{
    shmobject shmobj(66);
    // 循环读取数据,并更新索引
    for(int i=0;i<30;i++)
    {
        unsigned char *readdata= shmobj.getdataforread();
        if(readdata!=NULL)//表示已经获取到可读数据
        {
            printf("读取标志位%d\n",readdata[1]);
            readdata[0]=0;//读取完成标志位
        }
        else
        {
            printf("无可读数据,请等待写入,稍候重试\n");
        }
        usleep(500000);//等待1s
    }
    shmobj.readrelease();
    return 0;
}

 

你可能感兴趣的:(ROS学习,ROS操作系统学习)