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)父进程完毕写的内容.同一时候 ...
随机推荐
- java面试基础问题
1.一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致. 2.Java有 ...
- react native 安卓打包--mac环境,如果打包不成功可注意下my-release-key.keystore的位置关系(绝对路径)
// my-release-key.keystore和my-key-alias都是可修改的名称 1.生成签名密钥(keytool -genkey -v -keystore my-release-key ...
- 干货:排名前16的Java工具类
在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取的5万个开源项目源码. 一. ...
- Info.plist权限设置
<!-- 相册 --> <key>NSPhotoLibraryUsageDescription</key> <string>App需要您的同意,才能访问 ...
- UWP 播放媒体控件
最近我的uwp需要有一个有声朗读的功能,like this 点击声音按钮就可以有声朗读了.这里主要是用了媒体播放的控件. 一般我们把需求分为两种: 一种是不需要呈现播放器的样子,只需要用户点击一下别的 ...
- 【bzoj1123】BLO
1123: [POI2008]BLO Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 2222 Solved: 1090[Submit][Status ...
- Gym - 101810C ACM International Collegiate Programming Contest (2018)
bryce1010模板 http://codeforces.com/gym/101810 #include <bits/stdc++.h> using namespace std; #de ...
- Java EE学习笔记(五)
Spring事务管理 1.Spring事务管理概述 1).在实际开发中,操作数据库时都会涉及到事务管理问题,为此Spring提供了专门用于事务处理的API.(事务特性:ACID,原子性,一致性,隔离性 ...
- MODBUS移植的参考文章
https://github.com/armink/FreeModbus_Slave-Master-RTT-STM32 http://www.360doc.com/content/14/0906/09 ...
- myBatis-类型关联
1.一对多 collection <resultMap id="deptsql" type="Dept"> <id column=" ...