【Linux】Linux进程间通信(一)

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:Linux
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【Linux】Linux进程概念

文章目录

  • 进程间通信介绍
    • 进程间通信的概念
    • 进程间通信的目的
    • 进程间通信的本质
    • 进程间通信的分类
      • 管道
      • System V IPC
      • POSIX IPC
  • 管道
    • 什么是管道
    • 匿名管道
      • 匿名管道的原理
      • pipe函数
      • 匿名管道使用步骤
      • 管道读写规则
      • 管道的特点
      • 管道的四种特殊情况
      • 管道的大小
  • 总结:

进程间通信介绍

进程间通信的概念

进程间通信简称IPC(Interprocess communication),进程间通信就是在不同进程之间传播或交换信息。

进程间通信的目的

数据传输: 一个进程需要将它的数据发送给另一个进程。
资源共享: 多个进程之间共享同样的资源。
通知事件: 一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件,比如进程终止时需要通知其父进程。
进程控制: 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信的本质

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

由于各个运行进程之间具有独立性,这个独立性主要体现在数据层面,而代码逻辑层面可以私有也可以公有(例如父子进程),因此各个进程之间要实现通信是非常困难的。

各个进程之间若想实现通信,一定要借助第三方资源,这些进程就可以通过向这个第三方资源写入或是读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域。

在这里插入图片描述

因此,进程间通信的本质就是,让不同的进程看到同一份资源(内存,文件内核缓冲等)。 由于这份资源可以由操作系统中的不同模块提供,因此出现了不同的进程间通信方式。

进程间通信的分类

管道

匿名管道
命名管道

System V IPC

System V 消息队列
System V 共享内存
System V 信号量

POSIX IPC

消息队列
共享内存
信号量
互斥量
条件变量
读写锁

管道

什么是管道

管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个“管道”。

例如,统计我们当前使用云服务器上的登录用户个数。
在这里插入图片描述

其中,who命令和wc命令都是两个程序,当它们运行起来后就变成了两个进程,who进程通过标准输出将数据打到“管道”当中,wc进程再通过标准输入从“管道”当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理。
在这里插入图片描述

注明: who命令用于查看当前云服务器的登录用户(一行显示一个用户),wc -l用于统计当前的行数。

匿名管道

匿名管道的原理

匿名管道用于进程间通信,且仅限于本地父子进程之间的通信。

进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。
在这里插入图片描述

注意:

这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。
管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。

pipe函数

pipe函数用于创建匿名管道,pip函数的函数原型如下:

int pipe(int pipefd[2]);

pipe函数的参数是一个输出型参数,数组pipefd用于返回两个指向管道读端和写端的文件描述符:

数组元素	  	含义
pipefd[0]	管道读端的文件描述符
pipefd[1]	管道写端的文件描述符

pipe函数调用成功时返回0,调用失败时返回-1。

匿名管道使用步骤

在创建匿名管道实现父子进程间通信的过程中,需要pipe函数和fork函数搭配使用,具体步骤如下:

1、父进程调用pipe函数创建管道。
在这里插入图片描述

2、父进程创建子进程。
在这里插入图片描述

3、父进程关闭写端,子进程关闭读端。
在这里插入图片描述

注意:

管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。
从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取。

我们可以站在文件描述符的角度再来看看这三个步骤:

1、父进程调用pipe函数创建管道。
在这里插入图片描述

2、父进程创建子进程。
在这里插入图片描述

3、父进程关闭写端,子进程关闭读端。
在这里插入图片描述

例如,在以下代码当中,子进程向匿名管道当中写入10行数据,父进程从匿名管道当中将数据读出。

//child->write, father->read                                                                                                                                                                                                                                                                                                                                                                                                                                                        
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0){
		//child
		close(fd[0]); //子进程关闭读端
		//子进程向管道写入数据
		const char* msg = "hello father, I am child...";
		int count = 10;
		while (count--){
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //子进程写入完毕,关闭文件
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端
	//父进程从管道读取数据
	char buff[64];
	while (1){
		ssize_t s = read(fd[0], buff, sizeof(buff));
		if (s > 0){
			buff[s] = '\0';
			printf("child send to father:%s\n", buff);
		}
		else if (s == 0){
			printf("read file end\n");
			break;
		}
		else{
			printf("read error\n");
			break;
		}
	}
	close(fd[0]); //父进程读取完毕,关闭文件
	waitpid(id, NULL, 0);
	return 0;
}

运行结果如下:
在这里插入图片描述

管道读写规则

pipe2函数与pipe函数类似,也是用于创建匿名管道,其函数原型如下:

int pipe2(int pipefd[2], int flags);

pipe2函数的第二个参数用于设置选项。

1、当没有数据可读时:
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

2、当管道满的时候:
O_NONBLOCK disable:write调用阻塞,直到有进程读走数据。
O_NONBLOCK enable:write调用返回-1,errno值为EAGAIN。

3、如果所有管道写端对应的文件描述符被关闭,则read返回0。
4、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
5、当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。
6、当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。

管道的特点

1、管道内部自带同步与互斥机制。

我们将一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行写入或是读取操作,因此管道也就是一种临界资源。

临界资源是需要被保护的,若是我们不对管道这种临界资源进行任何保护机制,那么就可能出现同一时刻有多个进程对同一管道进行操作的情况,进而导致同时读写、交叉读写以及读取到的数据不一致等问题。

为了避免这些问题,内核会对管道操作进行同步与互斥:

同步: 两个或两个以上的进程在运行过程中协同步调,按预定的先后次序运行。比如,A任务的运行依赖于B任务产生的数据。
互斥: 一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

实际上,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。对于管道的场景来说,互斥就是两个进程不可以同时对管道进行操作,它们会相互排斥,必须等一个进程操作完毕,另一个才能操作,而同步也是指这两个不能同时对管道进行操作,但这两个进程必须要按照某种次序来对管道进行操作。

也就是说,互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,而同步的任务之间则有明确的顺序关系。

2、管道的生命周期随进程。

管道本质上是通过文件进行通信的,也就是说管道依赖于文件系统,那么当所有打开该文件的进程都退出后,该文件也就会被释放掉,所以说管道的生命周期随进程。

3、管道提供的是流式服务。

对于进程A写入管道当中的数据,进程B每次从管道读取的数据的多少是任意的,这种被称为流式服务,与之相对应的是数据报服务:

流式服务: 数据没有明确的分割,不分一定的报文段。
数据报服务: 数据有明确的分割,拿数据按报文段拿。

4、管道是半双工通信的。

在数据通信中,数据在线路上的传送方式可以分为以下三种:

单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。

管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。
在这里插入图片描述

管道的四种特殊情况

在使用管道时,可能出现以下四种特殊情况:

1.写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被挂起,直到管道里面有数据后,读端进程才会被唤醒。
2.读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
3.写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
4.读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么操作系统会将写端进程杀掉。

其中前面两种情况就能够很好的说明,管道是自带同步与互斥机制的,读端进程和写端进程是有一个步调协调的过程的,不会说当管道没有数据了读端还在读取,而当管道已经满了写端还在写入。读端进程读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中还有空间,若是条件不满足,则相应的进程就会被挂起,直到条件满足后才会被再次唤醒。

第三种情况也很好理解,读端进程已经将管道当中的所有数据都读取出来了,而且此后也不会有写端再进行写入了,那么此时读端进程也就可以执行该进程的其他逻辑了,而不会被挂起。

第四种情况也不难理解,既然管道当中的数据已经没有进程会读取了,那么写端进程的写入将没有意义,因此操作系统直接将写端进程杀掉。而此时子进程代码都还没跑完就被终止了,属于异常退出,那么子进程必然收到了某种信号。

我们可以通过以下代码看看情况四中,子进程退出时究竟是收到了什么信号。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0){
		//child
		close(fd[0]); //子进程关闭读端
		//子进程向管道写入数据
		const char* msg = "hello father, I am child...";
		int count = 10;
		while (count--){
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //子进程写入完毕,关闭文件
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端
	close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)
	int status = 0;
	waitpid(id, &status, 0);
	printf("child get signal:%d\n", status & 0x7F); //打印子进程收到的信号
	return 0;
}

运行结果显示,子进程退出时收到的是13号信号。
在这里插入图片描述

通过kill -l命令可以查看13对应的具体信号。

kill -l

在这里插入图片描述

由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE信号将子进程终止的。

管道的大小

管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?

方法一:使用man手册

根据man手册,在2.6.11之前的Linux版本中,管道的最大容量与系统页面大小相同,从Linux 2.6.11往后,管道的最大容量是65536字节。
在这里插入图片描述

然后我们可以使用uname -r命令,查看自己使用的Linux版本。
在这里插入图片描述

根据man手册,我使用的是Linux 2.6.11之后的版本,因此管道的最大容量是65536字节。

方法二:使用ulimit命令

其次,我们还可以使用ulimit -a命令,查看当前资源限制的设定。
在这里插入图片描述

根据显示,管道的最大容量是 512 × 8 = 4096 512\times8=4096512×8=4096 字节。

方法三:自行测试

这里发现,根据man手册得到的管道容量与使用ulimit命令得到的管道容量不同,那么此时我们可以自行进行测试。

前面说到,若是读端进程一直不读取管道当中的数据,写端进程一直向管道写入数据,当管道被写满后,写端进程就会被挂起。据此,我们可以写出以下代码来测试管道的最大容量。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0){ //使用pipe创建匿名管道
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //使用fork创建子进程
	if (id == 0){
		//child 
		close(fd[0]); //子进程关闭读端
		char c = 'a';
		int count = 0;
		//子进程一直进行写入,一次写入一个字节
		while (1){
			write(fd[1], &c, 1);
			count++;
			printf("%d\n", count); //打印当前写入的字节数
		}
		close(fd[1]);
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端

	//父进程不进行读取

	waitpid(id, NULL, 0);
	close(fd[0]);
	return 0;
}

可以看到,在读端进程不进行读取的情况下,写端进程最多写65536字节的数据就被操作系统挂起了,也就是说,我当前Linux版本中管道的最大容量是65536字节。
在这里插入图片描述

总结:

今天我们学习了Linux进程间通信的相关知识,了解了进程间通信介绍,管道等 。接下来,我们将继续学习Linux的其他知识。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何选择适合企业的数字化看板系统

数字化看板是一种数字化管理工具&#xff0c;它具有实时数据展示、任务分配与优先级管理、问题追踪与解决、供应链管理与协同以及数据分析与预测等功能。这些功能可以帮助企业提高生产效率、降低成本、优化资源配置并实现生产过程的透明化。 具体来说&#xff0c;数字化看板可以…

Navicat 使用

安装包请私信本人 软件安装 需要注意以下不要先运行navicat软件,先运行 在工具的第一个选项(1.Patch)里选择Backup&#xff0c;然后点击&#xff0c;Patch按钮&#xff0c;选择Navicat的安装位置中的navicat.exe文件 如果显示这样表示成功了 在这选择语言简体中文 点击Gener…

第五章 路由技术及应用

目录 5.1 直连路由概述 5.1.1 直连路由工作原理 5.1.2 直连路由配置 5.2 直连路由仿真 5.3 静态路由技术 5.3.1 静态路由定义 5.3.2 静态路由工作原理 5.3.3 静态路由配置 5.3.4 默认路由 (1) 默认路由概述 (2) 配置默认路由 (3) 默认路由应用场合&#xff1a;上网…

基于springboot实现疫苗接种管理系统项目【项目源码】计算机毕业设计

基于springboot实现疫苗接种管理系统演示 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的…

3+差异分析+PPI+预后+实验的生信思路,简单直接容易上手

今天给同学们分享一篇生信文章“Identification of hub genes and pathways in lung metastatic colorectal cancer”&#xff0c;这篇文章发表在BMC Cancer期刊上&#xff0c;影响因子为3.8。 结果解读&#xff1a; 原发性和肺转移性CRC组织之间的差异表达基因的鉴定 使用在…

Spring Boot EasyPOI 使用指定模板导出Excel

相信大家都遇到过&#xff0c;用户提出要把界面上的数据导成一个Excel&#xff0c;还得是用户指定的Excel格式&#xff0c;用原生的POI&#xff0c;需要自己去实现&#xff0c;相信是比较麻烦的&#xff0c;所以我们可以使用开源的EasyPOI. 先上个图&#xff0c;看看是不是大家…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之进程管理2》(4)

《Linux操作系统原理分析之进程管理2》》&#xff08;4&#xff09; 3 进程管理3.5 P、V操作3.5.1 信号量3.5.2 信号量的应用3.5.3 进程同步机制 3.6 进程通信3.6.1 消息通信3.6.2 信箱通信 3.7 死锁3.7.1 死锁的定义3.7.2 死锁产生的必要条件3.7.3 死锁的预防3.7.4 死锁的避免…

如何使用Omniverse Kit开发元宇宙

最近我研究了一些潜在的元宇宙开发平台。尽管Facebook上个月引发了关于元宇宙的最新一轮炒作&#xff0c;但另一家公司英伟达&#xff08;Nvidia&#xff09;在开发实际元宇宙平台方面走得更远。Nvidia的Omniverse自2019年以来一直在运行&#xff0c;因此在今天的帖子中&#x…

vscode中Chinese (Simplified)汉化无效解决方法

问题复现 之前已经下载了 Chinese (Simplified)插件并启用了&#xff0c;都是正常的中文简体。有时候打开vscode的时候&#xff0c;会发现汉化失效了&#xff0c;如图&#xff1a; 解决方法 依次点击 扩展&#xff08;Extensions&#xff09;— Chinese (Simplified) — 选…

SpringBoot使用DevTools实现后端热部署

&#x1f4d1;前言 本文主要SpringBoot通过DevTools实现热部署的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&…

WorkPlus移动数字化平台,助力企业全面掌控业务和生态

在移动化的时代&#xff0c;企业面临着将业务和生态纳入数字化平台的挑战。WorkPlus作为一款安全专属的移动数字化平台&#xff0c;成为企业业务和生态全面掌控的有力助手。它如同一艘强大的“航空母舰”&#xff0c;助力企业实现全面发展&#xff0c;从业务到生态&#xff0c;…

Linux安装Docker完整教程

Linux安装Docker完整教程 1.卸载旧版本的Docker&#xff08;可选&#xff09;2.安装Docker&#xff08;1&#xff09;yun安装&#xff08;2&#xff09;离线安装 3.启动Docker4.配置镜像加速5.Docker拉取镜像命令 参考链接 https://blog.csdn.net/m0_59196543/article/details/…

9.基于SpringBoot3+MybatisPlus定制化代码生成器类

我们在3.基于SpringBoot3集成MybatisPlus中讲到自定义代码生成器&#xff0c;但是往往遗留代码生成的类格式或者命名不符合要求&#xff0c;需要手工修改&#xff0c;但是当表很多时就比较头痛,所以我们自定义模板在进行代码生成 1. 新建MyTemplateEngine.java类 里面大多实现…

小型心仪什么牌子好又便宜?迷你洗衣机品牌推荐

相信不少人群为了方便清洗衣服&#xff0c;都会直接选择了大型的洗衣机。由于家里住的人多&#xff0c;所以对洗衣机的使用还是比较频繁&#xff0c;但内衣裤和其它衣服混合在一起清洗的话&#xff0c;或许大多数人都不太会接受的了&#xff0c;所以都是选择自己手工洗的&#…

【uniapp/uview1.x】u-upload 在 v-for 中的使用时, before-upload 如何传参

引入&#xff1a; 是这样一种情况&#xff0c;在接口获取数据之后&#xff0c;是一个数组列表&#xff0c;循环展示后&#xff0c;需要在每条数据中都要有图片上传&#xff0c;互不干扰。 分析&#xff1a; uview 官网中有说明&#xff0c;before-upload 是不加括号的&#xff…

springboot服务和python服务如何自定义启动banner

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 shigen最近在修改ruoyi的框架&#xff0c;看到了框架自带的banner图&#xff0c;就是一个不爽&#xff0c;于是…

AI大模型低成本快速定制法宝:RAG和向量数据库

文章目录 1. 前言2. RAG和向量数据库3. 论坛日程4. 购票方式 1. 前言 当今人工智能领域&#xff0c;最受关注的毋庸置疑是大模型。然而&#xff0c;高昂的训练成本、漫长的训练时间等都成为了制约大多数企业入局大模型的关键瓶颈。 这种背景下&#xff0c;向量数据库凭借其独特…

3.5 Windows驱动开发:应用层与内核层内存映射

在上一篇博文《内核通过PEB得到进程参数》中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息&#xff0c;本篇文章同样需要使用进程附加功能&#xff0c;但这次我们将实现一个更加有趣的功能&#xff0c;在某些情况下应用层与内核层需要共享一片内存…

freertos源码下载和目录结构分析

1、源码下载 下载网址&#xff1a;https://www.freertos.org/zh-cn-cmn-s/&#xff1b; 2、源码目录结构 3、关键的代码文件

全国行政区划2023年最新版

全国行政区划包含以下字段&#xff0c;行政区划第五级&#xff0c;省、市、县、乡镇、村。文章末尾已整理成sql文件。 父级行政代码,行政代码,邮政编码,区号,名称,简称,组合名,拼音,经度,纬度, 获取方式 关键词“行政区划”获取文件。 免费获取行政区划