【Linux进程通信 —— 管道】

Linux进程通信 —— 管道

  • 进程间通信介绍
    • 进程间通信的概念
    • 进程间通信的目的
    • 进程间通信的本质
    • 进程间通信的分类
  • 管道
    • 什么是管道
    • 匿名管道
        • 匿名管道的原理
        • pipe
        • 用fork来共享管道原理
        • 站在文件描述符角度-深度理解管道
        • 站在内核角度-管道本质
        • 管道读写规则
        • 管道的特点
        • 管道的四种特殊情况
        • 管道的大小
    • 命名管道
        • 命名管道的原理
        • 创建一个命名管道
        • 命名管道的打开规则
        • 用命名管道实现 serve&client 通信
        • 命名管道和匿名管道的区别

进程间通信介绍

进程间通信的概念

在 Linux 中,进程间通信(IPC,Inter-Process Communication)是指 不同进程之间交换数据和信息的一种机制。 这种通信可以是在同一台计算机上的不同进程之间,也可以是在不同计算机之间的进程之间。

进程间通信的目的

进程间通信(IPC) 的主要目的是实现不同进程之间的数据交换和协作,从而实现更复杂的任务和功能。以下是进程间通信的几个主要目的:

  1. 数据交换:进程间通信允许不同进程之间交换数据和信息。这些数据可以是简单的消息、文件、共享内存中的数据等。通过数据交换,不同进程可以共享信息,协作完成复杂的任务。

  2. 协作:进程间通信使得不同进程能够协同工作,共同完成某些任务。例如,一个进程负责生成数据,另一个进程负责处理数据,它们之间通过通信来协调工作。

  3. 资源共享:进程间通信可以实现共享资源,如共享内存、文件、设备等。多个进程可以同时访问和操作共享资源,从而提高系统的利用率和效率。

  4. 进程同步:进程间通信可以实现进程之间的同步操作,确保它们按照一定的顺序执行。例如,使用信号量来控制对共享资源的访问,或者使用消息队列来实现进程间的同步消息传递。

  5. 并发控制:进程间通信可以实现对并发访问的控制,避免竞态条件和数据不一致性。例如,通过信号量或互斥锁来控制对共享资源的访问,以确保数据的一致性和可靠性。

总的来说,进程间通信的目的是实现不同进程之间的数据交换、协作和同步,从而实现更复杂、更高效的系统功能和任务。它是操作系统中的重要概念,对于实现多任务处理、并发编程和分布式系统等方面具有重要意义。

进程间通信的本质

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

在一个正在运行的操作系统中,存在许多相互独立的进程,它们需要相互协作以确保操作系统的正常运行。为了实现这种协作,这些进程通过进程间通信来共享资源,即让不同的进程能够访问并操作同一份共享资源,从而实现数据共享和协作。
在这里插入图片描述

一个简单的例子是父子进程间的通信。假设父进程需要向子进程发送一个命令,子进程收到命令后执行相应的操作,并将结果返回给父进程。这里的通信可以通过管道、消息队列或共享内存来实现。父进程向管道写入命令,子进程从管道中读取命令并执行,然后将结果写回管道,父进程再从管道中读取结果。这样,父子进程之间就实现了简单的数据交换和通信。

进程间通信的分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

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

POSIXIPC

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

管道

什么是管道

管道(Pipe) 是一种用于进程间通信的机制,允许一个进程的输出直接成为另一个进程的输入。它主要用于在父进程和子进程之间或者在同时运行的两个进程之间进行通信。管道可以分为匿名管道(Anonymous Pipe)命名管道(Named Pipe) 两种类型。
在这里插入图片描述

匿名管道

匿名管道的原理

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

匿名管道是一种单向通信管道,只能在相关的父子进程间使用。它是通过调用pipe()系统调用创建的,具有读端写端 。匿名管道的数据流向是单向的,即数据只能从写端流入到读端。

在这里插入图片描述
注意:操作系统维护父子进程共享的文件资源时,并不会在父子进程间进行数据的写时拷贝。管道使用文件的概念,但操作系统并不会将进程通信的数据刷新到磁盘上,因为这样做既会涉及到IO操作从而降低效率,也是没有必要的。换言之,这些文件通常只存在于内存中,而不会被写入到磁盘上。

pipe

pipe函数是Unix/Linux操作系统提供的一个系统调用,用于创建一个匿名管道。它的原型如下:

#include <unistd.h>int pipe(int pipefd[2]);
  • 参数pipefd是一个包含两个整数元素的数组,用于返回新创建的管道的文件描述符。pipefd[0]用于从管道中读取数据,pipefd[1]用于向管道中写入数据。

  • 调用 成功 时,返回值为 0 ;调用 失败 时,返回值为 -1 ,并设置全局变量errno来指示错误类型。

用fork来共享管道原理

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

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

3、父进程关闭写端,子进程关闭读端。

在这里插入图片描述

  1. 管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。
  2. 从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取。
站在文件描述符角度-深度理解管道

在这里插入图片描述

站在内核角度-管道本质

在这里插入图片描述

管道读写规则

pipe2函数与pipe函数类似,同样用于创建一个匿名管道。它的原型如下:

#include <unistd.h>int pipe2(int pipefd[2], int flags);

pipe 函数不同的是,pipe2 函数允许通过参数 flags 设置一些附加的选项,以控制管道的行为。常用的选项包括:

  • O_CLOEXEC:在父进程执行 fork 创建子进程时,子进程会自动关闭父进程中不需要的文件描述符。这可以通过设置 O_CLOEXEC 标志来实现,以确保在子进程中关闭管道的文件描述符。
  • O_NONBLOCK:设置管道的读取和写入操作为非阻塞模式。在非阻塞模式下,读取和写入操作会立即返回,不会等待直到管道中有数据可读或有空间可写。

这两个选项可以通过按位或运算组合使用。例如,要创建一个非阻塞的管道并在父进程中关闭不需要的文件描述符,可以将 flags 设置为 O_NONBLOCK | O_CLOEXEC

pipe2 函数的返回值和 pipe 函数类似,成功时返回0,失败时返回-1,并设置全局变量 errno 来指示错误类型。

管道的特点
  • 单向性:管道是单向的,只能用于单向数据流的传输。通常有两种类型的管道:单向管道和双向管道。单向管道只能实现单向数据流的传输,而双向管道则可以实现双向数据流的传输。

  • 半双工:管道是半双工的,即同一时间只能有一个方向的数据流动。在一个管道中,数据只能单向流动,要么从父进程流向子进程,要么从子进程流向父进程。

  • 适用于有亲缘关系的进程:管道通常用于具有亲缘关系的进程之间进行通信,例如父子进程之间。因为管道是通过 fork 系统调用创建的,只有具有亲缘关系的进程才能共享同一个管道。

  • 有限缓冲区:管道具有有限的缓冲区,因此在读取端没有读取数据时,写入端会被阻塞。当管道的缓冲区已满时,写入端也会被阻塞,直到缓冲区有足够的空间来容纳写入的数据。

  • 不支持随机访问:管道是顺序访问的,不支持随机访问。也就是说,只能按照数据写入的顺序依次读取数据,不能直接定位到某个位置读取数据。

  • 自动关闭:在进程终止时,管道会自动关闭。当所有指向管道的文件描述符都被关闭时,管道将被系统自动释放。

管道的四种特殊情况

管道的四种特殊情况:

  1. 写端进程不写,读端进程一直读

    • 情况描述:写端进程不向管道写入数据,但读端进程一直尝试读取数据。
    • 表现:读端进程会被挂起,直到管道中有数据可读。
    • 解释:读端进程在读取数据时,如果管道中没有数据可读,则会被挂起,直到有数据可供读取。
  2. 读端进程不读,写端进程一直写

    • 情况描述:写端进程不断向管道写入数据,但读端进程没有读取数据。
    • 表现:当管道被写满后,写端进程会被挂起,直到管道中的数据被读取后才会继续写入。
    • 解释:当写端不断写入数据,而读端没有读取时,管道会被写满。此时写端进程会被挂起,直到读端读取数据释放空间。
  3. 写端进程写完后关闭写端

    • 情况描述:写端进程将数据写入管道后关闭了写端。
    • 表现:读端进程将管道中的数据读完后,继续执行后续代码,而不会被挂起。
    • 解释:写端进程关闭写端后,读端进程读取完管道中的数据后会收到EOF,继续执行后续代码。
  4. 读端进程关闭读端

    • 情况描述:读端进程关闭了读端,但写端进程仍在向管道写入数据。
    • 表现:操作系统会将写端进程杀掉。
    • 解释:当读端进程关闭读端后,写端继续向管道写入数据,此时操作系统会向写端进程发送信号,通知其管道已关闭,然后将写端进程杀掉。

示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {int pipefd[2];pid_t pid;char buffer[10];// 创建管道if (pipe(pipefd) == -1) {perror("pipe");exit(EXIT_FAILURE);}// 创建子进程pid = fork();if (pid == -1) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) {  // 子进程// 关闭写端,子进程从管道中读取数据close(pipefd[1]);// 读取数据read(pipefd[0], buffer, sizeof(buffer));printf("Child Process: Data read from pipe: %s\n", buffer);// 关闭读端close(pipefd[0]);exit(EXIT_SUCCESS);} else {  // 父进程// 关闭读端,父进程向管道中写入数据close(pipefd[0]);// 写入数据write(pipefd[1], "Hello", 5);printf("Parent Process: Data written to pipe: Hello\n");// 关闭写端close(pipefd[1]);// 等待子进程结束wait(NULL);exit(EXIT_SUCCESS);}return 0;
}

在这个示例中,父进程创建了一个管道并生成了一个子进程。父进程通过管道将字符串"Hello"写入管道,而子进程则从管道中读取数据并打印到控制台上。在这个过程中,我们可以观察到以下特殊情况:

  • 写端进程不写,读端进程一直读:在这个示例中,如果父进程不写入数据,子进程会一直阻塞在读取管道的操作,直到有数据可读。
  • 读端进程不读,写端进程一直写:如果子进程不读取管道中的数据,而父进程持续写入数据,那么管道会被写满,父进程的写操作会阻塞,直到有空间可写。
  • 写端进程写完后关闭写端:在父进程写入数据后,关闭了写端。子进程读取完数据后,管道的读端会返回EOF,子进程继续执行后续代码。
  • 读端进程关闭读端:在子进程读取完数据后,关闭了读端。如果父进程继续写入数据,操作系统会向父进程发送信号,告知读端已关闭,然后父进程被杀死。
管道的大小

我们可以利用管道四种特殊情况中的第二种 读端进程不读,写端进程一直写 来验证:
让读端不读,写端进程一直写,直到写端被写满无法继续写时,则可以得到管道具体的大小。

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>const int size = 1024;//写
void SubProcessWrite(int wfd)
{int pipesize = 0;std::string message = "father, i am your son process!";while(true){char c = 'A';write(wfd,&c,1);std::cout << "pipesize = " << ++pipesize << std::endl;}
}//读
void FatherProcessRead(int rfd)
{// sleep(500);char inbuffer[size];    //用于储存读到的信息的缓冲区while(true){// sleep(4);ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);if(n > 0){inbuffer[n] = 0;std::cout << "father get message"  << inbuffer << std::endl;}}
}int main()
{//创建管道int pipefd[2];int n = pipe(pipefd);   //输出型参数if(n != 0){std::cerr << "errno: " << errno << ":" << "errstring: " << strerror(errno) << std::endl;return 1;}//pipefd[0] ->0 -r 嘴巴 读   pipefd[1] ->1 -w 🖊 写std::cout << "pipefd[0]: " << pipefd[0] << " " << "pipefd[1]: "<< pipefd[1] << std::endl;//创建子进程pid_t id = fork();if(id == 0){std::cout << "子进程已经关闭了读fd,保留写fd,准备开始写消息了。" << std::endl;//关闭不必要的fd//子进程//writeclose(pipefd[0]);SubProcessWrite(pipefd[1]);close(pipefd[1]);exit(0);}//父进程//readstd::cout << "父进程已经关闭了写fd,保留读fd,准备开始读消息了。" << std::endl;close(pipefd[1]);// FatherProcessRead(pipefd[0]);// close(pipefd[0]);int status = 0;pid_t rid = waitpid(id,&status,0);if(rid > 0){std::cout << "wait child process done, exit sig:" << (status&0x7f) << std::endl;std::cout << "wait chile process done, exit code(ign)" << ((status>>8)&0xff) << std::endl;}return 0;
}

在这里插入图片描述
可知:我当前Linux版本中管道的最大容量是65536字节。

命名管道

命名管道的原理

命名管道的原理

命名管道是一种特殊类型的文件系统对象,它允许不同进程通过文件来进行通信。其原理基于文件系统的特性,它实际上是一个由操作系统维护的特殊文件,具有磁盘上的路径名,可以在文件系统中找到。与匿名管道不同,命名管道可以通过文件系统中的路径名进行访问,从而允许不同进程在不同的时间段内进行通信。

创建一个命名管道

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

qq@iZ0jl65jmm6w9evbwz2zuoZ:~/bt111/Linux/5_09/test$ mkfifo fifo

在这里插入图片描述
可以看到,创建出来的文件的类型是p,代表该文件是命名管道文件。
在这里插入图片描述
我们这里通过shell简单的进行两个进程的通信,这里可以看到左边的进程使用while done 来向fifo写入数据,右边进程来读取fifo的信息。

在这里插入图片描述


我们也可以使用mkfifo函数来创建命名管道

mkfifo函数
mkfifo函数是一个用于创建命名管道的Unix/Linux函数。他的原型如下:

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);

参数:

  • pathname:指定要创建的命名管道的路径名。
  • mode:指定创建的管道的权限模式(权限位)。三位八进制数字。具体的权限可查看这篇文章Linux当中的权限问题
  1. 创建命名管道: 首先,需要使用特定的系统调用(如mkfifo函数)在文件系统中创建一个命名管道。这个系统调用将在文件系统中创建一个特殊类型的文件,其类型为FIFO(先进先出),并分配一个唯一的路径名。

  2. 进程打开管道: 创建命名管道后,进程可以通过打开文件系统中的路径名来访问该管道。进程可以像打开普通文件一样打开命名管道,并且可以使用文件描述符来进行读取和写入操作。

  3. 进程读写管道: 一旦管道被打开,进程就可以使用相应的文件描述符进行读取和写入操作。写入到管道的数据会按照先进先出的顺序被读取出来。

  4. 进程关闭管道: 当进程不再需要使用管道时,应该关闭相应的文件描述符,以释放系统资源。

总的来说,命名管道的原理是利用文件系统的特性创建一个特殊类型的文件对象,允许不同进程通过文件进行通信。这种通信方式具有持久性和可靠性,不同进程可以在不同的时间段内进行通信,而不受进程生命周期的限制。

命名管道的打开规则
  • 如果当前打开操作是为读而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
    • O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
    • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
用命名管道实现 serve&client 通信

我们先来写一个管道文件,如何在serve 和 client 端分别打开这个管道文件。
在这个管道文件中分别封装 读操作接口写操作接口 以供使用。

namePipe.hpp

#pragma once#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>const std::string comm_path = "./myfifo";#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BassSize 4096class NamePiped
{
private:bool OpenNamePipd(int mode)     //打开对应管道  mode决定打开方式{_fd = open(_fifo_path.c_str(),mode);if(_fd < 0){return false;}return true;}public:NamePiped(const std::string &path, int who):_fifo_path(path),_id(who){int res = mkfifo(_fifo_path.c_str(),0666);      //创建命名管道  名字是myfifo if(res != 0){//创建失败perror("mkfifo");}std::cout << "Creater create named pipe" << std::endl;}//创建打开接口//读bool OpenForRead(){return OpenNamePipd(Read);}//写bool OpenForWrite(){return OpenNamePipd(Write);}//write     输入项参数int WriteNamePipe(const std::string &in){return write(_fd,in.c_str(),in.size());}//read      输出型参数int ReadNamePipe(std::string *out){char buffer[BassSize];int n = read(_fd,buffer,sizeof(buffer));if(n > 0){buffer[n] = 0;*out = buffer;}return n;}~NamePiped(){if(_id == Creater){int res = unlink(_fifo_path.c_str());if(res != 0){perror("unlink");}std::cout << "creater free named pipe" << std::endl;}if(_fd != DefaultFd)    close(_fd);}
private:const std::string _fifo_path;   //命名管道的地址int _fd;    //fd  文件描述符int _id;    //id  用于判断Creater | User
};

client.cc

#include "namePipe.hpp"//write 写
int main()
{NamePiped fifo(comm_path,User);if(fifo.OpenForWrite()){std::cout << "client open named pipe done" << std::endl;while(true){std::cout << "Pleanse Enter>";std::string message;std::getline(std::cin,message);fifo.WriteNamePipe(message);}}return 0;
}

server.cc

#include "namePipe.hpp"//read 读
int main()
{NamePiped fifo(comm_path,Creater);if(fifo.OpenForRead()){std::cout << "server open named pipe done" << std::endl;while(true){std::string message;int n = fifo.ReadNamePipe(&message);if(n > 0){std::cout << "Client say > " << message << std::endl;}else if(n == 0){std::cout << "Client quit, Server Tool" << std::endl;}else{std::cout << "fifo.ReadNamedPipe Error" << std::endl;break;}}}return 0;
}

演示:

在这里插入图片描述

命名管道和匿名管道的区别
  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,由open函数打开。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。

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

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

相关文章

短视频矩阵系统/源码----可视化剪辑技术独家开发

现阶段市面上大多矩阵软件都非常程序化且需要使用者具有较强的逻辑思维能力或剪辑经验&#xff0c;这使得一些个人、团队、企业在使用时无形中增加了学习成本&#xff0c;剪辑出来的效果大多不尽如人意&#xff0c;发出来的视频没有流量&#xff0c;根本达不到预期效果。 如何提…

表白成功率百分百的向女朋友表白网页源代码,向女友表白HTML源代码

表白成功率百分百的向女朋友表白网页源代码&#xff0c;向女友表白HTML源代码 效果&#xff1a; 完整代码下载地址&#xff1a;向女友表白HTML源代码 <!DOCTYPE html> <!--STATUS OK--> <html><head><meta http-equiv"Content-Type" c…

嵌入式学习-PWM输出比较

简介 PWM技术 输出比较框图介绍 定时器部分 比较器控制部分 输出控制部分 相关寄存器

分布式任务调度框架xxl-job使用手册

官网地址和文档地址&#xff1a;https://www.xuxueli.com/xxl-job/ 一、快速入门 1.1 下载源码 https://github.com/xuxueli/xxl-job https://gitee.com/xuxueli0323/xxl-job 下载完成后有以下模块 1.2 初始化数据库 官方指定mysql8.0&#xff0c;但我是mysql5.7 执行/xxl…

【电路笔记】-有源低通滤波器

有源低通滤波器 文章目录 有源低通滤波器1、概述2、有源低通滤波器2.1 一阶低通滤波器2.2 带放大功能的有源低通滤波器3、有源低通滤波器示例4、二阶低通有源滤波器通过将基本的 RC 低通滤波器电路与运算放大器相结合,我们可以创建一个具有放大功能的有源低通滤波器电路。 1、…

leetcode-字符串变形-104

题目要求 思路 1.首先根据ASCII的规则&#xff0c;把字符串大小写替换&#xff0c;空格保持不变 2.将整个字符串进行翻转 3.以空格为区间&#xff0c;将区间内的字符串进行翻转&#xff0c;其中翻转的函数reverse() 代码实现 class Solution { public:string trans(string s…

基于springboot+vue+Mysql的交流互动系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【C语言】必备Linux命令和C语言基础

&#x1f31f;博主主页&#xff1a;我是一只海绵派大星 &#x1f4da;专栏分类&#xff1a;嵌入式笔记 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、文件和目录相关命令 Linux 的文件系统结构 文件系统层次结构标准FHS pwd命令 ls 列目录内容 文件的权限 c…

Anaconda下载安装

看到这篇文章的同学们&#xff0c;说明你们是要下载Anaconda&#xff0c;这篇文章讲的就是下载安装教程。 Anaconda下载网址&#xff1a; Download Now | Anaconda 根据我们需要的系统版本下载&#xff0c;我的电脑是window&#xff0c;所以选择第一个&#xff0c;如下图&am…

WebSocket or SSE?即时通讯的应用策略【送源码】

最近在研究H5推送&#xff0c;发现除了我们常用的WebSocket以外&#xff0c;其实还有一种协议也能实现H5推送&#xff0c;那就是SSE协议。 而且&#xff0c;当前主流的大模型平台&#xff0c;比如ChatGPT、通义千问、文心一言&#xff0c;对话时采用的就是SSE。 什么是SSE协议…

05-10 周五 推理是什么

05-10 周五 推理是什么 时间版本修改人描述2024年5月10日10:13:54V0.1宋全恒新建文档2024年5月13日11:08:42V1.0宋全恒填充了训练和推理的定义&#xff0c;并且对于推理加速的方面进行了详细的介绍 简介 最近要坐推理时的动态量化&#xff0c;因此&#xff0c;需要认真理解一下…

20232906 2023-2024-2 《网络与系统攻防技术》第十次作业

20232906 2023-2024-2 《网络与系统攻防技术》第十次作业 1.实验内容 一、SEED SQL注入攻击与防御实验 我们已经创建了一个Web应用程序&#xff0c;并将其托管在http://www.seedlabsqlinjection.com/&#xff08;仅在SEED Ubuntu中可访问&#xff09;。该Web应用程序是一个简…