Linux之进程间通信(管道)

目录

一、进程间通信

1、进程间通信的概念

2、进程间通信的目的

3、进程间通信的分类

二、管道

1、管道基本介绍

2、匿名管道

3、命名管道


一、进程间通信

1、进程间通信的概念

什么是进程间通信?

我们在学习了进程的相关知识后,知道,进程的运行是具有独立性的,每个进程都有自己独立的PCB,也都有只属于自己的地址空间,进程之间是互不干扰的。

所以说,进程之间要通信的话,难度是非常大的。

进程间通信的本质:操作系统需要直接或者间接给通信双方的进程提供一段 “内存空间”,并且要让不同的进程看到同一份资源(内存空间)。而这个内存空间不应该属于任何一个进程,而应该由操作系统来维护。

所以,进程间通信的最大成本就是:操作系统在设计的原生层面,进程是互相独立的,但是现在需要让它们之间进行通信。那么应该如何让不同的进程看到同一份资源。

2、进程间通信的目的

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

3、进程间通信的分类

~ 管道:1、匿名管道pipe       2、命名管道 

~ System V IPC:1、System V 消息队列     2、System V 共享内存    3、System V 信号量

~ POSIX IPC:1、消息队列     2、共享内存     3、信号量    4、互斥量     5、条件变量    6、读写锁

进程间通信的必要性:如果只是使用单进程,那么也就无法使用并发能力,更加无法实现多进程协同工作。

本篇文章我们来具体讲一讲,管道的相关知识。

二、管道

1、管道基本介绍

什么是管道?

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

管道只能进行单向通信,用来传输资源:数据。

我们按下面的步骤来分析:

1、首先,父进程分别以读和写的方式,打开了一个文件。

2、然后,父进程fork创建了一个子进程,我们知道,子进程会继承父进程,指向同一个文件。

3、我们规定,父进程对文件写入,子进程对文件读取。关闭父子进程各自不需要的功能。即:父进程关闭读的文件描述符,子进程关闭写的文件描述符。

最终,通过该文件就实现了单向通信。这就是管道。管道的本质其实就是内存级文件。所以两个进程间通信的数据,不需要刷新到磁盘里。

为什么能够这样做?

文件有属于自己的内核缓冲区,所以父进程和子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以通过文件缓冲区读取,此时就完成了进程间通信。

管道一般用来进行具有血缘关系的进程(父子进程)之间的通信。 

管道分为匿名管道和命名管道。下面我们就来具体讲一讲它们的内容。

2、匿名管道

通过文件名区分文件,但是如果当前进程的文件没有名字,这样的内存级文件称为匿名管道。匿名管道能用来父进程和子进程之间进行进程间通信。

匿名管道文件在磁盘上也没有实体,它只存在于内存中。

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

SYNOPSIS
       #include <unistd.h>
       int pipe(int pipefd[2]);
DESCRIPTION
    pipe() creates a pipe,pipefd[0]  refers  to  the  read end of the pipe.  pipefd[1] refers to the write end of the pipe.
RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

我们首先创建管道文件,打开读写端:在pipefd数组中,pipefd[0]代表读,pipefd[1]代表写。

#include <iostream>
#include <cassert>
#include <unistd.h>

using namespace std;

int main()
{
    // 创建管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;

    cout << "pipefd[0]: " << pipefd[0] << endl;
    cout << "pipefd[1]: " << pipefd[1] << endl;

    return 0;
}

接着,我们fork创建子进程, 然后,我们关闭父子进程不需要的文件描述符,完成通信框架的建立:

#include <iostream>
#include <cassert>
#include <unistd.h>

using namespace std;

int main()
{
    // 创建管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;

    pid_t id = fork();
    assert(id != -1);
    if(id == 0)
    {
        //子进程
        close(pipefd[1]);

        exit(0);
    }

    //父进程
    close(pipefd[0]);

    return 0;
}

最后,我们通过父进程给子进程发送消息来检测通信:

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main()
{
    // 创建管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;

    // 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        // 子进程
        close(pipefd[1]);
        char buffer[1024];
        while (true)
        {
            ssize_t mess = read(pipefd[0], buffer, sizeof(buffer));
            if (mess > 0)
            {
                buffer[mess] = 0;
                cout << "father process say: " << buffer << endl;
            }
        }

        exit(0);
    }

    // 父进程
    close(pipefd[0]);
    string message = "i am father ->";
    char buff[1024];
    int count = 0;
    while (true)
    {
        snprintf(buff, sizeof(buff), "%s[%d]: %d", message.c_str(), getpid(), count++);
        write(pipefd[1], buff, sizeof(buff));
        sleep(1);
    }

    pid_t ret = waitpid(id, nullptr, 0);
    assert(ret > 0);
    (void)ret;

    close(pipefd[1]);
    return 0;
}

管道的特点:

1、管道是用来进行具有血缘关系的进程间的通信,常用于父子进程。

2、管道能够让进程间协同,提供了访问控制。

3、管道是面向字节流的。

4、管道的生命周期随进程,进程退出,管道释放。

5、管道是单向通信,是半双工通信的特殊情况。

6、内核会对管道操作进行互斥与同步。

下面我们针对管道的特点2,来进行演示:

~ 读快写慢

其余代码不变,我们仅让父进程在每次写入后,休眠10秒再写。

我们发现,父进程休眠期间,子进程会等待父进程写入后再读取。(如果管道中没有数据,读端在读,此时默认会直接阻塞当前正在读取的进程)

~ 读慢写快

我们让父进程不停地写入。

子进程休眠5秒后再读取。

拿着管道读端不读,写端一直在写:写端往管道里写,而管道是有大小的,不断往写端写,会被写满。 

管道是固定大小的缓冲区,当管道被写满,就不能再写了。此时写端会阻塞。而休眠结束后,子进程就会开始读取,这时父进程才能继续写入。

~ 关闭写入端

父进程写入端关闭:写完第一次后,父进程休眠6秒,然后结束循环,关闭写入端。

 父进程作为写入的一方,关闭了写入端,子进程作为读取的一方,read会返回0,表示读到了文件的结尾。这时我们让子进程结束循环,并退出。

~ 关闭读端

管道是单向的:读端关闭,再写入就没有意义了,这时OS会终止写端,会给写进程发送信号,终止写端。

为了方便观察,我们让父进程读取,子进程写入。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int pipefd[2] = { 0 };
	if (pipe(pipefd) < 0)
    { 
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); 
	if (id == 0){
		//child
		close(pipefd[0]); 
		const char* msg = "hello father, I am child...";
		int count = 10;
		while (count--){
			write(pipefd[1], msg, strlen(msg));
			sleep(1);
		}
		close(pipefd[1]);
		exit(0);
	}
	//father
	close(pipefd[1]); 
	close(pipefd[0]); 
	int status = 0;
	waitpid(id, &status, 0);
	printf("child get signal:%d\n", status & 0x7F); 
	return 0;
}

操作系统向子进程发送的是SIGPIPE信号将子进程终止。 

3、命名管道

上面我们所讲的匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
而如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件。

一个进程打开了一个文件后,Linux内核里会有文件的struct file结构。如果还有第二个进程要打开这个文件,那么只要路径相同,那么两个进程打开的文件一定是同一个且是唯一的,那么第二个进程不需要继续创建struct file对象,因为OS会识别到打开的文件被打开了。

此时两个进程就看到了同一份资源,该文件也不需要把数据刷新到磁盘上去,不需要IO。

所以说,命名管道是真实存在于系统路径下的,只是它只有属性,没有内容。它能够使用open,close函数被打开或关闭。

mkfifo:也可以作为命令直接在指定路径下创建命名管道。mkfifo   命名管道名称

NAME
       mkfifo - make FIFOs (named pipes)

SYNOPSIS
    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);
RETURN VALUE
       On success mkfifo() returns 0.  In the case of an error, -1 is returned (in which case, errno is set appropriately).

下面我们就来使用一下:我们有三个文件,server.cc(读取端) ,client.cc(写入端) 是两个源文件,它们运行后会是两个不同且无关系的进程,我们来进行它们之间的通信。comm.h是一个头文件。

comm.h

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstdio>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

using namespace std;

string ipcPath = "./fifo.ipc";
#define MODE 0666
#define SIZE 128

server.cc

#include "comm.hpp"

int main()
{
    // 1.创建命名管道
    int ret = mkfifo(ipcPath.c_str(), MODE);
    if (ret == -1)
    {
        perror("mkfifo");
        exit(1);
    }

    // 2.正常文件操作:通信
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(2);
    }
    char buffer[SIZE];
    while (true)
    {
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            cout << "client say :" << buffer << endl;
        }
        else if (s == 0)
        {
            cerr << "read the end of file ->  client quit, server quit too" << endl;
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }

    close(fd);
    return 0;
}

client.cc

#include "comm.hpp"

int main()
{
    // 1.打开命名管道
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if (fd < 0)
    {
        perror("open");
        exit(1);
    }

    string buffer;
    while (true)
    {
        cout << "please enter message ->" << endl;
        getline(cin, buffer);
        write(fd, buffer.c_str(), buffer.size());
    }

    close(fd);
    return 0;
}

通信演示:

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

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

相关文章

Vue记录

vue2、vue3记录 vue2记录 经典vue2结构 index.vue&#xff1a; <template><div>...</div> </template><script>import method from "xxx.js"import component from "xxx.vue"export default {name: "ComponentName&…

Vue3.0性能提升主要是通过哪几方面体现的?

文章目录 一、编译阶段diff算法优化静态提升事件监听缓存SSR优化 二、源码体积三、响应式系统参考文献 一、编译阶段 回顾Vue2&#xff0c;我们知道每个组件实例都对应一个 watcher 实例&#xff0c;它会在组件渲染的过程中把用到的数据property记录为依赖&#xff0c;当依赖发…

Cloudreve存储策略-通过从机存储来拓展容量

Sham的云服务器是搬瓦工最低低低配的&#xff0c;1H 0.5G不说&#xff0c;硬盘容量也只有10g&#xff0c;说实话&#xff0c;装了宝塔面板和服务器套件后&#xff0c;基本满了&#xff0c;这时又想在云服务器上打个网盘用于下载、存储&#xff0c;这时就需要拓展硬盘&#xff0…

Redis 面试题 | 01.精选Redis高频面试题

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

HCIA——22DNS:DNS层次域名空间、域名服务器、域名解析的原理

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

基于springboot+vue的母婴商城系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目背景…

第二课:BERT

文章目录 第二课&#xff1a;BERT1、学习总结&#xff1a;为什么要学习BERT&#xff1f;预训练模型的发展历程BERT结构BERT 输入BERT EmbeddingBERT 模型构建BERT self-attention 层BERT self-attention 输出层BERT feed-forward 层BERT 最后的Add&NormBERT EncoderBERT 输…

c++ mysql数据库编程(linux系统)

ubuntu下mysql数据库的安装 ubuntu安装mysql&#xff08;图文详解&#xff09;-CSDN博客https://blog.csdn.net/qq_58158950/article/details/135667062?spm1001.2014.3001.5501 项目目录结构 数据库及表结构 public.h //打印错误信息 #ifndef PUBLIC_h #define PUBLIC_H…

Centos使用Docker搭建自己的Gitlab(社区版和设置汉化、修改密码、设置SSH秘钥)

根据我的经验 部署Gitlab&#xff08;社区版&#xff09; 至少需要2核4g的服务器 带宽3~4M 1. 在自己电脑上安装终端&#xff1a;宝塔ssl终端 或者 FinalShell&#xff0c;根据喜好安装即可 http://www.hostbuf.com/t/988.html http://www.hostbuf.com/downloads/finalshell_w…

macOS磁盘管理工具Paragon Hard Disk Manager,轻松且安全的改变磁盘分区

Paragon Hard Disk Manager mac版是Macos上一款磁盘管理工具&#xff0c;可以帮助你轻松而且安全的随意改变磁盘分区的大小和各种分区参数&#xff0c;作为mac磁盘分区工具也是游刃有余&#xff0c;同时在找回数据的时候也非常容易&#xff0c;并且不会损坏原来的数据&#xff…

项目解决方案:多地医馆的高清视频监控接入汇聚联网

目 录 一、背景 二、建设目标及需求 1.建设目标 2.现状分析 3.需求分析 三、方案设计 1.设计依据 2.设计原则 3.方案设计 3.1 方案描述 3.2 组网说明 四、产品介绍 1.视频监控综合资源管理平台介绍 2.视频录像服务器和存储 2.1概述 2.2存储设计 …

【工具】使用ssh进行socket5代理

文章目录 shellssh命令详解正向代理&#xff1a;反向代理&#xff1a;本地 socks5 代理 shell ssh -D 3333 root192.168.0.11 #输入密码 #3333端口已经使用远程机进行转发设置Windows全局代理转发 socks127.0.0.1 3333如果远程机为公网ip&#xff0c;可通过搜索引擎查询出网…

软件资源管理下载系统全新带勋章功能 + Uniapp前端

测试环境&#xff1a;php7.1。ng1.2&#xff0c;MySQL 5.6 常见问题&#xff1a; 配置好登录后转圈圈&#xff0c;检查环境及伪静态以及后台创建好应用 上传图片不了&#xff0c;检查php拓展fileinfo 以及public文件权限 App个人主页随机背景图&#xff0c;在前端uitl文件…

【数学笔记】集合及简要逻辑

集合 基础简要逻辑集合间的关系与运算 基础 集合定义&#xff1a;把一些能够确定的不同对象组成的整体叫做一个集合&#xff0c;每个对象叫做元素。集合记法&#xff1a;一般用大写字母 A , B , C . . . . . . A,B,C...... A,B,C......表示集合&#xff0c;小写字母 a , b ,…

GD32E230C8T6《调试篇》之 (软件) IIC通信(主机接收从机) + GN1650驱动芯片 + 按键 + 4位8段数码管显示 (成功)

GD32E230C8T6《调试篇》之 &#xff08;软件&#xff09; IIC通信 GN1650驱动芯片 4位8段数码管显示&#xff08;成功&#xff09; IIC是什么IIC简介1&#xff09;IIC总线物理连接2&#xff09;IIC时序协议 按键扫描代码1&#xff09;DIG2短按只一次&#xff0c;长按超过1s 一…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型、扩散模型、视觉导航

专属领域论文订阅 VX 扫嘛关注{晓理紫}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 分类: 大语言模型LLM视觉模型VLM扩散模型视觉导航具身智能&#xff0c;机器人强化学习开放词汇&#xff0c;检测分割 [晓理紫]每日论…

六、数组(1)一维数组

所谓数组&#xff0c;就是一个集合&#xff0c;里面存放了相同类型的数据元素 特点1&#xff1a;数组中每个数据元素都是相同的数据类型 特点2&#xff1a;数组是由连续的内存位置组成的 一、一维数组的定义方式 1、数据类型 数组名[数组长度]; 2、数据类型 数组名[数组长度…

谷歌浏览器通过network模拟HTTP中的GET/POST请求获取response

1、F12打开network选中需要模拟的方法Copy->Copy as fetch 2、通过AI帮你进行转换一下调用格式 原代码 fetch("https://mp.amap.com/api/forward/aggregate?mtop.alsc.kbt.intergration.toolkit.call.queryCallBlockInfo", {"headers": {"acce…

Latex绘图

排查Latex报错 “Command \csubfigure already defined” 这个可以通过添加如下命令&#xff1a; \usepackage{subfig} \usepackage{subfloat} ..... \begin{figure*}[h] \centering \subfloat[subfloat title]{ \label{fig:subfig:a} \includegraphics[scale0.7]{Figs/.....…

python-基础篇-函数

文章目录 函数基础目标01. 函数的快速体验1.1 快速体验 02. 函数基本使用2.1 函数的定义2.2 函数调用2.3 第一个函数演练思考 2.4 PyCharm 的调试工具2.5 函数的文档注释 03. 函数的参数3.1 函数参数的使用3.2 参数的作用3.3 形参和实参 04. 函数的返回值05. 函数的嵌套调用函数…