前言

  • [ ] 本节重点:
  • 进程间通信介绍。
  • 管道。
  • 消息队列(不涉及)。
  • 共享内存。
  • 信号量(网络时涉及)。

一、进程间通信介绍

1.进程间通信目的

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

2.进程间通信发展

  • 管道。
  • System V进程间通信。
  • POSIX进程间通信

3.进程间通信分类

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

二、管道

1.什么是管道

  • 管道是Unix中最古老的进程间通信的形式,最大的进程通信是网络。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道"。
  • 一般在linux命令中|(管道)之前的命令会输出大量的结果,|(管道)之后的命令一般就是带有条件的,只将|前满足条件的结果显示出来。
  • 举例:who | wc -l
    说明:就是把前一个命令的结果当成后一个命令的输入。结合本例就是先显示所有用户,然后再用wc命令在who的结果中列出查找用户。

2.管道符号

3.管道本质

4.管道的接口

include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

ps aux | grepmypipe

5.管道的初识

1    #include<iostream>
     2    #include<unistd.h>
     3    int main()
     4    {
     5       int fd[2];
     6       int ret=pipe(fd); 
     7       if(-1==ret)
     8       {
     9           std::cout<<"创建失败!"<<std::endl;
    10       }
    11       std::cout<<"fd[0]:"<<fd[0]<<std::endl<<"fd[1]:"<<fd[1]<<std::endl;
    12        return 0;
    13    }
12345678910111213

6.管道与搭配进程之间的了解


1    #include<iostream>
     2    #include<unistd.h>
     3    #include<stdlib.h>
     4    #include<string.h>
     5    int main()
     6    {
     7       int fd[2];
     8       int ret=pipe(fd); 
     9       if(-1==ret)
    10       {
    11           std::cout<<"创建失败!"<<std::endl;
    12       }
    13       std::cout<<"fd[0]:"<<fd[0]<<std::endl<<"fd[1]:"<<fd[1]<<std::endl;
    14       pid_t pid=fork();
    15       if(pid<0)
    16       {
    17           std::cout<<"fork error!"<<std::endl;
    18           exit(1);
    19       }
    20       else if(pid==0)
    21       {
    22           //child read
    23           close(fd[1]);
    24           char buf[1024];
    25           read(fd[0],buf,sizeof(buf)-1);
    26           std::cout<<"one child read:"<<buf<<std::endl;
    27           read(fd[0],buf,sizeof(buf)-1);
    28           std::cout<<"two child read:"<<buf<<std::endl;
    29       }
    30       else 
    31       {
    32           //father write
    33           sleep(2);
    34           close(fd[0]);
    35           const char* str="bit education!";
    36           write(fd[1],str,strlen(str));
    37       }
    38       while(1)
    39       {
    40          ;
    41       }
    42        return 0;
    43    }
12345678910111213141516171819202122232425262728293031323334353637383940414243

7.匿名管道

1    #include<iostream>
     2    #include<string.h>
     3    #include<unistd.h>
     4    #include<sys/wait.h>
     5    #include<sys/stat.h>
     6    #include<stdlib.h>
     7    int main()
     8    {
     9        int pipefd[2]={0};
    10        pipe(pipefd);
    11        pid_t pid=fork();
    12        if(pid<0)
    13        {
    14            std::cout<<"fork error!"<<std::endl;
    15        }
    16        else if(pid==0)
    17        {
    18            //child write fd[1]
    19            close(pipefd[0]);
    20            const char* str="I am child\n";
    21            int count=0;
    22            while(1)
    23            {
    24                write(pipefd[1],str,strlen(str));
    25                std::cout<<"child:"<<(count++)<<std::endl;
    26                if(count==13)
    27                {
    28                    close(pipefd[1]);
    29                    break;
    30                }
    31            }
    32            exit(1);
    33        }
    34        else 
    35        {
    36            //father read fd[0]
    37            close(pipefd[1]);
    38            char buffer[64];
    39            int count=0;
    40            while(1)
    41            {
    42               ssize_t s=read(pipefd[0],buffer,sizeof(buffer)-1); 
    43               if(s<0)
    44               {
    45                   std::cout<<"读取失败!"<<std::endl;
    46               }
    47               else if(s>0)
    48               {
    49                   buffer[s]=0;
    50                   std::cout<<"father get message:"<<buffer<<std::endl;
    51                   sleep(1);
    52               }
    53               if((count++)==2)
    54               {
    55                   close(pipefd[0]);
    56                   break;
    57               }
    58               int status=0;
    59               waitpid(pid,&status,0);
    60               std::cout<<"child exit signal:"<<(status&0x7F)<<std::endl;
    61            }
    62        }
    63        return 0;
    64    }
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

8.用fork来共享管道原理

9.站在文件描述符角度-深度理解管道


10.站在内核角度-管道本质

在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如下图:

有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。
这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。

11.管道读写规则

  • 如果所有管道写端对应的文件描述符被关闭,则read返回0。
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
  • 管道的大小是64K。
1    #include<iostream>
    2    #include<unistd.h>
    3    int main()
    4    {
    5        int fd[2];
    6        int ret=pipe(fd);
    7        if(ret==-1)
    8        {
    9            std::cout<<"管道创建失败!"<<std::endl;
   10        }
   11        int count=0;
   12        while(1)
   13        {
   14            write(fd[1],"a",1);
   15            std::cout<<"count="<<++count<<std::endl;
   16        }
   17        return 0;
   18    }
123456789101112131415161718

  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
  • [ ] 当没有数据可读时:
  • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

    读非阻塞

    写非阻塞

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

12.管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务。
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程。
  • 一般而言,内核会对管道操作进行同步与互斥。
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

13.如何将文件描述符设置成非阻塞


1    #include<iostream>
     2    #include<fcntl.h>
     3    #include<unistd.h>
     4    int main()
     5    {
     6        int fd[2];
     7        int ret=pipe(fd);
     8        if(ret==-1)
     9        {
    10            std::cout<<"管道文件创建失败!"<<std::endl;
    11        }
    12        int flag=fcntl(fd[1],F_GETFL);
    13        std::cout<<"fd[1]:"<<flag<<std::endl;
    14    
    15        flag=fcntl(fd[0],F_GETFL);
    16        std::cout<<"fd[0]:"<<flag<<std::endl;
    17    
    18        fcntl(fd[0],F_SETFL,flag|O_NONBLOCK);
    19        flag=fcntl(fd[0],F_GETFL);
    20        std::cout<<"2fd[0]:"<<flag<<std::endl;
    21    
    22        fcntl(fd[1],F_SETFL,flag|O_NONBLOCK);
    23        flag=fcntl(fd[1],F_GETFL);
    24        std::cout<<"2fd[1]:"<<flag<<std::endl;
    25        return 0;
    26    }
1234567891011121314151617181920212223242526



总结

以上就是今天要讲的内容,本文仅仅简单介绍了Linux进程间通信的概念和管道的使用,而进程间通信提供了大量能使我们快速便捷地处理数据的函数和方法。我们务必掌握。到这里,匿名管道就结束了,后序将会有更重要的文章陆续更新,希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。

版权声明:本文为CSDN博主「森明帮大于黑虎帮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44918090/article/details/118336034

最后修改:2022 年 04 月 04 日
如果觉得我的文章对你有用,请随意赞赏