Linux内核中的管道

来源:互联网 发布:广电网络运维工作总结 编辑:程序博客网 时间:2024/06/05 14:07

 一、介绍关于进程间通信

       Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。Linux则把两者继承了下来,关系如下图:

       其中,最初Unix IPC包括:管道、FIFO、信号;System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;Posix IPC包括: Posix消息队列、Posix信号灯、Posix共享内存区。有两点需要简单说明一下:1)由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(PSOIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循POSIX标准;2)BSD并不是没有涉足单机内的进程间通信(socket本身就可以用于单机内的进程间通信)。事实上,很多Unix版本的单机IPC留有BSD的痕迹,如4.4BSD支持的匿名内存映射、4.3+BSD对可靠信号语义的实现等等。

二、进程间通信中的管道

        1.管道的概述:

           管道(pipe)是Linux中进程间通信的一种方式。

           (1).它只能用于具有亲缘关系的进程之间的通信(即父子进程或兄弟进程之间)。

           (2).它是一个半双工的通信模式,具有固定的读端和写端。

           (3).管道也可以看成一种特殊的文件,对于它的读写也可以使用普通的read()write()等函数。但是她不是普通的文件,并不属于其它任何文件系统,并且只存在于内核的内存空间中。


         2.管道的创建和关闭

            (1).管道基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fd[0]fd[1],其中fd[0]固定用于读管道,而fd[1]则固定用于写管道,这样就构成了一个半双工的通道。

            (2).创建管道可以通过调用pipe()函数来实现。

            (3).关闭管道则只需使用普通的close()函数逐个关闭各个文件描述符。

    #incldue<unistd.h>       int pipe(int fd[2]);

          函数返回值:

           成功:0;出错:-1。

        3.管道的类型

           (1).命名管道:命名管道是为了解决无名管道只能用于近亲进程之间通信的缺陷而设计的。命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。

           (2).匿名管道:主要用于父进程与子进程之间,或者两个兄弟进程之间。在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。因此,每个管道都是单向的,当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。这种传输遵循“先入先出”(FIFO)的规则。


三、父子进程之间的管道通信

       1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]fd[1]指向管道的读端和写端。

       2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

       3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。


四、管道的特点及4中情况

       1.特点:

          1). 智能进行单向通信;
          2).智能用于有血缘关系的访问,常用于父子;
          3).生命周期随进程;
          4).通过流的方式进行访问;
          5).以互斥为前提条件,拥有同步机制。

        2.四种情况:

          1).读端不读不关闭相应文件描述符表,写端写满会等待;
          2).写端不写不关闭相应文件描述符表,读端会等待;
          3).读端不读且关闭相应文件描述符表,写端会被操作系统终止;
          4).写端不写且关闭相应文件描述符表,读端读完会关闭。

五、模拟实现一个命名管道

         1.什么是命名管道FIFO

            在之前的学习中我们知道如果我们要在两个相关进程间交换数据,那么使用管道即可,也就是调用pipe函数就可以了;那么如果我们要在不相关的进程间交换数据,那么使用FIFO文件将会十分方便,也就是调用mkfifo函数来进行FIFO文件通常也称为命名管道(named pipe)。命名管道是一种特殊类型的文件,它在文件系统中以文件名的形式存在。

#include <sys/types.h>  #include <sys/stat.h>  int mkfifo(const char *pathname, mode_t mode);  

               mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode给定后FIFO文件的权限还取决于umask),因此umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。  

          2.如下是命名管道的一个实现:

/*************************Makefile**********************/.PHONY:allall:server client server:server.c    gcc -o $@ $^client:client.c    gcc -o $@ $^.PNONY:cleanclean:    rm -f server client mypipe/**********************server.c*************************/#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>#include<string.h>#include<fcntl.h>int main(){    umask(0);    if(mkfifo("./mypipe",0666 | S_IFIFO) < 0)    {        perror("mkfifo");        return 2;    }    int fd = open("./mypipe",O_RDONLY);    if(fd<0)    {        perror("open");        return 1;    }    char buf[1024];    while(1)    {        ssize_t s = read(fd,buf,sizeof(buf)-1);        if(s > 0)        {            buf[s]='\0';            printf("client say #  %s\n",buf);        }        else if(s == 0)        {            printf("quiting ! please wait!\n");            fflush(stdout);            break;        }        else        {            perror("read");            return 3;        }    }    close(fd);    return 0;}/********************client.c********************/#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>#include<string.h>#include<fcntl.h>int main(){    umask(0);    if(mkfifo("./mypipe",0666 | S_IFIFO) < 0)    {        perror("mkfifo");        return 2;    }    int fd = open("./mypipe",O_RDONLY);    if(fd<0)    {        perror("open");        return 1;    }    char buf[1024];    while(1)    {        ssize_t s = read(fd,buf,sizeof(buf)-1);        if(s > 0)        {            buf[s]='\0';            printf("client say #  %s\n",buf);        }        else if(s == 0)        {            printf("quiting ! please wait!\n");            fflush(stdout);            break;        }        else        {            perror("read");            return 3;        }    }    close(fd);    return 0;}
        fifo read端中的“S_IFIFO|0666”指明创建一个命名管道且存取权限为0666,即为创建者、与创建者同组的用户、其他用户对该命名管道的访问权限都是读和写(这里要注意umask对生成的管道文件权限的影响)。


六、标准流管道及命名管道

      1.标准流管道:

         A:Linux的文件操作中有基于文件流得标准I/O操作一样,管道的操作也支持基于文件流的模式。这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的”另一个进程“也就是一个可以进行一定操作的可执行文件,例如,用户执行“ls -l”或者自己编写的程序“./pipe”等。由于这一类操作很常用,因为标准流管道就将一系列的创建过程合并到一个函数popen()中完成。她所完成的工作有以下几步:

              1).创建一个管道。

              2).fork()一个子进程

              3).在父子进程中关闭不需要的文件描述符。

              4).执行exec函数族调用。

              5).执行函数中指定的命令。

         B:标准流管道的优缺点

              1).标准流管道的使用可以大大减少代码的编写量,但同时也右一些不利之处,例如,它不如前边管道创建那样灵活多样,并且用popen()创建的管道必须使用标准I/O函数进行操作,但不能使用前面的read()write()一类不带缓冲的I/O函数。

               2).与之相对应,关闭popen()创建的流管道必须使用函数pclose()来关闭该管道流。该函数关闭标准I/O流,并等待命令结束。

         C:标准流管道的打开、关闭函数

               1).打开函数:

#include <stdio.h>FILE *popen(const char *command, const char *type)
                   conmand:指向的是一个以NULL结束符结尾的字符串,这个字符串包含一个shell命令,并被送到/bin/sh-c参数执行,即由shell来执行。

                   type:“r”:文件指针连接到command标准输出,即该命令的结果产生输出;“w”:文件指针连接到command标准输入,即该命令的结果产生输入。

                   返回值:成功:文件指针;出错:-1 。

                2).关闭函数:

#include <stdio.h>int pclose(FILE *stream)
     
                   stream:要关闭的文件流。

                   返回值:成功:返回由popen()所执行的进程退出码;出错:-1 。


          2.命名管道

             A:介绍

                1).前面介绍提过命名管道,不过值得注意的是,FIFO是严格地遵循先进先出的规则的,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如lseek()等文件定位操作。

                2).命名管道的创建可以使用函数mkfifo()函数,该函数类似文件中的open()操作,可以指定管道的路径和打开的模式。

            B:命名管道的创建

                在创建管道成功后,就可以使用open()read()write()这些函数了。与普通文件的开发设置一样,对于为读而打开的管道可在open()中设置O_RDONLY,对于为写而打开的管道可在open()中设置O_WRONLY,在这里与普通文件不同的是阻塞问题。

#include <sys/types.h>#include <sys/state.h>int mkfifo(const char *filename,mode_t mode)

               filename:要创建的管道。

               mode:(1)、O_RDONLY:读管道

                            (2)、O_WRONLY:写管道

                            (3)、O_RDWR:读写管道

                            (4)、O_NONBLOCK:非阻塞管道

                            (5)、O_CREAT:如果一个文件不存在,那么创建一个新的文件,并用第三个参数为其设置权限

                            (6)、O_EXCL:如果使用O_CREAT文件存在,那么可返回错误消息,这参数可测试文件是否存在。

                C:总结

                      1.由于普通文件的读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在open()函数中设定为O_NONBLOCK

                      2.对于读进程:

                        1)、若该管道是阻塞打开,且当前FIFO内没有数据,则对读进程而言将一直阻塞到有数据写入。

                        2)、若该管道是非阻塞打开,则不论FIFO内是否有数据,读进程都会立即执行度操作,如果FIFO内没有数据,则函数将立即返回0.

                      3.对于写进程:

                         1 )、若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入。

                         2)、若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败。







原创粉丝点击