[Linux] 进程间通信基础

在这里插入图片描述

💻文章目录

  • 📄前言
  • 进程间通信基础
    • 概念
  • 管道
    • 概念
    • 管道的工作原理
    • 模拟实现shell中的管道
  • 共享内存
    • 概念
    • 接口的介绍
    • 共享内存的使用
  • 📓总结


📄前言

你是否了解进程间是如何通信的呢?你是否知道管道的工作原理呢?管道是Linux中最基本的也是最常用的进程间通信手段,----(Todo)

进程间通信基础

概念

进程间通信(Inter-Process Communication)简称IPC,是不同进程之间传递数据的手段、接口。它是多进程间协同工作的核心机制。

Linux 主要的IPC接口有:

  1. 管道(pipe)
  2. 信号(Signal)
  3. 共享内存(Shared Memory)
  4. 信号量(Semaphore)
  5. 消息队列(Message Queue)
  6. 套接字(Socket)

本文将着重介绍管道与共享内存两种方式。

管道

概念

管道大概是最多Linux用户使用过的一种的IPC接口了吧,只要有学过控制台代码,你就一定会认识它。它是一种先进先出的结构,数据的流向是单向的,最简单的使用管道的方法就是在 shell 中使用 “ | ”.

echo "管道的使用方法" | grep "test.c"
# 其功能就像它的名字,将进程数据通过一个管子传到另一个进程
# [进程A] ---> |管道| ---> [进程B]

管道的类型有两种

  • 匿名管道: 主要用于父子进程间的通信,在文件系统中没有一个实际的名称,进程结束便消失。

  • 命名管道: 可用于不同进程之间的通信,与匿名管道不通命名管道在文件系统中有一个实际名称,可长久存在,并且可以通过两个管道来实现双向通信。

管道的工作原理

管道在实质上可以看成缓冲区,一端用于写,一端用于读。在进程将数据写入管道时,数据被储存在系统内核的缓冲区中,而不是直接存到管道文件中,管道文件只是作为一个标识符用于不同进程打开管道。正所谓实践出真知,其他的原理,让我们通过 Coding 来知晓吧。

  • 匿名管道的使用:
// 匿名管道与命名管道的使用// 头文件 unistd.h
// int pipe(int pipefd[2]);
// pipefd[0]被设为管道的读端,[1]为写端const char *msg = "i like linux!";void test_1()
{char buf[1024] = {0};int fd[2];pipe(fd);pid_t id = fork();	//生成子进程if(id < 0)  exit(1);else if(id == 0)	// 子进程入口{close(fd[0]);write(fd[1], msg, strlen(msg));close(fd[1]);exit(0);}// 父进程close(fd[1]);   //关闭读端read(fd[0], buf, sizeof(buf));printf("%s\n", buf);	close(fd[0]);wait(NULL);	//等待子进程结束
}//  	  父进程                          子进程
//  +----------------+             +----------------+
//  |                |   系统缓冲区  |                |
//  |   写入 fd[1]    |------------>|   读取 fd[0]   |
//  |                |     管道     |                |
//  +----------------+             +----------------+
//		写入端						    读入端// 你可能会有些疑问,为什么要用这么多个close。
// 关闭不需要用的文件描述符是个良好的编程习惯
// 因为文件描述符是有限的资源,而且不关闭会在某些情况下,导致进程阻塞。
  • 命名管道的使用:
// 头文件 sys/types.h sys/stat.h
// int mkfifo(const char* pathname, mode_t mode) 创建管道文件
// pathname:管道的名称    mode:设置文件的权限。// server.cpp
int main()
{int ret = mkfifo("fifo", 0644);const char* msg = "server:i love linux"int fd = open("fifo", O_WRONLY);	// O_WRONLY:只写模式for (int i = 0; i < 10; ++i){write(fd, msg, strlen(msg));	//读端没有打开,进程则会阻塞sleep(1);}return 0;
}// client.cpp
int main()
{int fd = open("fifo", O_RDONLY);  // O_RDONLY:只读模式char buf[1024];while (1){	// 如果管道写端没有打开,进程则会阻塞ssize_t n = read(fd, buf, sizeof(buf));buf[n] = '\n';if (n == 0)	// 读端断开连接,返回0{printf("process exit\n");return 0;}else if (n > 0){write(1, buf, n + 1);	// 向标准输出打印}}return 0;
}

管道的读写规则:

  • 当管道读写端任意一端未打开,则进程会阻塞等待。
  • 管道写满了,尝试写入的进程会阻塞
  • 管道无数据可读,如果写端关闭则返回0,写端没有关闭则阻塞进程。

模拟实现shell中的管道

int main()
{int pipefd[2];if(pipe(pipefd) == -1 && errno != EEXIST){perror("pipe");exit(1);}pid_t pid = fork();if(pid == 0){	//子进程dup2(pipefd[1], STDOUT_FILENO);	//dup = duplicate(复制),将pipefd[1]复制到标准输出(1号)// 像标准输出打印的数据都讲传入管道写端close(pipefd[0]);	close(pipefd[1]);	// 已经将其复制到标准输出,可以关闭char* argv[] = {"ls", "-l", nullptr};execvp(argv[0], argv);	//执行命令exit(0);}pid_t pid2 = fork();if(pid2 == 0){	//子进程dup2(pipefd[0], STDIN_FILENO);	//将fd[0]复制到标准输入close(pipefd[0]);close(pipefd[1]);char* argv[] = {"grep", (char*)"test", nullptr};execvp(argv[0], argv);exit(0);}close(pipefd[0]);close(pipefd[1]);wait(nullptr);wait(nullptr);return 0;
}

共享内存

概念

共享内存是所有IPC机制中最快的一种机制,它能使得多个进程访问同一块内存区域,而不用在进程间复制拷贝。 例如管道,它实际就是系统中的一块缓存区,两个进程间交流就必须将缓冲区的数据拷贝的自己内存中。共享内存则是在物理内存中开辟一段空间,然后通过页表将其映射到程序的共享区,进程直接对内存进行读写。

+---------+         +-------------------+        +---------+
|         |  ---->  |                   |  <---- |         |
| Process |         |   Shared Memory   |        | Process |
|    A    |  <----  |       Segment     |  ----> |    B    |
|         |         |                   |        |         |
+---------+         +-------------------+        +---------+^                                               ^|                                               |Read                                           Write

接口的介绍

在使用共享内存前,必须先简单介绍一下System V 与 POSIX,它们是UNIX系统的两种不同系统标准,而在Linux上他们两者的接口都有兼容,接下来我们要使用的共享内存属于System V的。

要使用共享内存就得先知道如何检查系统中的共享内存,以及如果程序遇到异常时,如何删除共享内存。

#显示共享内存
ipcs -m 
# 使用 ipcs 命令来检查system V的通信信息,默认情况下显示所有的资源。# 删除共享内存
ipcrm [shm|msg|sem] ID ... 
# 选项
# -m 根据共享内存的shmid来删除
# -M 根据共享内存的shmeky来删除

共享内存的接口:

// 接口介绍
// 涉及头文件: <sys/ipc.h> <sys/shm.h>key_t ftok(const char* pathname, int proj_id);
//使用ftok来生成唯一的key值,参数为路径名与项目IDint shmget(key_t key, size_t size, int shmflg);	
// 返回值为shmid
// 根据key来生成共享,size为共享内存的字节数只能为1024的倍数。
// shmflg:共享内存的权限,一般使用像 0644,IPC_CREAT、IPC_EXCL等。// 挂载共享内存
void* shmat(int shmid, const void* shmaddr, int shmflg);
// shmid 为shmget的返回值
// shmaddr:将共享内存连接到当前进程地址空间的特定地址
// shmflg:一般为0或者SHM_RDONLY(只读)//取消挂载共享内存
int shmdt(const void* shmaddr);
// shmaddr :共享内存的地址//控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
// shmid:共享内存的id号
// cmd:控制命令,一般为IPC_STAT(获取共享内存的状态)、IPC_SET(设置共享内存的参数)、IPC_RMID(删除共享内存)
// buf:指向shmid_ds 结构的指针,用于设置共享内存或存储共享内存的状态。

共享内存的使用

共享内存主要用于两个不同进程数据的交流,这里将使用两个不同进程,一个用于写数据,一个用于读数据。因为共享内存的读写是无法预测的(对方不知道你何时写了),所以需要用到命名管道来辅助。

server.cpp:

// server.cpp
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <string>
#include <unistd.h>const char* pathname = "/home/catianri/code";const int project_id = 0x11223344;int main() {key_t key = ftok(pathname, project_id); // 创建唯一的keyint cnt = 0, code = 0;	// code用于通知对方进程,已经开始写入。int shmid = shmget(key, 1024, 0666|IPC_CREAT); // 创建共享内存mkfifo(".fifo", 0666);	//创建命名管道int fd = open(".fifo", O_WRONLY);	int* arr = static_cast<int*>(shmat(shmid, nullptr, 0)); // 将共享内存附加到进程的地址空间while(cnt < 10){arr[cnt++] = cnt;	// 向共享内存写入数据 write(fd, &code, sizeof(int));	// 通知另一个进程sleep(1);}shmdt(arr); // 断开共享内存连接shmctl(shmid, IPC_RMID, NULL); // 销毁共享内存close(fd);return 0;
}

client.cpp:

#include <iostream>
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>const char* pathname = "/home/catianri/code";const int project_id = 0x11223344;int main() {key_t key = ftok(pathname, project_id); // 使用相同的文件和项目ID来创建keyint shmid = shmget(key, 1024, 0666|IPC_CREAT); // 连接到共享内存int *arr = static_cast<int*>(shmat(shmid, nullptr, 0)); // 将共享内存附加到进程的地址空间mkfifo(".fifo", 0666);int fd = open(".fifo", O_RDONLY);int* code, cnt = 0;while(1){ssize_t n = read(fd, &code, sizeof(int));if(n == 0)	//server已经断开写端{close(fd);std::cout << "process exit" << std::endl;}else {	//拿取共享内存中的数据。std::cout << arr[cnt++] << " ";fflush(stdout);	//刷新缓冲区。}}shmdt(arr); // 断开共享内存连接return 0;
}

共享内存的特点:

  • 高效性: 共享内存是最快的IPC方式,它允许进程直接访问共享数据,避免了拷贝操作。
  • 灵活性: 共享内存提供了高度的灵活性,开发者可以根据需要自定义共享数据的结构和管理方式。
  • 手动同步: 由于可以多进程同时访问,所以需要外部同步机制。
  • 复杂性: 与其他IPC机制(如管道和消息队列)相比,共享内存的使用和管理更为复杂。

📓总结

特性管道System V共享内存
性能和效率数据需要在进程间复制,存在额外的CPU开销和延迟。允许多个进程直接访问同一内存区,减少了数据复制,提高了效率。
使用场景适用于顺序数据流通信,常用于父子进程或紧密相关进程间。适用于性能要求高的场景,如大数据处理、实时系统,因为它几乎无延迟地实现数据共享。
同步机制自带同步机制,写入端和读取端会在必要时阻塞,直到对方准备好。需要额外的同步机制(如信号量、互斥锁)来防止数据竞争和保证一致性。
容错性和可靠性相对简单可靠,但需要正确处理EOF和管道破裂等情况。需要仔细管理资源和同步,避免竞态条件、死锁或数据损坏。
易用性API简单,容易实现和维护,但功能有限。提供了一套功能丰富的API,但相较于管道,使用和维护更加复杂。

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

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

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

相关文章

关于Web端 —— UI自动化测试

在手工测试阶段&#xff0c;针对项目输出了测试用例&#xff0c;如果这些测试用例需要在版本迭代的过程中&#xff0c;需要进行回归测试&#xff0c;通过手工重复地执行测试用例&#xff0c;将会耗费大量的人力。 为此应运而生就有了自动化测试&#xff0c;通过使用自动化工具…

【docker】Docker打包SpringBoot镜像

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;中间件 ⛺️稳中求进&#xff0c;晒太阳 前置说明 最为原始的打包方式spring-boot-maven-plugin插件jib-maven-plugin插件dockerfle-maven-plugin插件 最为原始的方式 也就是使用Docker的打…

大模型知识库

一种利用 langchain 思想实现的基于本地知识库的问答应用&#xff0c;目标期望建立一套对中文场景与开源模型支持友好、可离线运行的知识库问答解决方案。 1. 下载Langchain-chatchat git clone https://github.com/chatchat-space/Langchain-Chatchat/ 2. 下载大模型和embe…

汽车电子零部件(8):T_Box

前言: 网联汽车(Connected Vehicles ,CV)是一个广泛的概念,四个主要的CV线程已发展起来:互联、自主、共享和电动。这些应用于包括CV在内的垂直领域:汽车、通信、互联网和共享手机服务。中国汽车工程师学会(SAEC)提倡将车载ADAS(高级驾驶员辅助系统)与通信技术相结合…

gitlab仓库使用流程(开发)

1.1.GitLab代码提交流程&#xff1a; 1.1.1准备阶段&#xff1a; 确保已经安装了Git&#xff0c;并且配置了正确的用户名和邮箱地址。 在本地创建一个新的文件夹&#xff0c;用于存放即将开发的代码。 1.1.2.拉取代码&#xff1a; 使用git clone命令从GitLab上拉取项目代码…

基于python的在线学习与推荐系统

技术&#xff1a;pythonmysqlvue 一、系统背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。这样的大…

阿里5年经验之谈 —— 接口测试用例如何编写?

接口测试用例如何编写&#xff1f;下面简单给大家讲解一下。 接口测试用例是目前软件开发中不可或缺的一个重要部分&#xff0c;因此编写接口测试用例同样重要。 接口测试用例的作用非常明显&#xff0c;它能够帮助我们了解产品正在考验、调整它如何表现在特定情境之下、产品…

01|模型IO:输入提示、调用模型、解析输出

Model I/O 可以把对模型的使用过程拆解成三块&#xff0c;分别是输入提示&#xff08;对应图中的Format&#xff09;、调用模型&#xff08;对应图中的Predict&#xff09;和输出解析&#xff08;对应图中的Parse&#xff09;。这三块形成了一个整体&#xff0c;因此在LangCha…

VScode 设置个性化背景(保姆级教程)

VS Code设置个性化背景的作用主要体现在以下几个方面&#xff1a; 提升编程体验&#xff1a;个性化背景能够让编程环境更符合个人的审美和习惯&#xff0c;使得长时间在VS Code中进行代码编辑时&#xff0c;能够保持愉悦的心情&#xff0c;从而提高编程效率。减少视觉疲劳&…

【工具】Mermaid + 大模型画流程图

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 引入使用画TCP三次握手了解历史人物 总结 引入 最近看面试文章关于TCP三次握手和…

数据预处理:重复值

数据重复值处理 数据重复值出现情况重复的记录用于分析演变规律重复的记录用于样本不均衡处理重复的记录用于检测业务规则问题 数据重复值出现情况 数据集中的重复值包括以下两种情况&#xff1a; 数据值完全相同的多条数据记录。这是最常见的数据重复情况。数据主体相同但匹…

XAI之TDB:transformer-debugger的简介、安装和使用方法、应用案例之详细攻略

XAI之TDB&#xff1a;transformer-debugger的简介、安装和使用方法、应用案例之详细攻略 导读&#xff1a;小语言模型在处理一些任务时会出现无法明确解释的行为&#xff0c;难以细致追踪模型内部各个组件如神经元、注意力头等在推理过程中的作用。2024年3月12日&#xff0c;Op…

第二十六天-统计与机器学习SciPy,Scikit-Leaen

目录 1.介绍 2.使用scipy 1. 安装 2.拟合曲线 3.随机变量与概率分布 4.假设检验 5.参数检验 3.使用Scikit-Learn 1. 机器学习库&#xff0c;建立在numpy,scipy,matplotlib基础上 2.包含功能 3.安装 1.官网&#xff1a;https://scikit-learn.org 2.下载 3.线性回归…

STM32 ADC库函数

单片机学习&#xff01; 目录 1. RCC_ADCCLKConfig 函数 2. ADC_DeInit 函数 3. ADC_Init 函数 4. ADC_StructInit 函数 5. ADC_Cmd 函数 6. ADC_DMACmd 函数 7. ADC_ITConfig 函数 8. 用于校准的函数 8.1 ADC_ResetCalibration 函数 8.2 ADC_GetResetCalibrationSta…

【Java扫盲篇】==和equals的区别

这是一道面试经典题&#xff0c;面试官对你说&#xff1a;小伙子&#xff0c;请你说说和equals的区别 对于来说 如果比较的是基本数据类型&#xff0c;那么比较的是数据的值是否相同。注意&#xff1a;&#xff08;与数据类型无关&#xff09; int a 10;int b 10;System.out…

数据库系统概念(第二周 第二堂)(关系模型)

目录 回顾 关系模型 历史与现状 组成成分 数据结构——关系 关系定义 关系性质 关系和关系模式 难点概念理解 关系属性的分类 一、超码&#xff08;superkey&#xff09; 二、候选码&#xff08;candidate key&#xff09; 三、主码&#xff08;primary key&#…

Catmull-Rom P5 ThreeJs与前端

文章目录 问题Echarts 3D如何让曲线变得平滑&#xff1f;Echarts 2D图中平滑效果是如何实现的&#xff1f;如何在一个Echarts 3D图中画一个圆圈&#xff1f;如何在Echarts 3D图中画一个立方体&#xff1f; Catmull-Rom插值算法先来回答第二个问题回到第一个问题在Echarts 3D图中…

git基础命令(三)之远程命令

目录 基础概念origin git clonegit remote add 添加远程存储库git remote 显示远程存储库列表git pushgit pushgit push origin mastergit push origin --allgit push -f origin mastegit push origin --tags git fetch获取远程仓库的更新查看远程分支的更新情况拉取特定远程分…

【Linux】——进程地址空间 Linux2.6内核进程调度队列

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 一、环境变量的补充 二、进程空间的地址 2.1、程序地址空间 2.2、研究背景 2.3、程序地址空间 来段代码感受一下 2.4、进程地址空间 2.5、如何…

一文搞懂IP

IP 1. 基本介绍2. IP地址定义3. IP地址分类4. 子网掩码5. 全局地址与私有地址 1. 基本介绍 TCP/IP 协议的心脏是网络层&#xff0c;主要“实现节点之间的通信”&#xff0c;即“点对点(end-to-end)通信”。 网络层包含IP(Internet Protocol)及DNS&#xff08;Domain Name Sys…