进程的管道通信

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程的管道通信相关的知识,希望对你有一定的参考价值。

#include <signal.h>
#include <stdio.h>
int pid1,pid2;

main( )

int fd[2];
char outpipe[100],inpipe[100];
pipe(fd); /*创建一个管道*/
while ((pid1=fork( ))==-1);
if(pid1==0)

lockf(fd[1],1,0);
sprintf(outpipe,"child 1 process is sending message!"); /*把串放入数组outpipe中*/
write(fd[1],outpipe,50); /*向管道写长为50字节的串*/
sleep(5); /*自我阻塞5秒*/
lockf(fd[1],0,0);
exit(0);

else

while((pid2=fork( ))==-1);
if(pid2==0)
lockf(fd[1],1,0); /*互斥*/
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);

else
wait(0); /*同步*/
read(fd[0],inpipe,50); /*从管道中读长为50字节的串*/
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);




运行结果是乱码, 高手看看什么原因

参考技术A 进程协同工作时,需要互相交换信息,有些情况下进程间交换的少量信息,有些情况下进程间交换大批数据。进程通信即为两个并行进程可以通过互相发送消息进行合作,消息是通过消息缓冲而在进程之间相互传递的。 如进程同步是一种进程通信,通过修改信号量,进程之间可建立起联系,相互协调运行和协同工作;

进程间通信的方式有:信号通信机制; 共享存储区通信机制;共享文件通信机制;消息传递通信机制.

管道是连接读写进程的一个特殊文件,允许进程按先进先出方式传送数据,也能使进程同步执行操作。发送进程以字符流形式把大量数据送入管道,接收进程从管道中接收数据,所以叫管道通信.

管道的实质是一个共享文件,基本上可借助于文件系统的机制实现,包括(管道)文件的创建、打开、关闭和读写.进程对通信机构的使用应该互斥,一个进程正在使用某个管道写入或读出数据时,另一个进程就必须等待.发送者和接收者双方必须能够知道对方是否存在,如果对方已经不存在,就没有必要再发送信息.管道长度有限,发送信息和接收信息之间要实现正确的同步关系,当写进程把一定数量的数据写入管道,就去睡眠等待,直到读进程取走数据后,把它唤醒。

进程间通信——管道

文章目录

进程间通信介绍

进程间通信目的

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

进程间通信发展

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

进程间通信分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

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

POSIX IPC

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

要让两个不同的进程实现通信,前提是让这两个进程看到同一份资源

管道

什么是管道

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

匿名管道

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

实例代码

//例子:从键盘读取数据,写入管道,读取管道,写到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )

    int fds[2];
    char buf[100];
    int len;
    
    if ( pipe(fds) == -1 )//创建匿名管道
    	perror("make pipe"),exit(1);
    
    // read from stdin
    while ( fgets(buf, 100, stdin) ) //循环向buf中写数据
    
        len = strlen(buf);
        
        // write into pipe
        //先写再判断
        if (write(fds[1], buf, len) != len) //当实际写入管道的字符个数不等于buf长度时,就写入错误
        
            perror("write to pipe");
            break;
        
        
        memset(buf, 0x00, sizeof(buf));//将buf的内容置为0
        
        // read from pipe
        if ( (len=read(fds[0], buf, 100)) == -1 ) //先读再判断
        
            perror("read from pipe");
            break;
        
        
        // write to stdout
        if ( write(1, buf, len) != len ) //先写再判断
        
            perror("write to stdout");
            break;
        
        
    

用fork来共享管道原理

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

因为我们最开始的进程打开了标准输入、标准输出、标准错误三个文件,所以之后所有的进程都默认打开这三个文件

为什么文件不会各自私有?因为文件是不属于进程的

站在内核角度-管道本质

f_op就是我们之前说的函数指针。

图是简化理解的,inode指针并不在struct file中

所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想” 。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \\ do \\  \\ perror(m); \\ exit(EXIT_FAILURE); \\  while(0)
int main(int argc, char *argv[])
 	
    int pipefd[2]; 
    if (pipe(pipefd) == -1) 
        ERR_EXIT("pipe error");
    
    pid_t pid;
	pid = fork();
    if (pid == -1)
    	ERR_EXIT("fork error");
    
    if (pid == 0) //子进程
    
        close(pipefd[0]);//关闭读端
        
        //向管道写入
        write(pipefd[1], "hello", 5);
        //关闭写端,减少文件描述符的占用
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    
    
    //父进程关闭写端
    close(pipefd[1]);
    
    char buf[10] = 0;
    //从管道中读数据
    read(pipefd[0], buf, 10);
    printf("buf=%s\\n", buf);
    
    return 0;

管道读写规则

  1. 如果写端不关闭文件描述符,也不写入,读端可能会阻塞很长时间(可能管道中一开始就有数据,不会立马就阻塞)

    示例:

    #include<stdio.h>
    #include <unistd.h>
    #include<string.h>
    
    
    int main()
    
      int pi[2] = 0;
      if (pipe(pi) < 0)//创建匿名管道失败
      
        perror("pipe");
        return 1;
      
      
      int pid = fork();
      if (pid < 0)
      
        perror("fork");
        return 1;
      
      
      //让子进程写,父进程读  
      if (pid == 0)//子进程
      
        //怕失误使用了另一个接口,所以关闭子进程的读
        close(pi[0]);
    
        const char* buf = "I am a child!\\n";
    
        while (1)
        
          write(pi[1], buf, strlen(buf));
          sleep(3);//子进程每隔3s才写入
        
        
      
      else//父进程,只读 
      
        close(pi[1]);
        char buf[64];
        while (1)
        
          ssize_t s = read(pi[0], buf, sizeof(buf) - 1);
          
          if (s > 0)
          
            buf[s] = 0;
          
    
          printf("father get info from pipe:%s\\n", buf);
        
         
      
    
      return 0;
    
    
    

    因为子进程每隔3s才写入管道,而父进程则是无间断地读取管道数据,所以相对来说父进程会阻塞一段时间:

    所以,如果我们关闭了子进程的文件描述符,父进程就会一直在等待,也就是陷入长时间的阻塞。

  2. 当我们写入时,如果写入条件不满足(管道满了,读端读取速度太慢),写入端就会阻塞

    #include<stdio.h>
    #include <unistd.h>
    #include<string.h>
    
    
    int main()
    
      int pi[2] = 0;
      if (pipe(pi) < 0)//创建匿名管道失败
      
        perror("pipe");
        return 1;
      
      
      int pid = fork();
      if (pid < 0)
      
        perror("fork");
        return 1;
      
      
      //让子进程写,父进程读  
      if (pid == 0)//子进程
      
        //怕失误使用了另一个接口,所以关闭子进程的读
        close(pi[0]);
    
        const char* buf = "I am a child!\\n";
        
        int count = 0;
        while (1)
        
          
          write(pi[1], buf, strlen(buf));
          printf("CHILD:%d\\n", count++);
        
      
      else//父进程,只读 
      
        close(pi[1]);
        char buf[64];
        while (1)
        
          ssize_t s = read(pi[0], buf, sizeof(buf) - 1);
          sleep(1);//父进程每隔1s读一次
    
          if (s > 0)
          
            buf[s] = 0;
          
            
          printf("father get info from pipe:%s\\n", buf);
        
         
      
    
      return 0;
    
    

    可以看到,子进程一开始就在猛地写入,而父进程每隔1s才读一次数据,并且从父进程开始读数据,子进程就没有再写入了,是因为管道满了,写入条件不满足,无法完成写入。

  3. 如果写端关闭文件描述符,读端在读取管道时会读到0,也就是read函数返回0

    #include<stdio.h>
    #include <unistd.h>
    #include<string.h>
    
    
    int main()
    
      int pi[2] = 0;
      if (pipe(pi) < 0)//创建匿名管道失败
      
        perror("pipe");
        return 1;
      
      
      int pid = fork();
      if (pid < 0)
      
        perror("fork");
        return 1;
      
      
      //让子进程写,父进程读  
      if (pid == 0)//子进程
      
        //怕失误使用了另一个接口,所以关闭子进程的读
        close(pi[0]);
    
        const char* buf = "I am a child!\\n";
        
        int count = 0;
        while (1)
        
          if (count == 5)//子进程只写入5次
            close(pi[1]);
    
          write(pi[1], buf, strlen(buf));
          ++count;
        
      
      else//父进程,只读 
      
        close(pi[1]);
        char buf[64];
        while (1)
        
          ssize_t s = read(pi[0], buf, sizeof(buf) - 1);
          sleep(1);//父进程休眠1s再读取
    
          if (s > 0)
          
            buf[s] = 0;
            
          	printf("father get info from pipe:%s\\n", buf);
          
          printf("father exit return : %d\\n", s);
         
    
      
    
      return 0;
    
    
    

    子进程无休眠地只写入5次,而父进程休眠1s读取一次,当子进程停止写入后,父进程从管道中读取的数据就为0了。

  4. 如果读端关闭,写端进程可能直接被杀掉

    #include<stdio.h>
    #include <unistd.h>
    #include<string.h>
    #include<stdilb.h>
    
    int main()
    
      int pi[2] = 0;
      if (pipe(pi) < 0)//创建匿名管道失败
      
        perror("pipe");
        return 1;
      
      
      int pid = fork();
      if (pid < 0)
      
        perror("fork");
        return 1;
      
      
      //让子进程写,父进程读  
      if (pid == 0)//子进程
      
        //怕失误使用了另一个接口,所以关闭子进程的读
        close(pi[0]);
    
        const char* buf = "I am a child!\\n";
        
        int count = 0;
        while (1)
        
    		
          write(pi[1], buf, strlen(buf));
        	printf("CHILD:%d\\n", count++);
        
    
        exit(2);
      
      else//父进程,只读 
      
        close(pi[1]);
        char buf[64];
        int count = 0;
        while (1)
        
          if (5 == count++)
             
              close(pi[0]);
          	  break;
          
          ssize_t s = read(pi[0], buf, sizeof(buf) - 1);
          sleep(1);
    
          if (s > 0)
          
            buf[s] = 0;
          	printf("father get info from pipe:%s\\n", buf);
          
    
         
    
      
    
      return 0;
    
    
    

    初步观察:

我们看到在父进程读取5次后,父子进程都结束了,子进程也没有继续打印了,而是退出了。

我们不让父进程结束,再来用脚本来观察父子进程的状态:

shell脚本:

   while :; do ps axj |grep mypipe | grep -v grep; echo "######################################"; sleep 1; done

效果:

可以看到实际上子进程是处于僵尸状态了,也就是退出了。

实际上,它是被操作系统杀掉了,因为管道里面已经没人读数据了,而子进程还往管道里面写数据是在浪费资源,操作系统不允许这种行为发生,于是,就杀掉了子进程。

既然子进程是被杀掉的,就需要用父进程回收,并且获取它的信号

修改一下代码,让父进程等待,并且获取信号:

可以看到子进程的退出信号是13

我们使用kill -l查看一下kill的13号命令:

顾名思义,该信号是用来杀死管道相关的进程的。

修改代码:

   #include<stdio.h>
   #include <unistd.h>
   #include<string.h>
   #include<stdlib.h>
   #include<sys/wait.h>
   
   int main()
   
     int pi[2] = 0;
     if (pipe(pi) < 0)//创建匿名管道失败
     
       perror("pipe");
       return 1;
     
     
     int pid = fork();
     if (pid < 0)
     
       perror("fork");
       return 1;
     
     
     //让子进程写,父进程读  
     if (pid == 0)//子进程
     
       //怕失误使用了另一个接口,所以关闭子进程的读
       close(pi[0]);
   
       const char* buf = "I am a child!\\n";
       
       int count = 0;
       while (1)
       
   
         write(pi[1], buf, strlen(buf));
         printf("CHILD:%d\\n", count++);
       
   
       exit(2);
     
     else//父进程,只读 
     
       close(pi[1]);
       char buf[64];
       int count = 0;
       while (1)
       
         if (5 == count++)
          
           close(pi[0]);
           break;
         
         
         ssize_t以上是关于进程的管道通信的主要内容,如果未能解决你的问题,请参考以下文章

进程间通信——管道

Linux进程间通信

Linux系统编程--进程间通信 ---管道篇

Linux进程间通信

Linux进程间通信——管道

Linux进程间通信