【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)

 9efbcbc3d25747719da38c01b3fa9b4f.gif

                                                      作者主页:     作者主页

                                                      本篇博客专栏:Linux

                                                      创作时间 :2024年11月2日

9efbcbc3d25747719da38c01b3fa9b4f.gif

命名管道:

  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件 

我们可以使用mkfifo命令来创建一个管道。

然后通过echo往里面写入一段内容:

回车之后管道不会关闭,在终端2查看可以发现他的内存大小仍然是0,当我们在管道2打印出内容后,管道就自动关闭了

当我们这样执行的时候,我们就可以发现在一直不停的打印我们输入到管道的内容

下面我们在程序里面建立一个管道:

 返回值为0是成功,不为0就是失败。

下面是一个运用命名管道进行通信的例子:

Pipe.hpp:(这里是一些的共享的资源,包括路径,打开管道,关闭管道)

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;const string gpathname = "./myfifo";
const mode_t gmode = 0600;
const int gdefault = -1;
const int gsize = 1024;
const int gForWrite = O_WRONLY;
const int gForRead = O_RDONLY;int OpenPipe(int flag){int _fd = ::open(gpathname.c_str(), flag);if (_fd == -1){std::cerr << "open errno" << std::endl;return _fd;}return _fd;}void ClosePipeHelper(int fd){if (fd >= 0)::close(fd);}// class NamePipe
// {
// private:// public:// };// int CreateNamePipe(string pathname)
// {
//     umask(0);
//     int n = mkfifo(pathname.c_str(), 0600);
//     if (n != 0)
//     {
//         std::cerr << "mkfifo errno" << std::endl;
//         return -1;
//     }
//     return n;
// }// class NamePipe
// {
// public:
//     NamePipe()
//     {
//     }
//     ~NamePipe()
//     {
//     }// private:
//     const string fifo_path;
//     int _id;
//     int _fd;
// };

Server.hpp及Server.cc

#include "Pipe.hpp"class Init
{
public:Init(){umask(0);int n = ::mkfifo(gpathname.c_str(), gmode);if (n < 0){std::cerr << "mkfifo errno" << std::endl;return;}std::cout << "mkfifo success" << std::endl;}~Init(){int n = ::unlink(gpathname.c_str());if (n != 0){std::cerr << "unlink failed" << std::endl;}std::cout << "unlink success" << std::endl;}private:
};Init init;class Server
{
public:Server() : _fd(gdefault){}bool OpenPipeForRead(){_fd = OpenPipe(gForRead);if (_fd < 0){std::cerr << "Open cerrno" << std::endl;return false;}return true;}int RecvPipe(std::string *out){char buffer[gsize];ssize_t n = ::read(_fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;*out = buffer;}return n;}void ClosePipe(){ClosePipeHelper(_fd);}~Server(){}private:int _fd;
};
#include "Server.hpp"int main()
{Server s;// 打开管道s.OpenPipeForRead();string message;while (1){if (s.RecvPipe(&message) > 0){std::cout << "Client Say:" << message << std::endl;}else{break;}}std::cout << "Client quit,me too" << std::endl;// 关闭管道s.ClosePipe();return 0;
}

Client.hpp及Client.cc

#include "Pipe.hpp"class Client
{
public:Client() : _fd(gdefault){}bool OpenPipeForWrite(){_fd = OpenPipe(gForWrite);if (_fd < 0)return false;return true;}int SendPipe(const std::string &in){return ::write(_fd, in.c_str(), in.size());}void ClosePipe(){if (_fd >= 0){::close(_fd);}}~Client(){}private:int _fd;
};
#include "Client.hpp"int main()
{Client c;c.OpenPipeForWrite();while (1){std::string message;std::cout << "Client Enter:" << std::endl;std::getline(std::cin, message);c.SendPipe(message);}c.ClosePipe();return 0;
}

system V共享内存

 system V IPC是一种本地通信方案。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

共享内存在系统中可以同时存在多份,供不同对进程进行通信。

共享内存不是简单的一段内存空间,它也要有描述并管理共享内存的数据结构和匹配算法。

共享内存函数 

shmget 函数

该函数是系统调用,操作系统提供系统调用,让我们创建共享内存。

功能:用来创建共享内存 

参数:

key:这个共享内存段名字(由用户形成)

size:共享内存大小

shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

常见标志位组合和使用:

IPC_CREAT  、IPC_EXCL

IPC_CREAT:如果你要创建的共享内存不存在,就创建。如果存在,就获取该共享内存并返回。
IPC_EXCL:单独使用没意义,只有和IPC_CREAT组合才有意义
IPC_CREAT | IPC_EXCL:如果你要创建的共享内存不存在,就创建。如果存在,就出错返回。(如果成功返回,意味着这shm是全新的)
key,用来标志共享内存,可以让进程a和b找到共享内存。

系统提供了随机生成key值的方法ftok

返回值:成功则返回共享内存段的标识码;失败返回-1

ftok函数:

 ftok不是系统调用,通过我们提供路径和id(这两个值可以随便写)帮我们生成一个key值。我们给a、b两个进程提供同样的路径和id,调用ftok,就能形成同样的key,就能看到同一个共享内存了。

返回值:成功了就返回key值,失败就返回-1。

 共享内存的释放

 共享内存不随着进程的结束而自动释放,需要我们手动释放(指令或者其他系统调用),否则会一直存在,直到系统重启。

共享内存的生命周期随内核,文件的生命周期随进程。

我们可以通过指令ipcs -m 来查看共享内存

可以通过ipsrm -m shmid(自己得到)来释放共享内存

若不想通过指令释放,可以通过下面这个函数释放

 shmctl 函数

shmid:由shmget返回的共享内存标识码

cmd:将要采取的动作(有三个可取值,如下图)

buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0;失败返回-1

传IPC_STAT可以获取共享内存的属性,传IPC_RMID可以删除共享内存。

key和shmid的比较

key:属于用户形成,内核使用的一个字段,用户不能用key来进行shm的管理。它是内核用来进行区分shm的唯一性的。

shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值。

shmat 函数 

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

如果未来不想使用该共享内存,可以用shmdt去关联

 参数

shmid: 共享内存标识

shmaddr:指定共享内存想挂接到哪个地址上 

shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回共享内存的起始地址;失败返回-1

shmdt 函数

功能:将共享内存段与当前进程脱离

参数

shmaddr: 由shmat所返回的指针

返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

接口使用例子

Client.cc

#include "Shm.hpp"
#include "namedPipe.hpp"int main()
{// 1. 创建共享内存Shm shm(gpathname, gproj_id, gUser);shm.Zero();char *shmaddr = (char *)shm.Addr();sleep(3);// 2. 打开管道NamePiped fifo(comm_path, User);fifo.OpenForWrite();// 当成stringchar ch = 'A';while (ch <= 'Z'){shmaddr[ch - 'A'] = ch;std::string temp = "wakeup";std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;fifo.WriteNamedPipe(temp);sleep(2);ch++;}return 0;
}

Server.cc

#include "Shm.hpp"
#include "namedPipe.hpp"int main()
{// 1. 创建共享内存Shm shm(gpathname, gproj_id, gCreater);char *shmaddr = (char*)shm.Addr();shm.DebugShm();// 2. 创建管道NamePiped fifo(comm_path, Creater);fifo.OpenForRead();while(true){std::string temp;fifo.ReadNamedPipe(&temp);std::cout << "shm memory content: " << shmaddr << std::endl;}sleep(5);return 0;
}

Shm.hpp

#ifndef __SHM_HPP__
#define __SHM_HPP__#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>const int gCreater = 1;
const int gUser = 2;
const std::string gpathname = "/home/whb/code/111/code/lesson22/4.shm";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*nclass Shm
{
private:key_t GetCommKey(){key_t k = ftok(_pathname.c_str(), _proj_id);if (k < 0){perror("ftok");}return k;}int GetShmHelper(key_t key, int size, int flag){int shmid = shmget(key, size, flag);if (shmid < 0){perror("shmget");}return shmid;}std::string RoleToString(int who){if (who == gCreater)return "Creater";else if (who == gUser)return "gUser";elsereturn "None";}void *AttachShm(){if (_addrshm != nullptr)DetachShm(_addrshm);void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}void DetachShm(void *shmaddr){if (shmaddr == nullptr)return;shmdt(shmaddr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}public:Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr){_key = GetCommKey();if (_who == gCreater)GetShmUseCreate();else if (_who == gUser)GetShmForUse();_addrshm = AttachShm();std::cout << "shmid: " << _shmid << std::endl;std::cout << "_key: " << ToHex(_key) << std::endl;}~Shm(){if (_who == gCreater){int res = shmctl(_shmid, IPC_RMID, nullptr);}std::cout << "shm remove done..." << std::endl;}std::string ToHex(key_t key){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", key);return buffer;}bool GetShmUseCreate(){if (_who == gCreater){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);if (_shmid >= 0)return true;std::cout << "shm create done..." << std::endl;}return false;}bool GetShmForUse(){if (_who == gUser){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);if (_shmid >= 0)return true;std::cout << "shm get done..." << std::endl;}return false;}void Zero(){if(_addrshm){memset(_addrshm, 0, gShmSize);}}void *Addr(){return _addrshm;}void DebugShm(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);if(n < 0) return;std::cout << "ds.shm_perm.__key : " << ToHex(ds.shm_perm.__key)  << std::endl;std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;}private:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;void *_addrshm;
};#endif

共享内存虽然速度上占有一定优势,但是共享内存对内存不会提供任何的保护机制,会导致数据不一致的问题,即双方不存在谁等谁的情况,比如我想传一个hello,可能我刚写入一个h就已经被读走了,这样就会导致数据不一致的问题,我们在访问共享内存时,没有任何系统调用,所以速度是所有IPC中最快的。

system V消息队列

  •  消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

消息队列的接口的使用跟共享内存函数很像。

如果要发消息队列的数据,用:

如果要接收数据,用:

  要查消息队列就用 ipcs -q   ,它的指令跟共享内存就一字之差

 system V信号量

信号量主要用于同步和互斥的。 

System V 信号量是由内核维护的一组整数,它可以被多个进程同时访问和修改。每个信号量代表一个资源的可用数量,进程可以通过对信号量进行操作来控制对资源的访问。信号量的操作包括增加、减少和等待信号量的值达到某个特定条件等。

信号量特点:

  1. 原子性操作:信号量的操作是原子性的,这意味着多个进程同时对信号量进行操作时,系统会保证操作的完整性和一致性,不会出现部分操作完成而其他操作未完成的情况。
  2. 可用于同步和互斥:信号量可以用于实现进程间的同步和互斥。例如,可以使用信号量来确保多个进程不会同时访问同一个共享资源,或者确保一个进程在某个条件满足之前等待另一个进程完成某个任务。
  3. 内核维护:信号量是由内核维护的,这意味着即使进程崩溃或退出,信号量的值也不会丢失。当进程重新启动时,可以继续使用信号量来控制对共享资源的访问。

 进程互斥

  • 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
  • 在进程中涉及到互斥资源的程序段叫临界区 

 多个执行流(进程),都能看到的一份资源:共享资源

被保护起来的资源:临界资源。 用互斥的方式进行保护。

互斥:任何时刻只能有一个进程在访问共享资源。

 信号量的主要作用是用来保护共享资源的。经过保护,共享资源就变成临界资源。

假设上面的方格是电影院的座位。看电影时,只要有了票,位置就一定是你的,而不是谁先坐到就是谁的。 所以成功申请了信号量,即使不访问共享资源,也会留着一部分资源给你。

这里的信号量也叫多元信号量。

对共享资源的整体使用,即资源只有一个,也就是有人用了,别人就用不了了,即互斥。申请信号量时,这种信号量叫二元信号量。 

信号量也是一个公共资源。

信号量本质是一个计数器,申请信号量时,计数器--,也叫P操作。释放信号量时,计数器++,也叫V操作。

信号量的操作

Linux中允许用户一次申请多个信号量,用信号量集保存,信号量集用数组来维护。

如果申请了多个信号量,上面的nsems就是申请的信号量的个数。

如果信号量不需要了,就用 semctl 。 semid就是要删除的信号量集,semnum就是要删除的信号量集的下标。 

 要对信号量进行PV操作,就用 semop  

查看信号量,用 ipcs -s 。删除操作跟前面类似。

最后:

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1.一个冷知识:
屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。

2.你不用变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。
正所谓:君子可内敛不可懦弱,面不公可起而论之。

3.成年人的世界,只筛选,不教育。

4.自律不是6点起床,7点准时学习,而是不管别人怎么说怎么看,你也会坚持去做,绝不打乱自己的节奏,是一种自我的恒心。

5.你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。

最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

划界与分类的艺术:支持向量机(SVM)的深度解析

划界与分类的艺术&#xff1a;支持向量机&#xff08;SVM&#xff09;的深度解析 1. 引言 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是机器学习中的经典算法&#xff0c;以其强大的分类和回归能力在众多领域得到了广泛应用。SVM通过找到最优超平面来分…

Java设计模式(代理模式整理中ing)

一、代理模式 1、代理模式定义&#xff1a; 代理模式&#xff1a;由于某些原因要给某对象提供一个代理以控制对该对象的访问&#xff0c;这时访问对象不适合或者不能够直接引用目标对象&#xff0c;代理对象作为访问对象与目标对象之间的中介进行连接调控调用。 2、代理模式的…

【含文档+源码】基于SpringBoot+Vue的新型吃住玩一体化旅游管理系统的设计与实现

开题报告 本文旨在探讨新型吃住玩一体化旅游管理系统的设计与实现。该系统融合了用户注册与登录、旅游景点管理、旅游攻略发帖、特色旅游路线推荐、附近美食推荐以及酒店客房推荐与预定等多项功能&#xff0c;旨在为游客提供全方位、一体化的旅游服务体验。在系统设计中&#…

如何卸载电脑上的软件?彻底删除第三方和系统自带软件方法!(新款)

如何卸载电脑上的软件&#xff1f;在日常使用电脑的过程中&#xff0c;我们经常会安装各种软件以满足不同的需求。然而&#xff0c;随着时间的推移&#xff0c;一些不再使用的软件可能会占用系统资源&#xff0c;影响电脑性能。因此&#xff0c;定期卸载不需要的软件是保持系统…

cocos开发QA

目录 TS相关foreach循环中使用return循环延迟动态获取类属性 Cocos相关属性检查器添加Enum属性使用Enum报错 枚举“XXX”用于其声明前实现不规则点击区域使用cc.RevoluteJoint的enable激活组件无效本地存储以及相关问题JSON.stringify(map)返回{}数据加密客户端复制文本使用客户…

LeetCode :21. 合并两个有序链表(Java)

目录 题目描述: 代码: 第一种: 第二种: 题目描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; …

删除的文件怎么找回?删除文件恢复全面指南

我们常常在日常生活或工作中不小心删除了重要文件&#xff0c;这样的情况可能瞬间让人感到无助。不过&#xff0c;数据恢复技术已相当成熟&#xff0c;我们可以通过多种方法来找回误删的文件。下面我们将从简单到复杂逐步讲解找回删除文件的方法&#xff0c;希望可以帮助大家在…

D57【python 接口自动化学习】- python基础之异常

day57 异常捕获 学习日期&#xff1a;20241103 学习目标&#xff1a;异常 -- 73 异常捕获&#xff1a;出现异常时&#xff0c;如何利用程序进行处理&#xff1f; 学习笔记&#xff1a; try-except代码块 # 捕获异常 num1 num10 try:num/num1except Exception as e:print(上…

【06】A-Maven项目SVN设置忽略文件

做Web项目开发时&#xff0c;运用的是Maven管理工具对项目进行管理&#xff0c;在项目构建的过程中自动生成了很多不需要SVN进行管理的文件&#xff0c;SVN在对源码进行版本管理时&#xff0c;需要将其忽略&#xff0c;本文给出了具体解决方案。 SVN设置忽略Maven项目中自动生成…

logback日志级别动态切换四种方案

生产环境中经常有需要动态修改日志级别。 现在就介绍几种方案 方案一&#xff1a;开启logback的自动扫描更新 配置如下 <?xml version"1.0" encoding"UTF-8"?> <configuration scan"true" scanPeriod"60 seconds" debug…

Linux——Ubuntu的基础操作

压缩与解压缩 gzip压缩工具 创建文件 a.c和b.c touch a.c touch b.c 压缩文件a.c和b.c gzip a.c gzip b.c 解压缩a.c.gz和b.c.gz gzip -d a.c.gz 对文件夹进行压缩 gzip -r 对文件夹进行解压缩 gzip -rd 注意&#xff1a;这只是对文件夹里所有文件进行压缩&#xff0c…

win10下MMSegmentation自定义数据集

下载1.2.1版本: Releases open-mmlab/mmsegmentation GitHub 安装环境 本地torch环境为1.9.1 pip install -U openmim mim install mmengine mim install "mmcv>=2.0.0" 报mmcv版本不匹配的问题,形如:MMCV==X.X.X is used but incompatible. Please inst…

App Store 截图生成器:轻松制作专业级应用营销图片

在线使用 &#x1f449; 立即使用截图生成器 简介 App Store 截图生成器是一个专门为 iOS 开发者和营销人员设计的在线工具&#xff0c;可以快速生成符合 App Store 规范的应用预览图片。无论是 iPhone 还是 iPad 应用&#xff0c;都能轻松创建出精美的营销截图。 主要特点…

2024年超详细Pycharm安装保姆级教程,Python环境配置和使用指南,看完这一篇就够了

PyCharm 是由 JetBrains 打造的一款 Python IDE &#xff08;集成开发环境&#xff0c;Integrated Development Environment&#xff09;&#xff0c;带有一整套可以帮助用户在使用 Python 语言开发时提高其效率的工具&#xff0c;比如调试、语法高亮、Project 管理、代码跳转、…

使用DJL和PaddlePaddle的口罩检测详细指南

使用DJL和PaddlePaddle的口罩检测详细指南 完整代码 该项目利用DJL和PaddlePaddle的预训练模型&#xff0c;构建了一个口罩检测应用程序。该应用能够在图片中检测人脸&#xff0c;并将每张人脸分类为“戴口罩”或“未戴口罩”。我们将深入分析代码的每个部分&#xff0c;以便…

filebeat+elasticsearch+kibana日志分析

1 默认配置 1.1 filebeat filebeat-7.17.yml,从网关中下载k8s的配置&#xff0c;指定es和kibana的配置 通过kibana查询可以查询到日志了&#xff0c;但此时还不知道具体怎么用。 1.2 kibana 在Discover中创建索引格式&#xff1a;filebeat-*&#xff0c;得到如下图&#xf…

MySQL表的增删改查(CRUD1)

好兄弟们&#xff0c;有没有忘了咱们上节说的知识点呢&#xff1f;忘了也没关系&#xff0c;让我们开始复习吧&#xff01;&#xff01;&#xff01; 上期我们介绍了数据类型&#xff0c;还有一些表的操作&#xff0c;我们常用的数据类型有&#xff1a;1.数值类型 tinyint …

C++和OpenGL实现3D游戏编程【连载17】——着色器进阶(附源码)

🔥C++和OpenGL实现3D游戏编程【目录】 1、本节要实现的内容 在前面着色器初步一节我们了解了着色器的一些初步知识,通过顶点着色器和片段着色器显示出了一个彩色的立方体。我们这节课就来了解一些在着色器中显示纹理等一系列实用操作,同时了解一些进阶的图像渲染技术,比如…

C++ | Leetcode C++题解之第520题检测大写字母

题目&#xff1a; 题解&#xff1a; class Solution { public:bool detectCapitalUse(string word) {// 若第 1 个字母为小写&#xff0c;则需额外判断第 2 个字母是否为小写if (word.size() > 2 && islower(word[0]) && isupper(word[1])) {return false;…

教育技术革新:SpringBoot在线试题库系统开发

2 相关技术 2.1 Spring Boot框架简介 Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff0c;Sprin…