共享内存 

  共享内存是内核为进程创建的一个特殊内存段,它将出现在进程自己的地址空间中,其它进程可以将同一段共享内存连接(attach)到自己的地址空间。这是最快的进程间通信方式,但是不提供任何同步功能(需要我们信号量实现)。

  

  使用共享内存实现生产者消费者任务模式。

共享内存系统调用 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semget(key_t key, int size, int flag);
void *shmat(int shmid, void *addr, int flag);
int shmdt(void *addr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmget函数:

  功能:获得或创建一个共享内存标识符。

int semget(key_t key, size_t size, int shmflag);
  • 成功返回一个共享内存标识符,失败返回-1;
  • 第一个参数key为共享内存段命名(一般由ftok产生);
  • 第二个参数size为需要共享的内存容量。(如果共享内存已存在时,不能不大于该共享内存段的大小);
  • 第三个参数设置访问权限(低9位)与IPC_CREAT, IPC_EXCL 的按位或。

shmat函数

  功能:将共享内存段连接到一个进程的地址空间中。

void *shmat(int  shm_id, const  void *addr, int shmflg) ;  
  • 成功返回共享存储段连接的实际地址,失败返回-1
  • 第一个参数shm_id为shmget返回的共享内存标识符。
  • 第二个参数addr指明共享内存段要连接到的地址(进程空间内部地址),通常指定为空指针,表示让系统来选择共享内存在进程地址空间中出现的地址。
  • 第三个参数shmflg可以设置为两个标志位(通常设置为0)
  • SHM_RND( 表示第二个参数指定的地址应被向下靠拢到内存页面大小的整数倍)
  • SHM_RDONLY,要连接的共享内存段是只读的。

shmdt函数

  功能:将共享内存从当前进程中分离。

int shmdt(const  void *shmaddr) ;    //其中shmaddr为shmat返回的地址。

shmctl函数

  功能:查看及修改共享内存段的shmid_ds结构,删除该结构以及相连的共享存储段标识。

int shmctl(int  shm_id, int command, struct shmid_ds *buf) ; 
  • 成功返回0,失败返回-1
  • 第二个参数commad取值:
  • IPC_STAT 获取当前共享内存段的shmid_ds结构
  • IPC_SET 把共享内存段的当前关联值设置为shmid_ds结构给出的值
  • IPC_RMID 从系统中删除该共享存储段。
  • 第三个参数buf是一个结构指针,它指向共享内存模式和访问权限的结构

    struct shmid_ds
    {
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
    };

生产者/消费者模式

  某个模块负责产生数据(生产者),另一个模块来负责处理(消费者)。

生产者任务流程图

消费者任务流程图

为什么要使用生产者消费者模式

  • 解耦 ----如果让生产者直接与消费者交互,那么生产者对于消费者就会产生依赖(也就是耦合);
  • 支持并发----传统方式,在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。使用了生产者/消费者模式之后,生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据;
  • 支持忙闲不均----当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。

实现共享内存的生产者消费者模式 

  编写两个应用程序,其中一个应用程序实现生产者任务,一个应用程序实现消费者任务。
生产者任务和消费者任务之间通过共享内存机制实现跨进程的共享缓冲池;在信号量集合中支持一个信号量(或利用一个POSIX信号量),实现对共享缓冲池的互斥访问;缓冲区的分配计数通过修改缓冲池结构中的计数变量来实现。 

  缓冲池结构:

struct shared_use_st
{
//为0表示对应的缓冲区未被生产者使用,可分配但不可消费;为1表示对应
的缓冲区以被生产者使用,不可分配但可消费 //5个字符串缓冲区
char Buffer[][];
int Index[];
};

  缓冲区的互斥访问,5个缓冲区的缓冲池作为一个临界资源:

  • 当生产者任务从数据源—文件中读取数据后将会申请一个缓冲区,并将此数据放入缓冲区中。
  • 消费者任务从一个缓冲区中取走数据,并将其中的内容打印输出。
  • 当一个生产者任务正在访问缓冲区时,其他生产者和消费者任务不能访问缓冲区
  • 当一个消费者任务正在访问缓冲区时,其他其他生产者和消费者任务不能访问缓冲区
  • 使用信号量集(包含1个信号量,其初始值为1)实现对缓冲池的互斥访问

源码

#Producer.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <fcntl.h>
#define TEXT_SZ 1024 //缓冲池结构
struct shared_use_st
{
int Index[]; //5个缓冲池,为0表示对应的缓冲区未被生产者使用,可分配但不可消费;为1表示对应的缓冲区已被生产者使用,不可分配但可消费
char Buffer[][TEXT_SZ]; //5个字符串缓冲区
sem_t sem; //信号量,同步功能
}; int main()
{
int running = ;
int i = ;
void *shm = NULL; //共享存储段连接的实际地址
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + ]; //缓冲区存放字符
int shmid; //共享内存标识符
//获得或创建一个共享内存标识符
shmid = shmget((key_t), sizeof(struct shared_use_st), |IPC_CREAT);
if(shmid == -) //获取或创建一个共享内存标识符失败
{
exit(EXIT_FAILURE);
}
shm = shmat(shmid, (void*), ); //返回共享存储段连接的实际地址
if(shm == (void*)-)
{
exit(EXIT_FAILURE);
}
printf("Memory attached at %ld\n", (intptr_t)shm);
shared = (struct shared_use_st*)shm; //缓冲池为共享存储段连接地址
for( ; i < ; i++ )
{
shared->Index[i] = ; //对缓冲池初始化,Index为0表示可以生产
}
sem_init(&(shared->sem),,); //信号量化初始化,且信号量初始值为第二个1
i = ;
while(running) //制造一个循环
{
if(sem_wait(&(shared->sem)) == -) //sem_wait为P操作,减少信号量的值
{
printf("P操作 ERROR!\n");
exit(EXIT_FAILURE);
}
for(i = ; i < && shared->Index[i] == ; i++)
;
if(i == ) //Index为1表示缓冲池被消费者占用
{
//当五个空间都被消费者占用时输出“waiting...”
sem_post(&shared->sem); //sem_post为V操作,用来增加信号量的值
sleep(); //sleep一段时间,再次进入循环
printf("Waiting for some time...\n");
}
else
{
sem_post(&shared->sem); //V 操作增加信号量
printf("Enter some text with keyboard: ");
fgets(buffer, BUFSIZ, stdin); //读取stdin字符流最多BUFSIZ-1个,并存在buffer数组中 其中stdin是键盘输入到缓冲区的字符
strncpy(shared->Buffer[i%], buffer,TEXT_SZ); //读取的字符串存入缓冲区shared->Buffer中
shared->Index[i%] = ; //表示该缓冲区被生产者使用了
if(strncmp(buffer, "end", ) == ) //缓冲区的字符为end时,结束循环
{
running = ;
}
}
}
//将共享内存从当前进程中分离
if(shmdt(shm) == -) //失败
{
exit(EXIT_FAILURE);
}
/*查看及修改共享内存段的shmid_ds结构,删除该结构以及相连的共享存储段标识
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
*/
if(shmctl(shmid, IPC_RMID, ) == -) //失败
{
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
#Consumer.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <fcntl.h>
#define TEXT_SZ 1024 //缓冲池结构
struct shared_use_st
{
int Index[]; //5个缓冲池,为0表示对应的缓冲区未被生产者使用,可分配但不可消费;为1表示对应的缓冲区被生产者使用,不可分配但可消费
char Buffer[][TEXT_SZ]; //5个字符串缓冲区
sem_t sem; //信号量,同步功能
}; int main()
{
int running = ;
int i = ;
void *shm = NULL; //共享存储段连接的实际地址
struct shared_use_st *shared = NULL; //缓冲池
int shmid; //声明共享内存标识符 shmid = shmget((key_t), sizeof(struct shared_use_st), |IPC_CREAT); //获得或创建一个共享内存标识符
if(shmid == -) //获取或创建一个共享内存标识符失败
{
exit(EXIT_FAILURE);
} //将共享内存段连接到一个进程的地址空间中,返回void *指针
shm = shmat(shmid, , ); //返回共享存储段连接的实际地址
if(shm == (void*)-) //失败
{
exit(EXIT_FAILURE);
}
printf("Memory attached at %ld\n", (intptr_t)shm);
shared = (struct shared_use_st*)shm; //缓冲池为共享存储段连接地址
while(running)
{
if(sem_wait(&(shared->sem)) == -) //sem_wait为P操作,减少信号量的值
{
printf("P操作 ERROR!\n");
exit(EXIT_FAILURE);
}
for(i = ; i < && shared->Index[i] == ; i++)
;
//五个缓冲区没有都被生产者占用
if(i != )
{
printf("You wrote: %s\n", shared->Buffer[i%]); //打印出生产者写入的字符
shared->Index[i%] = ; //为0时,表示已被消费者使用
sem_post(&shared->sem); //sem_post为V操作
sleep();
if( strncmp(shared->Buffer[i%], "end", ) == ) //缓冲区的字符为end时,结束循环
{
running= ;
}
}
//五个空间都被占用,输出waiting...
else
{
sem_post(&shared->sem); //V操作
sleep();
printf("Waiting for some time...\n");
}
}
//将共享内存从当前进程中分离
if(shmdt(shm) == -) //分离失败
{
exit(EXIT_FAILURE);
}
if(shmctl(shmid, IPC_RMID, ) == -) //失败
{
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}

Linux进程通信之共享内存实现生产者/消费者模式的更多相关文章

  1. linux 进程通信之 共享内存

    共享内存是被多个进程共享的一部分物理内存.共享内存是进程间共享数据的一种最快的方法.一个进程向共享内存区域写入了数据,共享这个内存区域的全部进程就能够立马看到当中的内容. 关于共享内存使用的API k ...

  2. Linux 进程通信(共享内存区)

    共享内存是由内核出于在多个进程间交换信息的目的而留出的一块内存区(段). 如果段的权限设置恰当,每个要访问该段内存的进程都可以把它映像到自己的私有地址空间中. 如果一个进程更新了段中的数据,其他进程也 ...

  3. linux进程通信之共享内存

    共享内存同意两个或多个进程共享一给定的存储区,由于数据不须要来回复制,所以是最快的一种进程间通信机制.共享内存能够通过mmap()映射普通文件(特殊情况下还能够採用匿名映射)机制实现,也能够通过系统V ...

  4. linux 进程学习笔记-共享内存

    如果能划定一块物理内存,让多个进程都能将该内存映射到其自身虚拟内存空间的话,那么进程可以通过向这块内存空间读写数据而达到通信的目的.另外,和消息队列不同的是,共享的内存在用户空间而不是核空间,那么就不 ...

  5. python进阶:Python进程、线程、队列、生产者/消费者模式、协程

    一.进程和线程的基本理解 1.进程 程序是由指令和数据组成的,编译为二进制格式后在硬盘存储,程序启动的过程是将二进制数据加载进内存,这个启动了的程序就称作进程(可简单理解为进行中的程序).例如打开一个 ...

  6. Linux 进程通信之:内存共享(Shared Memory)(转,好文章)

    https://blog.csdn.net/afei__/article/details/84188548

  7. Linux进程通信之System V共享内存

    前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...

  8. linux进程间的通信之 共享内存

    一.共享内存介绍 共享内存是三个IPC(Inter-Process Communication)机制中的一个. 它允许两个不相关的进程访问同一个逻辑内存. 共享内存是在两个正在进行的进程之间传递数据的 ...

  9. 撸代码--linux进程通信(基于共享内存)

    1.实现亲缘关系进程的通信,父写子读 思路分析:1)首先我们须要创建一个共享内存. 2)父子进程的创建要用到fork函数.fork函数创建后,两个进程分别独立的执行. 3)父进程完毕写的内容.同一时候 ...

随机推荐

  1. C语言提高代码效率的几种方法

    一段完美的代码不仅在于找到一个给定的问题的解决方案,但在它的简单性,有效性,紧凑性和效率(内存).设计的代码比实际执行更难.因此,每一个程序员当用C语言开发时,都应该保持这些基本的东西在头脑中.本文向 ...

  2. iOS开发 - 线程与进程的认识与理解

    进程: 进程是指在系统中正在运行的一个应用程序,比如同时打开微信和Xcode,系统会分别启动2个进程; 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内; 线程: 一个进程要想执行任务 ...

  3. cmd - 命令行窗口中文乱码

    问题 在cmd窗口中输入curl www.baidu.com可以看到有中文乱码的现象,这是因为默认使用的是GBK编码.另外,curl是利用URL语法在命令行方式下工作的开源文件传输工具.它被广泛应用在 ...

  4. PostgreSQL-14-异常值处理

    -- 查看异常值CREATE TABLE outerdata(id int PRIMARY KEY,value numeric); \COPY outerdata FROM 'C:\Users\iHJ ...

  5. bryce1010专题训练——Splay树

    Prob Hint BZOJ 3323 文艺平衡树 区间翻转 BZOJ 1251 序列终结者 区间翻转,询问最值 BZOJ 1895 supermemo 区间加,翻转,剪切,询问最值.点插入,删除. ...

  6. python之生成器(~函数,列表推导式,生成器表达式)

    一.生成器 概念:生成器的是实质就是迭代器 1.生成器的贴点和迭代器一样,取值方式也和迭代器一样. 2.生成器一般由生成器函数或者声称其表达式来创建,生成器其实就是手写的迭代器. 3.在python中 ...

  7. c/c++学习系列之内存对齐

    1.C++内存对齐规则 每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数).程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你 ...

  8. Windows下打开某些软件时显示显卡驱动不是最新的问题

    在Windows下打开某些对显卡要求比较高的软件时,会出现某些显卡驱动不是最新,要求更新到最新的提示,但是当你真的去更新显卡驱动的时候,却发现现在的显卡驱动已经是最新了,那么为什么还会有这样的提示呢, ...

  9. hdu4578Transformation(线段树多个lz标记)

    这里以3次方来举例讲一下这题的做法,其它维类似. 如果要求某一个值的3次方那么sum = t^3,设t = x+y.那也就是sum = (x+y)^3. 假如我让每个数都加z t = x+y+z,我可 ...

  10. 将GitLab上面的代码克隆到本地

    1.安装GitLab客户端 2.去GitLab服务端找项目路径 3.去GitLab客户端去克隆代码 右键-->git Clone 4.最后结果