16.进程间通信(二)

article/2025/6/20 23:22:13

一、命名管道

1.概念

匿名管道解决了具有血缘关系的进程之间的通信,如果两个进程毫不相干,如何进行通信呢?通过文件,管道文件。

对于两个不同进程,打开同一路径下的同一文件,inode和文件内核缓冲区不会加载多次,因为没有必要,操作系统不会做没有意义的事情。

那么两个进程的file*就看到了同一份资源,文件内核缓冲区 -> 通过打开同一路径下的同一个文件 -> 文件有路径,有名字且路径具有唯一性 -> 命名管道。

例如:父子进程向同一个显示器打印,向同一个键盘读取。

2.操作

指令:创建mkfifo xxx   删除unlink xxx 

int mkfifo(const char *pathname, mode_t mode);

pathname:路径名,mode:权限

返回值:成功返回0,错误返回-1,错误码被设置

int unlink(const char *pathname); //删除命名管道

返回值:成功返回0,失败返回-1,错误码被设置。

特别注意:

命名管道具有同步机制,以读方式open时,会阻塞,直到对应的写端也open。

命名管道代码如下:

namedpipe.hpp

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define EXIT_ERROR(m) \do                \{                 \perror(m);    \exit(1);      \} while (0)#define PATH "."
#define PIPE_NAME "fifo"
#define BUFF_SIZE 1024
#define MODE_DEFAULT 0666class Namedpipe
{
public:Namedpipe(const std::string &path, const std::string &name, int mode): _mode(mode){_pathname = path + "/" + name;// 1.创建命名管道int ret = mkfifo(_pathname.c_str(), _mode);if (ret < 0){EXIT_ERROR("mkfifo");}printf("mkfifo success!\n");}~Namedpipe(){// 删除命名管道std::cout << "~Namedpipe" << std::endl;int num = unlink(_pathname.c_str());if (num < 0){EXIT_ERROR("unlink");}printf("unlink %s success!\n", _pathname.c_str());}private:std::string _pathname;int _mode;
};class FileOper
{
public:FileOper(const std::string &path, const std::string &name){_pathname = path + "/" + name;}~FileOper(){}void OpenForRead(){// 2.以只读方式打开文件,从文件里读,最后关闭_fd = open(_pathname.c_str(), O_RDONLY);if (_fd < 0){EXIT_ERROR("open for read");}printf("open for read success!\n");}void OpenForWrite(){_fd = open(_pathname.c_str(), O_WRONLY);if (_fd < 0){EXIT_ERROR("open for write");}printf("open for write success!\n");}void Read(){char buff[BUFF_SIZE];while (true){std::cout << "client says# ";int num = read(_fd, buff, BUFF_SIZE - 1);if (num > 0){buff[num] = 0;std::cout << buff << std::endl;}else if (num == 0){std::cout << "client exit!" << std::endl;break;}else{EXIT_ERROR("read");}}}void Write(){std::string msg;while (true){std::cout << "please Enter# ";std::getline(std::cin, msg);int ret = write(_fd, msg.c_str(), msg.size());}}void Close(){close(_fd);printf("close fd:%d\n", _fd);}private:std::string _pathname;int _fd;
};

二、System V

1.共享内存的原理

进程间通信本质:让不同的进程,先看到同一份资源。

System V是一种标准 -> Linux内核支持了这种标准,专门设计了一个IPC通信模块 -> 通信的接口设计,原理,接口,相似。

共享内存的原理:先在内存上开辟一段空间,然后将该空间映射到两个进程的地址空间中,拿到起始虚拟地址,就可以利用这段内存进行通信。

1.图中所有工作,都是由OS自己完成的。OS提供系统调用,我们通过系统调用完成。

2.取消关联关系,OS释放内存。

3.可能同时存在多组进程都在使用共享内存来进行通信,即有多个共享内存。

要对共享内存进行管理,先描述在组织。

2.使用共享内存的接口

1.创建共享内存 shmget、ftok

int shmget(key_t key, size_t size, int shmflg);

size表示共享内存大小。

shmflg表示对应的操作:IPC_CREATE、IPC_EXCL。其中 IPC_EXCL 要和 IPC_CREATE 一起使用,如果shm不存在就新建,存在就出错返回,表示存在了。如果新建,要带上权限位,直接 | 或上权限即可。

核心问题:如何保证两个进程看到的是同一份内存?

key,不同的进程,shm来进行通信,标识共享内存的唯一性 -> key用来区分,不是内核直接形成的,而是在用户端,构造并传入的。

为什么要用户端约定一个数字?

        因为如果不约定,而是采用使用共享内存标识符的方式,那么创建共享内存的那个进程创建完后要将这个标识符给另一个进程,这个标识符是不确定的值,那么怎么给?只能是进程间能通信,相悖。

解决:用户端约定一个确定的数字,确定的数字由用户端直接传入就行了。

对于key值的生成,尽量冲突越小越好。

key_t fok(const char *pathname, int proj_id);
参数可以随便传,但最好是传路径,因为路径是唯一的。

2.删除共享内存

进程结束,如果没有删除共享内存,共享内存资源会一直存在 -> 共享内存的资源,生命周期随内核 -> 没有显示删除,即使进程退出,IPC资源依然存在

1.ipcs/ipcrm删除

2.代码删除

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid为共享内存描述符。

cmd为操作,IPC_RMID表示删除。

buf传nullptr就行。

返回值:失败返回-1,错误码被设置。

删除,控制共享内存,在用户层,不能使用key!key只能用来区分唯一性!

需要用shmid来管理共享内存,指令本质是运行在用户空间的。

3.共享内存的 挂载 和 去关联(重点)

void *shmat(int shmid, const void *shmaddr, int shmflg);  //shmat: at 表示 attach。

shmid为共享内存描述符。

shmaddr为虚拟地址,可以固定地址进行挂接,用户层数使用填nullptr就行。

shmflg为设置选项,填0用默认设置就行,

返回值:挂载后共享内存在进程地址空间中的起始虚拟地址。错误,-1返回,错误码设置。

读写共享内存时,没有用到系统调用 -> 共享区属于用户空间,可以让用户直接使用

共享内存时进程间通信中,速度最快的方式:

1.映射之后,读写,直接被对方看到!

2.不需要进行系统调用进行读写!

但是,缺点是通信双方,没有所谓的“同步机制”!对共享内存的数据,没有保护机制。

如何用现有的知识解决?利用命名管道的同步机制解决。

去关联:

int shmdt(const void *shmaddr); //dt:detach 去关联

shmaddr为关联的起始虚拟地址。

返回值:成功返回0;失败返回-1,错误码被设置。

代码如下:

shm.hpp

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>#define SIZE 4096
#define MODE_DEFAULT 0666
#define EXIT_ERROR(m) \do                \{                 \perror(m);    \exit(1);      \} while (0)#define PATH_NAME "."
#define PROJ_ID 1class Shm
{
private:void Create(const key_t key, const int shmflg){_shmid = shmget(key, SIZE, shmflg);if (_shmid < 0){EXIT_ERROR("shemget");}printf("create shm success!\n");}public:Shm(const std::string &pathname, const int projid, const std::string &identity): _identity(identity){// 1.创建共享内存key_t key = ftok(pathname.c_str(), projid);if (_identity == "user"){Create(key, IPC_CREAT);}else if (_identity == "server"){Create(key, IPC_CREAT | IPC_EXCL | MODE_DEFAULT);}else{printf("identity false!\n");exit(1);}}~Shm(){// 4.删除共享内存if (_identity == "server"){int ret = shmctl(_shmid, IPC_RMID, nullptr);if (ret < 0){EXIT_ERROR("shmctl");}printf("delete shm success!\n");}}void Attach(){// 2.挂载,将共享内存映射到进程地址空间中,拿到起始虚拟地址_addr = shmat(_shmid, nullptr, 0);if ((long long)_addr < 0){EXIT_ERROR("shmat");}printf("attach shm success!\n");}void *Virtualaddr() const { return _addr; }void Detach() const{// 3.去关联int ret = shmdt(_addr);if (ret < 0){EXIT_ERROR("shmdt");}printf("detach shm success!\n");}private:int _shmid;void *_addr;std::string _identity;
};

server.cc

#include "shm.hpp"
#include "namedpipe.hpp"int main()
{Namedpipe npipe(PATH, PIPE_NAME, MODE_DEFAULT);FileOper fp(PATH, PIPE_NAME);fp.OpenForRead();Shm shm(PATH_NAME, PROJ_ID, "server");shm.Attach();printf("%p\n", shm.Virtualaddr());char *addr = (char *)shm.Virtualaddr();while (true){if (fp.Wait()){printf("%s\n", addr);}elsebreak;}shm.Detach();fp.Close();return 0;
}

client.cc

#include "shm.hpp"
#include "namedpipe.hpp"int main()
{FileOper fp(PATH, PIPE_NAME);fp.OpenForWrite();sleep(3);Shm shm(PATH_NAME, PROJ_ID, "user");shm.Attach();printf("%p\n", shm.Virtualaddr());char *s = (char *)shm.Virtualaddr();int k = 0;for (char c = 'A'; c <= 'Z'; c++){sleep(1);s[k++] = c;s[k++] = c;s[k] = 0;fp.Wakeup();}shm.Detach();fp.Close();return 0;
}

 4.共享内存相关的结构

        其中buf是输出型参数,里面的struct shmid_ds内第一个成员为 ipc_perm ,其中的第一个成员为 key,就是用户约定传入的key,标识共享内存的唯一性,用这个输出型参数还能获取共享内存相关的属性。

        共享内存创建的时候,它的大小必须是4KB的整数倍,原因:内存基本是4KB。


http://www.hkcw.cn/article/eoAECAGqop.shtml

相关文章

优化的两极:凸优化与非凸优化的理论、应用与挑战

在机器学习、工程设计、经济决策等众多领域&#xff0c;优化问题无处不在。而在优化理论的世界里&#xff0c;凸优化与非凸优化如同两个截然不同的 “王国”&#xff0c;各自有着独特的规则、挑战和应用场景。今天&#xff0c;就让我们深入探索这两个优化领域的核心差异、算法特…

day15 leetcode-hot100-29(链表8)

19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; 1.暴力法 思路 &#xff08;1&#xff09;先获取链表的长度L &#xff08;2&#xff09;然后再次遍历链表到L-n的位置&#xff0c;直接让该指针的节点指向下下一个即可。 2.哈希表 思路 &#xff0…

rtpinsertsound:语音注入攻击!全参数详细教程!Kali Linux教程!

简介 2006年8月至9月期间&#xff0c;我们创建了一个用于将音频插入指定音频&#xff08;即RTP&#xff09;流的工具。该工具名为rtpinsertsound。 该工具已在Linux Red Hat Fedora Core 4平台&#xff08;奔腾IV&#xff0c;2.5 GHz&#xff09;上进行了测试&#xff0c;但预…

谷歌Stitch:AI赋能UI设计,免费高效新利器

在AI技术日新月异的今天&#xff0c;各大科技巨头都在不断刷新我们对智能工具的认知。最近&#xff0c;谷歌在其年度I/O开发者大会期间&#xff0c;除了那些聚光灯下的重磅发布&#xff0c;还悄然上线了一款令人惊喜的AI工具——Stitch。这是一款全新的、完全免费的AI驱动UI&am…

PowerBI企业运营分析——线性回归销售预测

PowerBI企业运营分析——线性回归销售预测 欢迎来到Powerbi小课堂&#xff0c;在竞争激烈的市场环境中&#xff0c;企业运营分析平台成为提升竞争力的核心工具。 该平台通过整合多源数据&#xff0c;实现关键指标的实时监控&#xff0c;从而迅速洞察业务动态&#xff0c;精准…

<4>, Qt窗口

目录 一&#xff0c;菜单栏 二&#xff0c;工具栏 三&#xff0c;状态栏 四&#xff0c;浮动窗口 五&#xff0c;对话框 一&#xff0c;菜单栏 MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);// 创建菜单栏…

多目标粒子群优化算法(MOPSO),用于解决无人机三维路径规划问题,Matlab代码实现

多目标粒子群优化算法&#xff08;MOPSO&#xff09;&#xff0c;用于解决无人机三维路径规划问题&#xff0c;Matlab代码实现 目录 多目标粒子群优化算法&#xff08;MOPSO&#xff09;&#xff0c;用于解决无人机三维路径规划问题&#xff0c;Matlab代码实现效果一览基本介绍…

具有离散序列建模的统一多模态大语言模型【AnyGPT】

第1章 Instruction 在人工智能领域、多模态只语言模型的发展正迎来新的篇章。传统的大型语言模型(LLM)在理解和生成人类语言方面展现出了卓越的能力&#xff0c;但这些能力通常局限于 文本处理。然而&#xff0c;现实世界是一个本质上多模态的环境&#xff0c;生物体通过视觉、…

嵌入式学习笔记 - STM32 HAL库以及标准库内核以及外设头文件区别问题

一 CMSIS内核驱动文件夹 标准库中CMSIS内核驱动文件夹中&#xff0c;仅包含两个.h文件&#xff0c;其中stm32f10x.h 为stm10系列底层文件如总线以及各片上外设模块寄存器地址&#xff0c;system_stm32f10x.h为系统底层配置文件&#xff0c;主要为时钟配置。 HAL库中CMSIS内核驱…

LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表

LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表 第 一 题 - - - 移 除 链 表 元 素方 法 一 - - - 原 地 删 除方 法 二 - - - 双 指 针方 法 三 - - - 尾 插 第 二 题 - - - 反 转 链 表方 法 一 - - - 迭 代方 法 二 - - - 采 用 头 插 创 建 新 链 表 总 结 &a…

Ros真(node?package?)

Ros中 都是靠一个个节点相互配合的 如同APP之间的配合 然后节点不好单独存在&#xff0c; 我们一般把他们放在一个包里 也就是Package。 也可以自己设立一个包 如图这种 ———————————— 建立包 流程 &#xff1a; —————— 我们弄好之后 在VSCODE SRC右键 …

电路图识图基础知识-常用仪表识图及接线(九)

一、 直流电流表的使用和接线 用来测量直流电流的仪表&#xff0c;我们称为直流电流表&#xff0c;下图所示为直流电流表。 直流电流表有两种接入方式&#xff1a;直接接入法、间接接入法。下图所示为直流电流表接线方 法 。 4.1.2 交流电流表的使用和接线 交流电流表也是一种…

分享两款使用免费软件,dll修复工具及DirectX修复工具

装软件老是弹窗报错&#xff1f;两个小工具解决系统运行库问题 安装软件时弹出DLL缺失&#xff1f;别急&#xff0c;这里有办法 安装软件的时候&#xff0c;突然跳出个弹窗&#xff0c;提示缺少什么“MSVCP140.dll”或者“VCRUNTIME140.dll”&#xff0c;完全不懂。这种情况并…

L56.【LeetCode题解】 电话号码的字母组合

目录 1.17. 电话号码的字母组合 2.分析 举例 枚举算法:使用递归(dfs) 递推 回归 特殊情况的考虑 代码 提交结果 事后回顾: 递归调用的部分展开图 1.17. 电话号码的字母组合 https://leetcode.cn/problems/letter-combinations-of-a-phone-number/ 给定一个仅包含数字…

基础补充(扩展方法/协变/访问修饰/接口)

文章目录 项目地址一、扩展方法&#xff08;Extension Methods&#xff09;1.1 创建扩展方法1.2 案例 二、访问修饰符2.1 顶级类2.2 类中成员&#xff08;字段、属性、方法&#xff09; 项目地址 教程作者&#xff1a;教程地址&#xff1a; 代码仓库地址&#xff1a; 所用到的…

MySQL 事务解析

1. 事务简介 事务&#xff08;Transaction&#xff09; 是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 经典案例&#xff1…

【Qt】Bug:findChildren找不到控件

使用正确的父对象调用 findChildren&#xff1a;不要在布局对象上调用 findChildren&#xff0c;而应该在布局所在的窗口或控件上调用。

Protos-SIP:经典 SIP 协议模糊测试工具!全参数详细教程!Kali Linux教程!

简介 该测试套件的目的是评估会话发起协议 (SIP) 实现的实现级别安全性和稳健性。 Protos-SIP 是一款专为 SIP 协议模糊测试&#xff08;Fuzzing&#xff09;设计的工具&#xff0c;最初由 OUSPG&#xff08;Oulu University Secure Programming Group&#xff09;开发&#…

springMVC-9数据格式化

数据格式化 学习目标&#xff1a; 理解在我们提交数据(比如表单时)&#xff0c;SpringMVC怎样对提交的数据进行转换和处理的 Spring MVC 上下文中内建了很多转换器&#xff0c;可完成大多数 Java 类型的转换工作。 基本数据类型可以和字符串之间自动完成转换 应用实例-页面…

【多线程初阶】死锁的产生 如何避免死锁

文章目录 关于死锁一.死锁的三种情况1.一个线程,一把锁,连续多次加锁2.两个线程,两把锁3.N个线程,M把锁 --哲学家就餐问题 二.如何避免死锁死锁是如何构成的(四个必要条件)打破死锁 三.死锁小结 关于死锁 一.死锁的三种情况 1.一个线程,一把锁,连续多次加锁 -->由synchroni…