• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

[Linux]进程间通信上篇——匿名管道管道原理,实现展示,端口情况探究

武飞扬头像
bang___bang_
帮助1

学新通

学新通  


 

目录

 1️⃣进程间通信 

🍙进程间通信目的

🍙进程间通信分类

🍙进程间通信的技术背景及必要性

🍙进程间通信的本质理解

2️⃣管道

🍙什么是管道

🍙匿名管道

🍥匿名管道原理

🍥匿名管道创建pipe

🍥匿名管道四种情况探究

🍥匿名管道读写规则

🍥匿名管道特定总结


1️⃣进程间通信 

🍙进程间通信目的

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

🍙进程间通信分类

🍘管道
  • 匿名管道pipe
  • 命名管道
🍘System V IPC
  • System V 消息队列
  • System V 共享内存
  • System V 信号量
🍘POSIX IPC
  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

🍙进程间通信的技术背景及必要性

        进程具有独立性的。虚拟地址空间 页表 保证进程运行的独立性(进程内核数据结构 进程的代码和数据)

        通信成本会比较高!(因为进程独立性很强!)

🍣必要性:

        单进程无法使用进程间的并发能力!无法实现多进程之间的协同工作!所以需要进程间通信来使得多进程进行协同(进程间通信不是手段,是目的!)

🍙进程间通信的本质理解

进程间通信的本质:先让不同的进程看到同一份资源(内存空间)

        进程间通信的前提,首先需要让不同的进程看到同一块资源(特定的结构组织的)

        所以所谓的进程看到同一块资源,属于哪一个进程呢?不能隶属于任何一个进程,而应该更强调共享。

2️⃣管道

🍙什么是管道

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

生活中我们见过的管道有:水管、石油管道、天然气管道等等。

学新通在Linux中也有管道,我们把这种将资源从一端送到另一端的数据流称为管道!!

特点:只能单向通信(半双工),传输的都是资源 -->数据!

🍙匿名管道

如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。

🌰在shell中输入命令:ls -l | grep my,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在my的信息的字符串(以行为单位)打印在屏幕上。

学新通

🍥匿名管道原理

通过父子进程继承关系,再将文件描述符关闭,实现一端写,一端读,就是匿名管道。

        1.父进程以读写方式打开文件。父进程fork创建子进程,(进程具有独立性)子进程要拷贝一份PCB结构,PCB中包含了files_struct结构,files_struct中有一个指向struct file(文件)的指针数组,而文件描述符就是这个数组的下标。

        2.在拷贝后,子进程也就有了指向struct file(文件)的对应数组元素下标(文件描述符)。

        3.而struct file(文件)是独属于文件的,和进程没有关系,也就不用拷贝,也就是说此时父子进程公共区域就是 struct file。(实现不同进程看到同一份资源)

        4.write是系统调用接口,会将数据放在内核缓冲区,底层定期刷新缓冲区将内容写到磁盘。

        5.为了实现半双工的通信方式,一端读,一端写,关闭父子进程不需要的文件描述符。

学新通
流程图

🍥匿名管道创建pipe

学新通

int pipe(pipefd[2]);

参数:(输出型参数,通过参数找到打开的2个fd)文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端

返回值:成功返回0,失败返回错误码。

🍥匿名管道四种情况探究

🌰匿名管道实例:(子进程读取,父进程写入)

  1.  
    #include<iostream>
  2.  
    #include<unistd.h>
  3.  
    #include<assert.h>
  4.  
    #include<string>
  5.  
    #include<cstring>
  6.  
    #include<cstdio>
  7.  
    #include<sys/wait.h>
  8.  
    #include<sys/types.h>
  9.  
    using namespace std;
  10.  
     
  11.  
    int main()
  12.  
    {
  13.  
    //1.创建管道
  14.  
    int pipefd[2]={0};//pipefd[0]:读端,pipefd[1]:写端
  15.  
    int n=pipe(pipefd);
  16.  
    assert(n!=-1);
  17.  
    (void)n;//debug下assert有效,release下assert无效。只定义不使用n容易报警告
  18.  
     
  19.  
    #ifdef DEBUG
  20.  
    cout<<"pipefd[0]:"<<pipefd[0]<<endl;//3
  21.  
    cout<<"pipefd[1]:"<<pipefd[1]<<endl;//4
  22.  
    #endif
  23.  
    //2.创建子进程
  24.  
    pid_t id=fork();
  25.  
    assert(id!=-1);
  26.  
    if(id==0)
  27.  
    {
  28.  
    //子进程
  29.  
    //3.构建单向通信的信道,父进程写入,子进程读取
  30.  
    //3.1关闭子进程不需要的fd
  31.  
    close(pipefd[1]);
  32.  
    char buffer[1024];
  33.  
    while(true)
  34.  
    {
  35.  
    ssize_t s=read(pipefd[0],buffer,sizeof(buffer)-1);
  36.  
    if(s>0)
  37.  
    {
  38.  
    buffer[s]=0;//手动添加/0
  39.  
    cout<<"我是子进程["<<getpid()<<"]Father#"<<buffer<<endl;
  40.  
    }
  41.  
    }
  42.  
     
  43.  
    exit(0);
  44.  
    }
  45.  
    //父进程
  46.  
    //3.构建单向通信的信道
  47.  
    //3.1关闭父进程不需要的fd
  48.  
    close(pipefd[0]);
  49.  
    string message="我是父进程,我正在给你发消息";
  50.  
    int count=0;
  51.  
    char send_buffer[1024];
  52.  
    while(true)
  53.  
    {
  54.  
    //3.2构建一个变化的字符串
  55.  
    snprintf(send_buffer,sizeof(send_buffer),"%s[%d]:%d",message.c_str(),getpid(),count );
  56.  
    //3.3写入
  57.  
    write(pipefd[1],send_buffer,strlen(send_buffer));
  58.  
    //3.4故意sleep
  59.  
    sleep(1);
  60.  
    }
  61.  
    pid_t res=waitpid(id,nullptr,0);
  62.  
    assert(res>0);
  63.  
    (void)res;
  64.  
    close(pipefd[1]);
  65.  
    return 0;
  66.  
    }
学新通
学新通
运行图

 A.写快,读慢,会发生什么?(我们修改代码,将子进程读端sleep(5),父进程添加一行打印send_buffer信息)

  1.  
    //添加父进程打印代码
  2.  
    //3.2构建一个变化的字符串
  3.  
    snprintf(send_buffer,sizeof(send_buffer),"%s[%d]:%d",message.c_str(),getpid(),count );
  4.  
    //3.3写入
  5.  
    write(pipefd[1],send_buffer,strlen(send_buffer));
  6.  
     
  7.  
    printf("%s\n",send_buffer);
学新通
写快读慢结果图

我们可以看到一瞬间父进程写满了,然后阻塞等待子进程读取

B.写慢,读快,会发生什么?(父进程sleep(5),子进程不休眠)

学新通
写慢结果图

我们可以看到,父进程在写入第一条信息后被子进程立刻读取,然后父进程休眠5s,子进程在这期间阻塞等待父进程继续写入 。

C.写端关闭,读端继续,会发生什么?(父进程关闭pipefd[1],即关闭写端,子进程修改代码显示read返回值不大于0的情况)

  1.  
    while(true)
  2.  
    {
  3.  
    ssize_t s=read(pipefd[0],buffer,sizeof(buffer)-1);
  4.  
    if(s>0)
  5.  
    {
  6.  
    buffer[s]=0;//手动添加/0
  7.  
    cout<<"我是子进程["<<getpid()<<"]Father#"<<buffer<<endl;
  8.  
    }
  9.  
    else
  10.  
    {
  11.  
    cout<<s<<endl;
  12.  
    }
  13.  
    sleep(1);
  14.  
    }
学新通
写端关闭结果图

结果显示:read返回0,read系统调用接口返回0即表示读取到文件结尾。

D.读端关闭,写端继续,会发生什么?(我们让子进程写数据,父进程读数据,读3次后关闭读端,此时父子进程读端都关闭)

  1.  
    #include <iostream>
  2.  
    #include <unistd.h>
  3.  
    #include <sys/wait.h>
  4.  
    #include <sys/types.h>
  5.  
    #include <string>
  6.  
    #include <cstring>
  7.  
    #include <cassert>
  8.  
    int main()
  9.  
    {
  10.  
    // 创建管道
  11.  
    int pipefd[2] = {0};
  12.  
    int n = pipe(pipefd);
  13.  
    // 子进程继承
  14.  
    pid_t id = fork();
  15.  
    if (id == 0)
  16.  
    {
  17.  
    // 子进程写端
  18.  
    close(pipefd[0]);
  19.  
    std::string message = "我是子进程,我正在给你发消息";
  20.  
    int count = 0;
  21.  
    char send_buffer[1024];
  22.  
    while (true)
  23.  
    {
  24.  
    if (count == 5)
  25.  
    break;
  26.  
    // 3.2构建一个变化的字符串
  27.  
    snprintf(send_buffer, sizeof(send_buffer), "%s[%d]:%d", message.c_str(), getpid(), count );
  28.  
    // 3.3写入
  29.  
    write(pipefd[1], send_buffer, strlen(send_buffer));
  30.  
    sleep(1);
  31.  
    }
  32.  
    close(pipefd[1]);
  33.  
    exit(0);
  34.  
    }
  35.  
    // 父进程读端
  36.  
    close(pipefd[1]);
  37.  
    char buffer[1024];
  38.  
    for(int i=0;i<3;i )
  39.  
    {
  40.  
    ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
  41.  
    if (s > 0)
  42.  
    {
  43.  
    buffer[s] = 0;
  44.  
    std::cout << "我是父进程:" << getpid() << "#" << buffer << std::endl;
  45.  
    }
  46.  
    }
  47.  
    close(pipefd[0]);
  48.  
    int status = 0;
  49.  
    pid_t res = waitpid(id, &status, 0);
  50.  
    assert(res > 0);
  51.  
    (void)res;
  52.  
    printf("接受到的信号为:%d\n", status & 0x7f);
  53.  
    return 0;
  54.  
    }
学新通
学新通
读端关闭结果图

结果显示:父进程接受到子进程返回的13号信号(SIGPIPE,write进程发生中断)

学新通
信号说明图

🍥匿名管道读写规则

         当没有数据可读时
                O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
                O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
         当管道满的时候
                O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
                O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
         如果所有管道写端对应的文件描述符被关闭,则read返回0
         如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
         当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
         当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

🍥匿名管道特定总结

        ★管道是用来进行具有血缘关系的进程进行进程间通信——常用于父子通信

        ★管道具有通过让进程间协同,提供了访问控制!

        ★管道提供面向流式的通信的服务——面向字节流——一般需要协议

        ★管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的!

        ★管道是单向通信的,就是半双工通信的一种特殊情况


        小伙伴们看到最后肯定会发现,匿名管道有一个很大的缺陷,就是必须要有父子关系的进程间进行通信,这是大大的限制了进程间的通信,那没有父子关系的进程如何通信呢?我们下一篇命名管道再讲解!

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfkkab
系列文章
更多 icon
同类精品
更多 icon
继续加载