zoukankan      html  css  js  c++  java
  • 进程间通信之管道

    1、介绍

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

    2、特点

    管道:本质是一个伪文件(实为内核使用环形队列机制,借助内核缓冲区(4k)实现)。

    有名管道:不同于管道,它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。

    半双工的,数据只能向一个方向流动;

    需要双方通信时,需要建立起两个管道;

    由两个文件描述符引用,一个表示读端,一个表示写端;

    规定数据从管道的写端流入管道,从读端流出(写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据,不支持诸如lseek()等文件定位操作);

    数据一旦被读走,便不在管道中存在,不可反复读取。

    3、管道的读写行为

    使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

    1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。

    2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

    3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。

    4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

    总结:

    ① 读管道: 1. 管道中有数据,read返回实际读到的字节数。

    2. 管道中无数据:

    (1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)。

    (2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)。

    ② 写管道: 1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)。

    2. 管道读端没有全部关闭:

    (1) 管道已满,write阻塞。

    (2) 管道未满,write将数据写入,并返回实际写入的字节数。

    4、有名管道读写行为

    1.读取数据:如果有进程写打开FIFO,且当前FIFO内没有数据

    (1)阻塞读(fd=open(FIFO_NAME,O_RDONLY);),则将一直阻塞等待数据。

    (2)非阻塞读(O_RDONLY | O_NONBLOCK),则返回-1,当前errno值为EAGAIN,提醒以后再试。

    所以对于阻塞读操作而言,造成阻塞的原因有:

    (1)FIFO内有数据,但有其它进程在读这些数据

    (2)FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。

    2.写入数据:

    (1)对于设置了阻塞标志的写操作:

    当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。(PIPE_BUF ==>> /usr/include/linux/limits.h)

    当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回(多个进程都写数据交错)。

    (2)对于没有设置阻塞标志的写操作:

    当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;

    当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。

    5、管道创建

    int pipe(int fd[2])
    fd为filedescriptors的缩写,其为一个二元数组,用于存放pipe函数所创建管道的两个文件描述符,fd[0]存放管道取端的文件描述符,fd[1]用于存放管道入端的文件描述符。通常,进程pipe()创建管道后,再fork一个子进程,调用exec函数族使子进程执行所需程序,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。通信方式:父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

    6、有名管道创建

     int mkfifo(const char * pathname, mode_t mode)

    该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EXIST错误,所以一般典型的调用代码首先会检查是否返回该错误。 一般文件的I/O函数都可以用于FIFO,如close、read、write等等。

    7、有名管道程序

    父进程传递参数和有名管道名给子进程,子进程做完操作,写管道数据,父进程阻塞直到读到数据

    父进程

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <sys/types.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/stat.h>
    #include <time.h> 
    int main(void)
    {
        char tgid[6] = "12345";
        int pid;
    
        int x;
        srand(time( NULL )); 
        x =rand();
        printf("x:%d
    ", x);
    
        char fifoname[10] = "xxx";
        if ((pid = fork()) < 0){
            perror ( "failed to fork " );
            return 1;
        }
        else if (pid > 0){
            //printf("parent:%d
    ", getpid());
            int m = mkfifo(fifoname,0664);
            if(m == -1) {
                perror("mkfifo");
                return 1;
            }
            int fd = open(fifoname, O_RDONLY);////没有O_NONBLOCK,所以read默认open会阻塞,直到子进程写打开并且写入数据。除非O_RDONLY | O_NONBLOCK才不阻塞
            if(fd == -1) {
                perror("open");
                return 1;
            }
            char buf[5] = "0";
            int r;
            while(1)//还是搞了个whild
            {
                r = read(fd, buf, sizeof(buf));
                if(strcmp(buf, "1") == 0)
                {
                    break;//读到子进程python给的值后才执行下面的
                }
            }
            close(fd);
            unlink(fifoname);//删除指定参数,即管道
            //printf("read:%s
    ", buf);
            return 0;
        }
        else{
            //printf("child:%d
    ", getpid());
    
            char *const p[]={"python", "/home/child.py", tgid, fifoname, NULL};//指向char的常量指针数组
            if(execv("/usr/bin/python",p)<0)
            //if(execl("/usr/bin/python","python","/home/child.py",tgid,fifoname,NULL)<0)//tgid为传给子进程python的,pipename是有名管道名
            //if(execl("./test","./test",tgid,NULL)<0)//C语言
            {
                fprintf(stderr,"execl failed:%s
    ",strerror(errno));
                return 1;
            }
        }
        //return 0;
    }

    注:上述关于exec函数族 、const关键字 、指针数组

    子进程:

    #!/usr/bin/python
    import sys
    import os
    def main(argv):
        print 'python pid:%s' % (os.getpid())
        print 'python tgid:%s' % (sys.argv[1])
        f = os.open(sys.argv[2], os.O_WRONLY)
        time.sleep(10)
        print 'python end sleep'
        os.write(f, "1")
        os.close(f)
    
    
    if __name__ == "__main__":
        main(sys.argv[1:])

    子进程-C语言

    #include <stdio.h>
    #include <sys/prctl.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <string.h>
    #include <error.h>
    #include <fcntl.h>
    int main(int argc, char **argv)
    {
        int oret;
        int wret;
        prctl(PR_SET_PDEATHSIG, SIGHUP);
        printf("child.c tgid:%s
    ", argv[1]);
        printf("child.c fifoname:%s
    ", argv[2]);
    
        sleep(10);
        oret = open(argv[2], O_WRONLY);
        if(oret < 0)
        {
            perror("open error");
            return 1;
        }
        wret = write(oret,"1",2);
        if(wret == -1)
        {
            perror("write error");
            return 1;
        }
        close(oret);
    
        return 0;
    }

    定义参考:

    https://blog.csdn.net/u011068702/article/details/54914774

    https://blog.csdn.net/firefoxbug/article/details/8137762

    https://www.cnblogs.com/fangshenghui/p/4039805.html

    https://blog.csdn.net/oguro/article/details/53841949

  • 相关阅读:
    5 Python3 函数进阶&迭代器与生成器
    2 python第三章文件操作
    4 python内置函数
    python内置函数 eval()、exec()以及complie()函数
    0 字符与字节的区别
    python enumerate() 函数
    1 python 文件处理
    python 之编写登陆接口
    python 之九九乘法表
    第一模块第二章-数据类型整理
  • 原文地址:https://www.cnblogs.com/beixiaobei/p/9568418.html
Copyright © 2011-2022 走看看