Linux 进程间通信 System V系列: 共享内存,信号量,简单介绍消息队列

进程间通信 System V系列: 共享内存,初识信号量

  • 一.共享内存
    • 1.引入
    • 2.原理
    • 3.系统调用接口
      • 1.shmget
      • 2.shmat和shmdt
      • 3.shmctl
    • 4.边写代码边了解共享内存的特性
      • 1.ftok形成key,shmget创建与获取共享内存
      • 2.shm相关指令
      • 3.shmat和shmdt挂接和取消挂接
      • 4.shmctl获取共享内存信息,释放共享内存
      • 5.开始通信
    • 5.利用管道实现共享内存的协同机制
      • 1.Sync(同步类)
      • 2.读写端的修改
      • 3.动图演示
    • 6.共享内存的优缺点
  • 二.消息队列
    • 1.概念
    • 2.接口,数据结构等等
  • 三.信号量理论
    • 1.信号量的原理
    • 2.信号量的理论
      • 1.从生活中的例子理解信号量
      • 2.进程角度的信号量
      • 3.信号量的细节
        • 1.信号量必须要由OS提供并维护
        • 2.信号量的基本操作
    • 3.信号量的接口
      • 1.semget
      • 2.semctl
      • 3.semop
  • 四.System V系列的进程间通信的小总结
  • 五.利用信号量实现共享内存的协同机制
    • 1.思路
    • 2.Server创建并获取信号量,Client获取信号量 -> ftok和semget
      • 1.ftok
      • 2.shmget
    • 3.Server阻塞申请信号量资源 - semop
    • 4.Client初始化信号量资源 - semctl
    • 5.Server释放信号量资源 - semctl
    • 6.完整代码
      • 1.Common.hpp
      • 2.sem.hpp
      • 3.ShmServer.cpp
      • 4.ShmClient.cpp
    • 7.演示

我们不是都有管道了吗?为什么还要有其他的进程间通信方式呢?
当时的年代,通信技术是一个非常火的点,就像现在人工智能和各种大模型一样,类似于百家争鸣的样子,所以有很多进程间通信的方式

因为共享内存跟我们学的进程地址空间有密切联系,所以我们重点学习
而信号量我们就先认识一下,学习一下理论即可

一.共享内存

1.引入

管道方便是方便,直接复用文件接口即可,但是想要使用管道是需要访问内核的,而且管道的内核级缓冲区也是在内核当中的,因此会导致效率不是特别好(因为访问内核本身就是一个比较大的消耗)

那么有没有什么办法能够让两个进程无需访问内核就能进行进程间通信呢?
在这里插入图片描述

2.原理

跟命名管道一样,共享内存也是允许完全无关的两个进程商量一下一起使用同一份资源,从而实现进程间通信的
在这里插入图片描述
看似很好懂,但是有几个值得关注的点:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.系统调用接口

shm就是shared_memory:共享内存
ipc: InterProcess Communication:进程间通信

1.shmget

在这里插入图片描述
第三个参数为什么要这么设计呢?
我们一起来分析一下
在这里插入图片描述
我们刚才还没有说返回值
在这里插入图片描述

2.shmat和shmdt

分配完一个共享内存了,下面要做的事情就是把共享内存映射到进程的进程地址空间当中,并用页表建立该映射

shmat:shmattach是负责建立映射的,也就是将共享内存和进程挂接起来
shmdt:shmdetach(detach是分离,拆卸的意思),也就是取消该共享内存跟进程的挂接关系
在这里插入图片描述
shmdt直接传入shmat的返回值即可
在这里插入图片描述
shmat:如果挂接失败,返回(void*)-1

3.shmctl

在这里插入图片描述

4.边写代码边了解共享内存的特性

1.ftok形成key,shmget创建与获取共享内存

在这里插入图片描述
在这里插入图片描述
下面我们应该是要使用shmat和shmdt了,不过在此之前,我们还要介绍几个指令

2.shm相关指令

在这里插入图片描述
如何释放呢?
可以通过shmctl系统调用接口来释放,也可以通过指令来释放
我们先介绍指令释放
在这里插入图片描述
这里的key显示的是16进制,我们刚才打印的是10进制
因此我们改一下代码,让它以16进制打印
在这里插入图片描述

3.shmat和shmdt挂接和取消挂接

在这里插入图片描述
在这里插入图片描述

while :;do ipcs -m;sleep 1;done
这里反过滤掉了root创建的共享内存
while :;do ipcs -m | grep -v root;sleep 1;done

在这里插入图片描述
我们看到了挂接和取消挂接的全过程

4.shmctl获取共享内存信息,释放共享内存

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们实现了共享内存的创建/获取,挂接,取消挂接和释放
下面是时候开始让这两个进程开始通信了
在挂接之后,取消挂接之前开始通信

5.开始通信

我们刚才获取信息只是为了告诉大家这个函数有这么个功能而已,因此我们就不调用这个获取信息的函数了哦
在这里插入图片描述
在这里插入图片描述
通信成功
在这里插入图片描述
那么没有协同机制怎么办?
一个很好的方法是借助信号量来解决这一问题,但是因为信号量的接口太麻烦(比共享内存的这些接口还要麻烦很多),因此我们以后详细介绍信号量的时候再去使用信号量的接口

要不然是像我刚才那样通信双方约定好一个暗号,读端读到暗号时意味着通信结束
而是那样只能解决一部分情况下保证读端读取完所有的写端数据时才退出
还是无法解决写端还没写入你读端就开始读了啊

我们可以利用天然具有协同机制的管道啊!!
又因为我们这两个进程是没有血缘关系的,因此我们用一下命名管道吧
这里直接把我们之前写的管理命名管道的代码拿过来

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#include <string>
using namespace std;
const char* path="./namedpipe";
#define MODE 0666
class Fifo
{
public:Fifo(const char* path):_path(path)//用成员变量保存路径{int ret=mkfifo(_path.c_str(),MODE);if(ret==-1)//说明创建失败{cerr<<"create namedpipe fail, errno: "<<errno<<" , strerror: "<<strerror(errno)<<endl;}else{cout<<"create namedpipe succeed"<<endl;}}~Fifo(){unlink(_path.c_str());}
private:string _path;
};

5.利用管道实现共享内存的协同机制

1.Sync(同步类)

在这里插入图片描述
在这里插入图片描述

2.读写端的修改

在这里插入图片描述
在这里插入图片描述

3.动图演示

在这里插入图片描述
成功解决了写端没写数据,读端还读的问题

6.共享内存的优缺点

在这里插入图片描述
下面我们趁热打铁快速了解一下消息队列

二.消息队列

1.概念

在这里插入图片描述
消息队列的生命周期也是随内核的,跟共享内存一样

2.接口,数据结构等等

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三.信号量理论

1.信号量的原理

这里介绍信号量的原理,
一方面是为了让我们更好地理解信号量,一方面是先提出一些多线程当中的概念

在这里插入图片描述

2.信号量的理论

1.从生活中的例子理解信号量

在这里插入图片描述

2.进程角度的信号量

在这里插入图片描述

3.信号量的细节

1.信号量必须要由OS提供并维护

在这里插入图片描述

2.信号量的基本操作

在这里插入图片描述

3.信号量的接口

1.semget

在这里插入图片描述

2.semctl

在这里插入图片描述

3.semop

在这里插入图片描述
关于信号量的更多话题我们等到多线程的时候还会再说明的

四.System V系列的进程间通信的小总结

共享内存,消息队列和信号量的很多接口都是相同的,它们的内核数据结构当中也都有一个一样的结构体:ipc_perm,
它们都是主要应用于本地通信的,因此在目前的网络时代当中并不常用(用的更多的还是网络通信)

它们都属于System V系列的进程间通信,OS为了管理它们搞了一个
ipc_ids结构体,ipc_id_ary结构体,kern_ipc_perm结构体实现了ipc资源的动态申请和释放,并将对ipc资源的管理转换为了对kern_ipc_perm的增删查改和对ipc_id_ary的动态扩容
在这里插入图片描述
在这里插入图片描述
不过因为System V系列的进程间通信的结构和数据结构都是独立设计的,跟文件是完全解耦的,因此不符合Linux一切皆文件的设计思想,这也是System V系列的进程间通信并不热门的原因

如果OS能够在struct file当中封装一个ipc_perm的指针,把kern_ipc_perm关联起来,并利用文件接口封装ipc资源使用的接口,就能让System V系列的进程间通信符合一切皆文件

那样的话使用起来肯定也就更容易,肯定就能热门了

五.利用信号量实现共享内存的协同机制

本来想写完前4点就结束吧,不过心血来潮想用一下信号量,下面我们一起来用一下信号量吧

1.思路

依旧是回归我们之前需要利用管道实现共享内存的协同机制的时候
我们的目的是让读端一开始阻塞等待,等到写端准备要进行写入的时候告诉读端: 我要开始写啦,你也开始读吧

此时就能够保证读端不会在一开始的时候做无意义的读取操作

大致流程分为:

  1. 读端(Server)创建并获取信号量
  2. Server阻塞申请信号量资源,此时读端就是阻塞等待写端进行写入
  3. Client获取读端创建好的信号量
  4. 写端(Client)准备写入时初始化信号量资源
  5. Server成功申请信号量资源,开始进程间通信
  6. 最后Server释放信号量资源

流程很清晰,
(那为什么我们一开始不用信号量呢? 因为信号量接口太麻烦了…,而且我用管道和信号量来解决这一共享内存的同步机制是为了学习熟悉这些接口)

之前有一些点我没有注意到,写代码的时候屡屡碰壁,最后才搞过来了

2.Server创建并获取信号量,Client获取信号量 -> ftok和semget

1.ftok

在这里插入图片描述

  1. ftok里面传入的路径必须是我们Linux系统中的确存在的路径!!!
  2. 我们申请的共享内存和信号量各自的key是不可以相同的(大家也能够很好的理解,因为key才是ipc资源的唯一标识嘛)

我们就用这个产生sem的key
在这里插入图片描述
用这个产生shm的key
在这里插入图片描述

2.shmget

在这里插入图片描述
我们共享内存的资源就是一个整体,因此nsems传入1
然后跟共享内存一样,Server传IPC_CREAT | IPC_EXCL | 0666, Client传入IPC_CREAT即可
在这里插入图片描述
Server:
在这里插入图片描述
Client:
在这里插入图片描述

3.Server阻塞申请信号量资源 - semop

在这里插入图片描述
当sem_op<0即需要申请信号量时,如果信号量==0,那么该进程就会阻塞,等待信号量>0
而信号量在还没有被进程设置之前默认值是0,因此我们可以这样来玩
(注意:

  1. semget时传入的nsems时你申请的这个信号量集当中的信号量数目,而不是信号量的初始值!!
  2. 初始值需要进程显式传入,而且默认值是0[我就是因为这点屡屡碰壁]
  3. sembuf是本来就有的,不需要我们显式提供[我就是因为这点屡屡碰壁]
    )
    Server不设置信号量,在读取之前申请信号量资源阻塞等待写端进行写入(我们起名为lock函数)
    Client即将进行写入之前初始化该信号量为1(我们起名为Unlock函数),此时Server等待成功,退出阻塞状态,开始进行读取操作
    在这里插入图片描述

4.Client初始化信号量资源 - semctl

在这里插入图片描述
在这里插入图片描述

5.Server释放信号量资源 - semctl

在这里插入图片描述

6.完整代码

1.Common.hpp

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;const char* shm_path="/home/wzs/ubuntucode/process_ipc/semaphore";
const int shm_id=0x5678;
const int agreeSize=4096;key_t GetKey(const char* k_path,int proj_id)
{key_t ret=ftok(k_path,proj_id);if(ret==-1){cout<<"ftok fail"<<endl;exit(1);}return ret;
}class Shm
{
public:int GetShmid(int key,int size,int shmflg){int shmid=shmget(key,size,shmflg);if(shmid==-1){cout<<"shmget fail"<<endl;exit(1);}return shmid;}void* Attach(int shmid){void* addr=shmat(shmid,nullptr,0);if(addr==(void*)-1){cout<<"shmat fail"<<endl;exit(1);}return addr;}void Detach(void* addr){shmdt(addr);}void DelShm(int shmid){shmctl(shmid,IPC_RMID,nullptr);}
};

2.sem.hpp

#pragma once
#include <sys/sem.h>
const char* sem_path="/home/wzs/ubuntucode/process_ipc/pipe/namepipe/name_pipepool";
const int sem_id=0x3f45289;union semun {  int val;                /* 用于SETVAL */  struct semid_ds *buf;  /* 用于IPC_STAT和IPC_SET */  unsigned short *array; /* 用于GETALL和SETALL */  
};void ChangeCount(sembuf* buf,int val)
{buf->sem_num=0;buf->sem_op=val;buf->sem_flg=SEM_UNDO;
}class Sem
{
public:int GetSemid(key_t key,int nsems,int semflg){int semid=semget(key,nsems,semflg);if(semid==-1){cout<<"semget fail"<<endl;exit(1);}return semid;}void DelSem(int semid){semctl(semid,0,IPC_RMID);}void Change(int semid,sembuf* sops){cout<<"wait sem resource..."<<endl;semop(semid,sops,1);cout<<"wait success!"<<endl;}void GetInfo(int semid){int val=semctl(semid,0,GETVAL);cout<<val<<endl;}void Init(int semid,semun un){semctl(semid,0,SETVAL,un);}void Unlock(int semid){union semun sem_union;sem_union.val=1;//将信号量的初始值设置为1,此时相当于开锁,读端可以拿到信号量,开始读取Init(semid,sem_union);}void lock(int semid){sembuf buf;ChangeCount(&buf,-1);Change(semid,&buf);//我想申请信号量,但是信号量默认是0,我需要阻塞等待}
};

3.ShmServer.cpp

#include "Common.hpp"
#include "sem.hpp"
int main()
{Shm shm;key_t sem_key=GetKey(shm_path,shm_id);//获取Keyint shmid=shm.GetShmid(sem_key,agreeSize,IPC_CREAT | IPC_EXCL | 0666);//申请shmchar* addr=(char*)shm.Attach(shmid);//挂接shm//利用二元信号量(锁)Sem sem;key_t k=GetKey(sem_path,sem_id);int semid=sem.GetSemid(k,1,IPC_CREAT | IPC_EXCL | 0666);//等待获取锁sem.lock(semid);cout<<"receive message begin########################################"<<endl;//开始读取while(true){if(addr[0]=='q') break;cout<<"this is message:"<<addr<<"。"<<endl;sleep(1);}cout<<"receive message over#########################################"<<endl;cout<<"Server will detach shm now..."<<endl;shm.Detach(addr);//解除挂接shm.DelShm(shmid);//删除shmsem.DelSem(semid);//删除semreturn 0;
}

4.ShmClient.cpp

#include "Common.hpp"
#include "sem.hpp"
int main()
{Shm shm;key_t sem_key=GetKey(shm_path,shm_id);int shmid=shm.GetShmid(sem_key,agreeSize,IPC_CREAT);Sem sem;key_t k=GetKey(sem_path,sem_id);int semid=sem.GetSemid(k,1,IPC_CREAT);char* addr=(char*)shm.Attach(shmid);memset(addr,0,agreeSize);cout<<"send message begin########################################"<<endl;//开锁sem.Unlock(semid);for(char c='A';c<='Z';c++){addr[c-'A']=c;sleep(1);}addr[0]='q';cout<<"send message over########################################"<<endl;cout<<"Client will detach shm now..."<<endl;shm.Detach(addr);return 0;
}

7.演示

在这里插入图片描述

以上就是Linux 进程间通信 System V系列: 共享内存,信号量,简单介绍消息队列的全部内容,希望能对大家有所帮助!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3030991.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

判断字符是否唯一——力扣

面试题 01.01. 判定字符是否唯一 已解答 简单 相关标签 相关企业 提示 实现一个算法&#xff0c;确定一个字符串 s 的所有字符是否全都不同。 示例 1&#xff1a; 输入: s "leetcode" 输出: false 示例 2&#xff1a; 输入: s "abc" 输出: true…

Vue项目npm install certificate has expired报错解决方法

1.Vue项目 npm install 安装依赖突然报错&#xff1a; npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/zrender/download/zrender-4.3.0.tgz failed, reason: certificate has expired npm ERR! A com…

Xilinx 千兆以太网TEMAC IP核简介

Xilinx 公司提供了千兆以太网MAC控制器的可参数化LogiCORET™IP解决方案&#xff0c;通过这个IPCore可以实现FPGA与外部网络物理层芯片的互连。基于Xilinx FPGA 的以太网设计&#xff0c;大大降低了工程的设计复杂度&#xff0c;缩短了开发周期&#xff0c;加快了产品的面市速度…

金南瓜EAP库使用开发

前言 最近做了 一个半导体公司的上位机开发。厂商要求要支持EAP通讯。 先了解一下EAP是什么吧&#xff1f;百度资料 EAP&#xff08; Equipment Automation Program&#xff09;设备自动化处理&#xff0c;工厂实现设备自动化生产和管理。 1. 机台状态数据收集&#xff0c;包…

网络编程——Socket——模拟用户登录

功能一&#xff1a;模拟用户登录 功能二&#xff1a;实现客户发送登录用户信息&#xff0c;服务器端显示登录信息并响应给客户端登录成功 这里设置的用户登录信息为&#xff1a;admin&#xff0c;123456 实现&#xff1a; 1.首先&#xff0c;服务端创建并启动服务器&#x…

JINGWHALE 虚拟现实物质与空间理论 —— 全息世界

JINGWHALE 对此论文相关未知以及已知概念、定理、公式、图片等内容的感悟、分析、创新、创造等拥有作品著作权。未经 JINGWHALE 授权&#xff0c;禁止转载与商业使用。 一、虚拟现实物质与空间理论 物质是由离散的奇点JING粒子&#xff0c;依据不同的维度粒度&#xff0c;通过…

SSM【Spring SpringMVC Mybatis】——Mybatis(二)

如果对一些基础理论感兴趣可以看这一期&#x1f447; SSM【Spring SpringMVC Mybatis】——Mybatis 目录 1、Mybatis中参数传递问题 1.1 单个普通参数 1.2 多个普通参数 1.3 命名参数 1.4 POJO参数 1.5 Map参数 1.6 Collection|List|Array等参数 2、Mybatis参数传递【#与…

【全开源】JAVA台球助教台球教练多端系统源码支持微信小程序+微信公众号+H5+APP

功能介绍 球厅端&#xff1a;球厅认证、教练人数、教练的位置记录、助教申请、我的项目、签到记录、我的钱包、数据统计 教练端&#xff1a;我的页面&#xff0c;数据统计、订单详情、保证金、实名认证、服务管理、紧急求助、签到功能 用户端&#xff1a;精准分类、我的助教…

第八章小程序后端开发,运用Bmob后端云

比目后端云简介 一个完整的小程序系统&#xff0c;不但需要前端的展现&#xff0c;而且需要后端服务器的支撑&#xff0c;以提供数据服务。也就是说&#xff0c;开发一个真正完整的小程序应用&#xff0c;需要前后端的相互配合。小程序与远程服务器之间通过HTTPS传输协议进行数…

Spring框架学习笔记(二):Spring IOC容器配置 Bean,分别基于XML配置bean 和 基于注解配置 bean

1 Spring 配置/管理 bean 介绍 Bean 管理包括两方面 &#xff1a;创建 bean 对象&#xff1b;给 bean 注入属性 Bean 配置方式&#xff1a;基于 xml 文件配置方式&#xff1b;基于注解方式 2 基于 XML 配置 bean 2.1 通过类型来获取 bean 方法&#xff1a;给getBean传入一…

linux fdisk 银河麒麟操作系统 v10 磁盘分区和挂载 详细教程

1查看 未加载的磁盘 fdisk -l 2 开始分区 fdisk /dev/vdb #查看分区 #新建分区和保存 3 格式化和挂载 fdisk -l mkfs.xfs /dev/vdb1 #查看uuid blkid /dev/vdb1 mkdir /data vi /etc/fstab UUID209daa-fb1c-48f2-bf5e-e63f38cb8a /data xfs defaults 0 0 #加载下 mo…

Vue 中动态与静态处理 Element UI/Element Plus 组件禁用状态样式

目录 一、静态样式修改 - 使用 ::v-deep 穿透组件样式二、选择器的优先级和匹配顺序三、动态添加样式 - 使用 Vue 实例属性&#xff08;非推荐&#xff09;四、区别总结五、应用场景总结 本文主要探讨在 Vue.js 项目中&#xff0c;特别是搭配 Element UI 或 Element Plus 组件库…

NVME协议第四章-Data Structures

本章描述NVME所需的数据结构&#xff08;讨论均基于PCIE over NVME&#xff09;。 一、Submission Queue & Completion Queue Definition host提交cmd到SQ&#xff0c;填入SQ后&#xff0c;Tail指针需要1&#xff0c;如果tail指针超过队列queue size&#xff0c;那么tai…

Springboot+Vue项目-基于Java+MySQL的车辆管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

TVM简介

TVM FGPA,CPU, GPU 1.什么是TVM&#xff1f; 是一个支持GPU&#xff0c;CPU&#xff0c;FPGA指令生成的开源编译器框架 2.特点 基于图和算符结构来优化指令生成&#xff0c;最大化硬件执行效率。其中使用了很多方法 来改善硬件执行速度&#xff0c;包括算符融合、数据规划…

LeetCode96:不同的二叉搜索树

题目描述 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 代码 /*dp[i]&#xff1a;表示i个节点有dp[i]个不同的二搜索叉树递推公式&#xff1a;dp[i] dp[j-1] * dp[i-j], j…

【C语言—猜数字小游戏】

一、游戏规则 电脑自动生成一个1~100范围内的随机数&#xff0c;由玩家猜测本轮生成的随机数是什么&#xff0c;系统根据玩家猜测数据的⼤⼩给出猜⼤了或猜⼩了的反馈&#xff0c;直到玩家猜对&#xff0c;游戏结束。 如何生成随机数&#xff1a;【C语言】/*如何生成随机值*/-C…

嵌入式学习70-复习(wireshark使用和http协议)

--------------------------------------------------------------------------------------------------------------------------------- wireshark 1.sudo wireshark 2.选择 any &#xff0c; 3.搜索 http/tcp 54 为 发送的数据包 58 回复的数据包 请求报文 请求报文…

【QA】Java集合常用的函数

文章目录 前言Collection接口通用函数 | Collections工具类通用函数 | List接口 Set接口List接口ArrayListLinkedList Set接口TreeSetHashSetLinkedHashSet Map接口通用函数TreeMapHashMapLinkedHashMap 前言 本文介绍Java集合中常用的函数。 Collection接口 通用函数 | Co…

cmake调用MSVC编译时报C2071、C2061、C2059、C2449等错误的解决方案

文章目录 1. 问题复述2. 问题原因3. 解决方案 1. 问题复述 项目一直在linux下编译&#xff0c;这次偶然要在windows下使用msvc编译&#xff0c;结果一堆报错&#xff0c;根据报错信息发现语法也没有问题&#xff0c;报错信息如下&#xff1a; 查找文献说是因为有中文注释导致…