• Linux 进程间通信之管道(pipe),(fifo)


     无名管道(pipe)

    管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

    定义函数: int pipe(int filedes[2])

    filedes[0]为管道里的读取端

    filedes[1]则为管道的写入端。

    实现机制:

    管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

    从原理上,管道利用fork机制建立,从而让两个进程可以连接到同一个PIPE上。最开始的时候,上面的两个箭头都连接在同一个进程Process 1上(连接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个连接也复制到新的进程(Process 2)。随后,每个进程关闭自己不需要的一个连接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入连接,Process 2关闭输出到PIPE的连接),这样,剩下的红色连接就构成了如上图的PIPE

    一、管道读写注意点

    1.必须在系统调用fork之前调用pipe,否则子进程不会继承文件描述符

    2.只有在管道读端存在时,向管道写入才有意义;否则,会收到内核中的出错信号:SIFPIPE只有在管道读端存在时,向管道写入才有意义;否则,会收到内核中的出错信号:SIFPIPE

    3.向管道写入数据时不保证写入的原子性,管道缓冲区一有空闲区域,写进程就试图向其写入内容。若读进程不读取管道中的内容,则写进程会一直阻塞。

    4.父子进程在运行时,它们的先后顺序得不到保证。因此在这里,为保证父进程关闭读描述符,可向子进程加入sleep(2)。

    二、实例

    1.无名管道

    /*pipe_rw.c*/
    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        int pipe_fd[2];
        pid_t pid;
        char buf_r[100];
        char* p_wbuf;
        int r_num;
        memset(buf_r,0,sizeof(buf_r));
        if(pipe(pipe_fd)<0)
        {
        printf("pipe create error
    ");
        return -1;
        }
        if((pid=fork())==0)    //若是子进程
        {
            printf("
    ");
            /*关闭子进程管道写端。睡眠2秒,确保父进程已相应地关闭了管道读端*/
            close(pipe_fd[1]);
            sleep(2);
            /*子进程读取管道内容*/
            if((r_num=read(pipe_fd[0],buf_r,100))>0){
                printf(   "%d numbers read from the pipe is %s
    ",r_num,buf_r);
            }    
            /*关闭子进程读端*/
            close(pipe_fd[0]);
            exit(0);
          }
        else if(pid>0)
        {    /*关闭父进程读端*/
            close(pipe_fd[0]);
            /*分两次向管道写入数据*/
            if(write(pipe_fd[1],"Hello",5)!=-1)
                printf("parent write1 success!
    ");
            if(write(pipe_fd[1]," Pipe",5)!=-1)
                printf("parent write2 success!
    ");
            /*关闭父进程写端并睡眠3秒,让子进程读数据*/
            close(pipe_fd[1]);
            sleep(3);
            /*收集子进程退出信息*/
            waitpid(pid,NULL,0);
            exit(0);
        }
    }

    运行结果如下:

    [root@localhost ipc]# ./pipe_rw 
    parent write1 success! parent write2 success! 10 numbers read from the pipe is Hello Pipe

    2.命名管道(named PIPE)

    由于基于fork机制,所以管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(named PIPE)。

    FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统(file system,命名管道是一种特殊类型的文件,因为Linux中所有事物都是文件,它在文件系统中以文件名的形式存在。)来为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。FIFO的好处在于我们可以通过文件的路径来识别管道,从而让没有亲缘关系的进程之间建立连接

    函数原型:

    #include <sys/types.h>
    #include <sys/stat.h>
    
    int mkfifo(const char *filename, mode_t mode);
    int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );

    其中pathname是被创建的文件名称,mode表示将在该文件上设置的权限位和将被创建的文件类型(在此情况下为S_IFIFO),dev是当创建设备特殊文件时使用的一个值。因此,对于先进先出文件它的值为0。

    FIFO读写规则

    1.从FIFO中读取数据: 约定:如果一个进程为了从FIFO中读取数据而阻塞打开了FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作

    2.从FIFO中写入数据: 约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。

    一旦创建了了FIFO,就可open去打开它,可以使用open,read,close等去操作FIFO 当打开FIFO时,非阻塞标志(O_NONBLOCK)将会对读写产生如下影响:

    1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞;

    2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,errno是ENXIO;

    /*fifo_write.c*/
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define FIFO_SERVER "/tmp/myfifo"
    
    main(int argc,char** argv)
    {
        int fd;
        char w_buf[100];
        int nwrite;
        
        if(fd==-1)
            if(errno==ENXIO)
                printf("open error; no reading process
    ");
        /*打开有名管道,并设置为非阻塞*/
        fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
        if(argc==1)
            printf("Please send something
    ");
        strcpy(w_buf,argv[1]);
        /*向管道写入字符串*/
        if((nwrite=write(fd,w_buf,100))==-1)
        {
            if(errno==EAGAIN)
                printf("The FIFO has not been read yet.Please try later
    ");
        }
        else 
            printf("write %s to the FIFO
    ",w_buf);
    }
    
    
    /*fifo_read.c*/
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define FIFO "/tmp/myfifo"
    
    main(int argc,char** argv)
    {
        char buf_r[100];
        int  fd;
        int  nread;
        
        /*创建有名管道,并设置相应的权限*/
        if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
            printf("cannot create fifoserver
    ");
        printf("Preparing for reading bytes...
    ");
        
        memset(buf_r,0,sizeof(buf_r));
        /*打开有名管道,并设置非阻塞标志*/
        fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
        if(fd==-1)
        {
            perror("open");
            exit(1);    
        }
        while(1)
        {
            memset(buf_r,0,sizeof(buf_r));
            /*读取管道中的字符串*/
            if((nread=read(fd,buf_r,100))==-1){
                if(errno==EAGAIN)
                    printf("no data yet
    ");
            }
            printf("read %s from FIFO
    ",buf_r);
            sleep(1);
        }    
        pause();
        unlink(FIFO);//这个谁mkfifo创建谁释放 
    }

    运行结果:

    终端1:

    [root@localhost ipc]# ./fifo_write 123123  
    write 123123 to the FIFO  

    终端2:

    [root@localhost ipc]# ./fifo_read
    Preparing for reading bytes...
    read  from FIFO
    read  from FIFO
    read  from FIFO
    read  from FIFO
    read  from FIFO
    read 123123 from FIFO
    read  from FIFO

    总结:

          1.read没有数据read阻塞,read读取后数据被删除

          2.数据有序,安装写入的顺序

          3.打开的描述符号可以读写(two-way)

          4.管道文件关闭后,数据不持久

          5.管道的数据在内核缓冲

          6.有名管道的名字仅仅是内核识别是否放回同一个fd的标识

  • 相关阅读:
    9-单表查询
    02-数据库概述
    01-MySql的前戏
    mysql+centos7+主从复制
    Mac下安装ipython与jupyter
    python开发之virtualenv与virtualenvwrapper讲解
    python操作redis
    权限管理具体代码实现
    docker入门
    多用判断&&
  • 原文地址:https://www.cnblogs.com/mingfeng002/p/6909867.html
Copyright © 2020-2023  润新知