实践:实现一个先进先出的共享内存shmfifo

使用消息队列即可实现消息的先进先出(FIFO), 但是使用共享内存实现消息的先进先出则更加快速;

我们首先完成C语言版本的shmfifo(基于过程调用), 然后在此基础上实现C++版本的ShmFifo, 将1块共享内存与3个信号量(1个mutext信号量, 1个full信号量, 1个empty信号量)封装成一个类ShmFifo, 然后编写各自的测试代码;

shmfifo说明:

将申请到的共享内存作为一块缓冲区, 将该内存的首部(p_shm到p_payload的内容)格式化为如上图所示的形式;

读/写进程不断的按照现金先出的原则从其中读出/写入数据, 则读/写进程就可以看成生产者/消费者了, 因此,使用信号量sem_mutex(初值为1)来互斥访问共享内存, 使用sem_full(初值为共享缓冲区块数), sem_empty(初值为0)来同步两个进程.

C版本:

//结构体类型定义
typedef struct shmhead shmhead_t;
typedef struct shmfifo shmfifo_t;

//共享内存首部定义
struct shmhead
{
    unsigned int blksize;   //块大小
    unsigned int blocks;    //总块数
    unsigned int rd_index;  //读索引块
    unsigned int wr_index;  //写索引块
};
//整个shmfifo句柄
struct shmfifo
{
    shmhead_t *p_shm;   //共享内存头部指针
    char *p_payload;    //有效负载其实地址

    int shmid;      //共享内存ID
    int sem_mutex;  //互斥信号量
    int sem_full;   //满信号量
    int sem_empty;  //空信号量
};
/**shmfifo初始化
既包含了共享内存的初始化, 也包含了三个信号量的初始化;
小技巧: 对一个IPC对象首先尝试打开, 如果打开失败, 则表示该IPC对象尚未创建, 则需要创建之, 而如果打开成功的话, 则进行其他操作
**/
shmfifo_t *shmfifo_init(int key, int blksize, int blocks)
{
    shmfifo_t *fifo = (shmfifo_t *)malloc(sizeof(shmfifo_t));
    assert(fifo != NULL);
    memset(fifo, 0, sizeof(shmfifo_t));

    // 尝试打开共享内存
    int shmid = shmget(key, 0, 0);
    // 如果打开失败, 则表示该共享内存尚未创建, 则创建
    if (shmid == -1)
    {
        /** 设置共享内存 **/
        int size = blksize*blocks + sizeof(shmhead_t);
        //创建共享内存
        fifo->shmid = shmget(key, size, IPC_CREAT|0666);
        if (fifo->shmid == -1)
            err_exit("shmget error");

        //创建共享内存成功, 则需要将其连接到进程的地址空间
        //void *shmat(int shmid, const void *shmaddr, int shmflg);
        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
        if (fifo->p_shm == (void *) -1)
            err_exit("shmat error");

        //将共享内存的首部初始化为struct shmhead
        fifo->p_shm->blksize = blksize;
        fifo->p_shm->blocks = blocks;
        fifo->p_shm->rd_index = 0;
        fifo->p_shm->wr_index = 0;

        fifo->p_payload = (char *)(fifo->p_shm+1);

        /** 设置三个信号量 **/
        fifo->sem_mutex = sem_create(key);
        sem_setval(fifo->sem_mutex, 1);

        fifo->sem_full = sem_create(key+1);
        sem_setval(fifo->sem_full, 10);

        fifo->sem_empty = sem_create(key+2);
        sem_setval(fifo->sem_empty, 0);
    }
    else
    {
        fifo->shmid = shmid;
        //共享内存已经存在, 并且打开成功, 则需要将其连接到进程的地址空间
        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
        if (fifo->p_shm == (void *) -1)
            err_exit("shmat error");

        fifo->p_payload = (char *)(fifo->p_shm+1);

        /** 设置三个信号量 **/
        fifo->sem_mutex = sem_open(key);
        fifo->sem_full = sem_open(key+1);
        fifo->sem_empty = sem_open(key+2);
    }

    return fifo;
}
/**shmfifo的销毁
既要销毁共享内存, 也要销毁三个信号量, 还需要将malloc出来的shmfifo_t结构体释放掉
**/
void shmfifo_destroy(shmfifo_t *fifo)
{
    //释放三个信号量
    sem_delete(fifo->sem_mutex);
    sem_delete(fifo->sem_full);
    sem_delete(fifo->sem_empty);
    //分离内存
    shmdt(fifo->p_shm);
    //删除共享内存
    if (shmctl(fifo->shmid, IPC_RMID, NULL) == -1)
        err_exit("remove share memory error");
    //将fifo内存释放
    free(fifo);
}
/**将buf内容按照顺序写入共享内存
注意此处的P,V操作并没有使用SEM_UNDO标记
**/
void shmfifo_put(shmfifo_t *fifo, const void *buf)
{
    sem_P(fifo->sem_full);
    sem_P(fifo->sem_mutex);

    //从结构体中获取写入位置
    char *index = fifo->p_payload +
                  (fifo->p_shm->wr_index * fifo->p_shm->blksize);
    memcpy(index, buf, fifo->p_shm->blksize);
    fifo->p_shm->wr_index = (fifo->p_shm->wr_index+1)%fifo->p_shm->blocks;

    sem_V(fifo->sem_mutex);
    sem_V(fifo->sem_empty);
}
/**将共享内存中的内容按照顺序读出到buf
注意此处的P,V操作并没有使用SEM_UNDO标记
**/
void shmfifo_get(shmfifo_t *fifo, void *buf)
{
    sem_P(fifo->sem_empty);
    sem_P(fifo->sem_mutex);
    //从结构体中获取读出位置
    char *index = fifo->p_payload +
                  (fifo->p_shm->rd_index * fifo->p_shm->blksize);

    memcpy(buf, index, fifo->p_shm->blksize);
    fifo->p_shm->rd_index = (fifo->p_shm->rd_index+1)%fifo->p_shm->blocks;

    sem_V(fifo->sem_mutex);
    sem_V(fifo->sem_full);
}
/**测试代码: write.cpp**/
struct Student
{
    char name[32];
    int age;
};

int main()
{
    shmfifo_t *fifo = shmfifo_init(1234, sizeof(Student), 15);

    Student s;
    bzero(&s, sizeof(s));
    strcpy(s.name, "xiaofang");
    for (int i = 0; i < 15; ++i)
    {
        sprintf(&(s.name[8]), "%d", i);
        s.age = i;
        shmfifo_put(fifo, &s);
        cout << "put success" << endl;
    }
}
/**测试代码: read.cpp**/
int main()
{
    shmfifo_t *fifo = shmfifo_init(1234, sizeof(Student), 3);

    Student s;
    for (int i = 0; i < 5; ++i)
    {
        bzero(&s, sizeof(s));
        shmfifo_get(fifo, &s);
        printf("name: %s, age = %d\n", s.name, s.age);
    }
    return 0;
}
/**测试代码: 销毁所创建的共享内存与信号量, free**/
int main()
{
    shmfifo_t *fifo = shmfifo_init(1234, sizeof(Student), 3);

    shmfifo_destroy(fifo);
    return 0;
}

完整C源代码:http://download.csdn.net/detail/hanqing280441589/8437855

C++版本:

//ShmFifo类设计
class ShmFifo
{
public:
    ShmFifo(int _key, int _blksize, int _blocks);
    ~ShmFifo();
    void put(const void *buf);
    void get(void *buf);
    void destroy();

private:
    typedef struct shmhead
    {
        unsigned int blksize;   //块大小
        unsigned int blocks;    //总块数
        unsigned int rd_index;  //读索引块
        unsigned int wr_index;  //写索引块
    } shmhead_t;

private:
    shmhead_t *p_shm;   //共享内存头部指针
    char *p_payload;    //有效负载其实地址

    int shmid;      //共享内存ID
    int sem_mutex;  //互斥信号量
    int sem_full;   //满信号量
    int sem_empty;  //空信号量
};
/** 构造函数 **/
ShmFifo::ShmFifo(int _key, int _blksize, int _blocks)
{
    // 打开一块共享内存
    shmid = shmget(_key, 0, 0);
    // 如果打开失败, 则表示该共享内存尚未创建, 则创建之
    if (shmid == -1)
    {
        /** 设置共享内存 **/
        int size = _blksize*_blocks + sizeof(shmhead_t);
        //创建共享内存
        shmid = shmget(_key, size, IPC_CREAT|0666);
        if (shmid == -1)
            err_exit("shmget error");

        //创建共享内存成功, 则需要将其连接到进程的地址空间
        p_shm = (shmhead_t *)shmat(shmid, NULL, 0);
        if (p_shm == (void *) -1)
            err_exit("shmat error");

        //将共享内存的首部初始化为struct shmhead
        p_shm->blksize = _blksize;
        p_shm->blocks = _blocks;
        p_shm->rd_index = 0;
        p_shm->wr_index = 0;

        p_payload = (char *)(p_shm+1);

        /** 设置三个信号量 **/
        sem_mutex = sem_create(_key);
        sem_setval(sem_mutex, 1);

        sem_full = sem_create(_key+1);
        sem_setval(sem_full, _blocks);

        sem_empty = sem_create(_key+2);
        sem_setval(sem_empty, 0);
    }
    else
    {
        //共享内存已经存在, 并且打开成功, 则只需需将其连接到进程的地址空间
        p_shm = (shmhead_t *)shmat(shmid, NULL, 0);
        if (p_shm == (void *) -1)
            err_exit("shmat error");

        p_payload = (char *)(p_shm+1);

        /** 打开三个信号量 **/
        sem_mutex = sem_open(_key);
        sem_full = sem_open(_key+1);
        sem_empty = sem_open(_key+2);
    }
}

/** 析构函数 **/
ShmFifo::~ShmFifo()
{
    shmdt(p_shm);   //将共享内存卸载
    p_shm = NULL;
    p_payload = NULL;
}
/** destroy函数 **/
void ShmFifo::destroy()
{
    sem_delete(sem_mutex);
    sem_delete(sem_full);
    sem_delete(sem_empty);
    if (shmctl(shmid, IPC_RMID, NULL) == -1)
        err_exit("remove share memory error");
}
/** put函数 **/
void ShmFifo::put(const void *buf)
{
    sem_P(sem_full);
    sem_P(sem_mutex);

    /** 进入临界区 **/
    //从结构体中获取写入位置
    char *index = p_payload +
                  (p_shm->wr_index * p_shm->blksize);
    //写入
    memcpy(index, buf, p_shm->blksize);
    //index后移
    p_shm->wr_index = (p_shm->wr_index+1)%p_shm->blocks;
    /** 退出临界区 **/

    sem_V(sem_mutex);
    sem_V(sem_empty);
}
/** get函数 **/
void ShmFifo::get(void *buf)
{
    sem_P(sem_empty);
    sem_P(sem_mutex);

    /** 进入临界区 **/
    //从结构体中获取读出位置
    char *index = p_payload +
                  (p_shm->rd_index * p_shm->blksize);
    //读取
    memcpy(buf, index, p_shm->blksize);
    p_shm->rd_index = (p_shm->rd_index+1)%p_shm->blocks;
    /** 退出临界区 **/

    sem_V(sem_mutex);
    sem_V(sem_full);
}

完整C++源代码:http://download.csdn.net/download/hanqing280441589/8438025

附-Makefile, 两个程序都可以使用该文件

.PHONY: clean all
CC = g++
CPPFLAGS = -Wall -g
BIN = write read free
SOURCES = $(BIN.=.cpp)
all: $(BIN)

%.o: %.cpp
	$(CC) $(CPPFLAGS) -c $^ -o $@

write: write.o shmfifo.o ipc.o
	$(CC) $(CPPFLAGS) $^ -lrt -o $@
read: read.o shmfifo.o ipc.o
	$(CC) $(CPPFLAGS) $^ -lrt -o $@

free: free.o shmfifo.o ipc.o
	$(CC) $(CPPFLAGS) $^ -lrt -o $@

clean:
	-rm -rf $(BIN) *.o bin/ obj/ core

Linux IPC实践(13) --System V IPC综合实践的更多相关文章

  1. linux进程间通讯-System V IPC 信号量

    进程间通信的机制--信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的很多其它内容,能够阅读我的还有一篇文章:Linux进程间通信--使用信号.以下就进入信号量的 ...

  2. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  3. 四十九、进程间通信——System V IPC 之消息队列

    49.1 System V IPC 介绍 49.1.1 System V IPC 概述 UNIX 系统存在信号.管道和命名管道等基本进程间通讯机制 System V 引入了三种高级进程间通信机制 消息 ...

  4. 《Unix网络编程》卷2 读书笔记 第3章- System V IPC

    1. 概述 三种类型的System V IPC:System V 消息队列.System V 信号量.System V 共享内存区 System V IPC在访问它们的函数和内核为它们维护的信息上共享 ...

  5. UNIX 进程间通讯(IPC)概念(Posix,System V IPC)

     IPC(Inter-Process Communication,进程间通讯)可以有三种信息共享方式(随文件系统,随内核,随共享内存).(当然这里虽然说是进程间通讯,其实也是可以和线程相通的). 相对 ...

  6. Linux进程通信之System V消息队列

    System V消息队列是Open Group定义的XSI,不属于POSIX标准.System V IPC的历史相对很早,在上个世70年代后期有贝尔实验室的分支机构开发,80年代加入System V的 ...

  7. Linux IPC实践(9) --System V共享内存

    共享内存API #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int ...

  8. Linux IPC实践(6) --System V消息队列(3)

    消息队列综合案例 消息队列实现回射客户/服务器   server进程接收时, 指定msgtyp为0, 从队首不断接收消息 server进程发送时, 将mtype指定为接收到的client进程的pid ...

  9. Linux 系统编程 学习:05-进程间通信2:System V IPC(2)

    Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...

随机推荐

  1. SpringMVC与Zookeeper、Dubbo使用示例

    Dubbo整合Zookeeper和spring示例程序 1.Dubbo架构 本篇文章基于dubbox,使用dubbo应该也可以正常运行. 我认为想讲清楚一个任何一个技术框架,首先熟悉架构是非常有必要的 ...

  2. 18. 4Sum(中等)

    Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = tar ...

  3. Matlab 编译EXE

    环境:vs2013  matlab2015b 一.生成独立可执行的程序(exe文件)步骤1.设置编译器.在matlab命令行输入mbuild –setup以及mex –setup,选择安装的c编译器. ...

  4. DotnetSpider (一) 架构的理解、应用、搭建

    第一次写博客,比较浅显,欢迎大牛们指点一二,不胜感激.   ** 温馨提示:如需转载本文,请注明内容出处.**   本文连接:http://www.cnblogs.com/grom/p/8931650 ...

  5. MacOS下Rails+Nginx+SSL环境的搭建(下)

    五.以Production环境部署Rails项目 这里插一个题外话,我们之前是以development环境运行的rails项目,现在我们希望在实际的生产系统中跑一下看看.这是十分有必要的,应该在rai ...

  6. ROS机器人程序设计(原书第2版)补充资料 (零) 源代码、资料和印刷错误修订等 2017年01月01日更新

    ROS机器人程序设计(原书第2版)补充资料 (零) 源代码等 ROS官网 版)部分内容修订 页:第1行,删去$ 页:第6行,float64 y 前面加一个空格 页:中间创建主题:下面程序不用换行,(& ...

  7. Android双击退出

    重写返回键 private long tempTime = 0; /** * 双击退出 */ @Override public void onBackPressed() { long firstCli ...

  8. SVN与eclipse整合与使用、SVN与Apache整合

    SVN与eclipse整合 下载SVN插件(http://subclipse.tigris.org) http://subclipse.tigris.org/servlets/ProjectDocum ...

  9. Shell脚本生成网页版相册浏览器

    今天学到了一招,那就是使用脚本制作一款网页版相册浏览器.先上图吧. 必备基础 操作系统: 以linux为内核的操作系统都行 编程语言:Shell(bash)脚本,相关基础知识即可 下载工具:wget ...

  10. Dynamics CRM2016 New features in Microsoft Dynamics CRM Online 2015 Update 1 are now available

    很多人看过Dynamics CRM Online 2015 Update 1后,被它新的一个界面风格所吸引,还有它的很多新增功能,虽然官网放出了些补丁,但最重要的Server补丁一直没出,包括我在内很 ...