Linux进程通信之共享内存实现生产者/消费者模式
共享内存
共享内存是内核为进程创建的一个特殊内存段,它将出现在进程自己的地址空间中,其它进程可以将同一段共享内存连接(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进程通信之共享内存实现生产者/消费者模式的更多相关文章
- linux 进程通信之 共享内存
共享内存是被多个进程共享的一部分物理内存.共享内存是进程间共享数据的一种最快的方法.一个进程向共享内存区域写入了数据,共享这个内存区域的全部进程就能够立马看到当中的内容. 关于共享内存使用的API k ...
- Linux 进程通信(共享内存区)
共享内存是由内核出于在多个进程间交换信息的目的而留出的一块内存区(段). 如果段的权限设置恰当,每个要访问该段内存的进程都可以把它映像到自己的私有地址空间中. 如果一个进程更新了段中的数据,其他进程也 ...
- linux进程通信之共享内存
共享内存同意两个或多个进程共享一给定的存储区,由于数据不须要来回复制,所以是最快的一种进程间通信机制.共享内存能够通过mmap()映射普通文件(特殊情况下还能够採用匿名映射)机制实现,也能够通过系统V ...
- linux 进程学习笔记-共享内存
如果能划定一块物理内存,让多个进程都能将该内存映射到其自身虚拟内存空间的话,那么进程可以通过向这块内存空间读写数据而达到通信的目的.另外,和消息队列不同的是,共享的内存在用户空间而不是核空间,那么就不 ...
- python进阶:Python进程、线程、队列、生产者/消费者模式、协程
一.进程和线程的基本理解 1.进程 程序是由指令和数据组成的,编译为二进制格式后在硬盘存储,程序启动的过程是将二进制数据加载进内存,这个启动了的程序就称作进程(可简单理解为进行中的程序).例如打开一个 ...
- Linux 进程通信之:内存共享(Shared Memory)(转,好文章)
https://blog.csdn.net/afei__/article/details/84188548
- Linux进程通信之System V共享内存
前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...
- linux进程间的通信之 共享内存
一.共享内存介绍 共享内存是三个IPC(Inter-Process Communication)机制中的一个. 它允许两个不相关的进程访问同一个逻辑内存. 共享内存是在两个正在进行的进程之间传递数据的 ...
- 撸代码--linux进程通信(基于共享内存)
1.实现亲缘关系进程的通信,父写子读 思路分析:1)首先我们须要创建一个共享内存. 2)父子进程的创建要用到fork函数.fork函数创建后,两个进程分别独立的执行. 3)父进程完毕写的内容.同一时候 ...
随机推荐
- Windows下SVN的下载、安装
SVN全称Subversion,是一款优秀的版本控制工具. SVN分为服务器.客户端两部分,服务器和客户端版本很多,有的是收费的,有的需要注册.我们选择免费.不用注册的. 以VisualSVN作为服务 ...
- go系列(2)- go框架beego以及命令bee的使用
上篇写了go的安装和GOPATH的配置,linux下go的安装 ,现在就看看如何用框架. 1.进入GOPATH的目录 cd /data/work/go 2.下载beego,通过go get go ge ...
- python 基础(九) 文件操作
文件操作 一.函数: f = open(’文件名','打开方式'[,encoding='字符编码']) open 打开的方式 字符 说明 r 只读的方式打开 rb 以二进制的形式打开文件 只读 r+ ...
- HDU1087(树状数组求LIS)
题是水题,学习一下用树状数组求LIS. 先离散化一下,注意去重:然后就把a[i]作为下标,dp[i]作为值,max作为维护的运算插进树状数组即可. 如果是上升子序列,询问(a[i] - 1):如果是不 ...
- ZJOI2017 day2 T2 线段树 想法题
考完D2发现自己简直zz了...花式扔基本分 首先这道题有个显然的套路:树上一些点到一个定点的距离和=这些点深度和+点数*定点深度和-2*lca深度和 ——上一次见这个套路是LNOI2014,上次做的 ...
- 洛谷 P1463 [HAOI2007]反素数
https://www.luogu.org/problemnew/show/P1463 注意到答案就是要求1-n中约数最多的那个数(约数个数相同的取较小的) 根据约数个数的公式,在约数个数相同的情况下 ...
- (转)MySql的常用的语句
MySQL常用语句 一.连接MySQL 格式: mysql -h 主机地址 -u 用户名 -p 用户密码 1.例1:连接到本机上的MYSQL. 首先在打开DOS窗口,然后进入目录 mysql bin, ...
- MVC dropdownlist 后端设置select属性后前端依然不能默认选中的解决方法
-----------------------------------来自网上的解决方法--------------------------------------------- ASP.Net MV ...
- 使用express4.x版、Jade模板以及mysql重写《nodejs开发指南》微博实例
最近阅读<nodejs开发指南>一书,书是不错的,然而其微博代码示例用的是express3.x,用些过时了,运行代码出现不少bug(我电脑安的是express4.x),于是用express ...
- tar.gz
tar.gz,或者.tgz的文件一般是在UNIX下用tar和gunzip压缩的文件.可能的文件名还有.tar.gz等.gunzip是一种比pkzip压缩比高的压缩程序,一般 UNIX下都有.tar是一 ...