在Linux操作系统中,每个进程都有属于自己的运行空间,空间内存放有数据和执行代码,那么不同的进程相互之间是如何进行数据和信息的交换呢?Linux中提供了一种用于进程间通信(IPC-Inter Process Communication)最基本的机制——管道。

    在Linux系统中一切皆文件,管道是一种特殊的文件,它是在内核中开辟了一块缓冲区,该缓冲区大小往往是固定的,Ubuntu中为65536也就2^16个字节也就是32K。这里值得提出的是,管道的通信是单向的,它提供了一个读端和一个写端,用函数pipe来创建,其返回值调用成功返回0,若失败返回-1:

wKioL1cJvSWgmpsTAAARVlInjY8002.png

开辟了管道之后,它实现进程间通信的机制就可以如下图所示:

wKioL1cJwxHzXSP2AAANu7cgMM4892.png

  1. 首先由一个父进程pipe出一个管道并得到其读端和写端的文件描述符;

  2. 其次当父进程fork出一个子进程时,子进程从父进程那里得到两个文件描述符指向同一管道

  3. 父进程关闭写端,子进程关闭读端,子进程向管道内写入数据父进程就可以从中读到,这样就实现了进程间的通信。


演示代码:

wKiom1cJzsexoNl3AAAWHyYy_qY807.png

wKioL1cJ5LChhWwjAACJVhRp7mw864.png


代码中子进程每隔1秒向管道中写入数据,父进程从管道的读端读取并输出,整个代码中只有父进程在输出,而子进程在写入,因此实现了进程间的通信,但是使用管道时,需要注意下面四种情况:

  1. 当管道的写端关闭,即写端的引用计数为0,而读端一直从管道内读取数据,当将管道内数据全部都读取完时,再次read就会返回0,就像是读到了文件的末尾一样。例:将上面程序中的子进程改为如下:

wKioL1cJ4bqCSNJ8AAAXywnF09U272.png


当count小于5时关闭子进程的写端,运行结果为:


wKioL1cJ7gWRn_8tAAAcMY82NR0620.png

2. 当管道的写端不关闭,但也不向管道内写入数据,这时有进程从管道内读取数据,当进程将管道内的   数据读取完时,再次read就会阻塞,直到有数据写入时才会再次读取。将上例中子进程改为如下:

wKioL1cJ723TwkIpAAAa7vgFhMA914.png


当count等于5时,父进程已经读取到了5条数据,这时不关闭子进程的写端,但是让子进程sleep上5秒,这时父进程再从管道中读取数据时就会阻塞,5秒过后子进程再次向管道内写入数据,父进程就可以正常从管道中取得数据了。


3. 当指向管道读端的文件描述符都关闭,即读端引用计数为0,但仍然有进程向管道内写入数据时,那   么该进程就会收到一个SIGPIPE的信号,通常会导致进程异常终止。将上例中的父进程改为如下:

wKiom1cKASHSELtQAAAo1g0HcKI712.png


父进程中当count小于5时就将管道的读端关闭,此时子进程仍然会向管道内写入数据,在父进程内调用waitpid函数获取子进程的退出码,因为参数status是一个整型,其低八位是子进程异常退出时的状态码,而次第八位是正常终止的状态码,用status按位与0xff可以得到其低八位,运行程序可得到如下结果:

wKioL1cKAdPSDzSDAAAdGb9OPIg817.png

子进程异常终止,其退出状态码为13,其代表就是SIGPIPE。


4. 当管道的读端并没有关闭,但没有进程从管道内读取数据,这时不断地有进程向管道内写入数据,当管道被写满时,再次write就会阻塞,直到有数据被读取管道内有空地时会再次写入。将上例中父子进程改为如下:

wKioL1cKGxvADdK6AAAtwcchkG8802.png

将子进程改为死循环,记录写入次数,每次向管道内写入1个字节,同时让父进程的读端并不读入数据,运行程序结果如下:

wKiom1cKGnLyU2-TAAALO80x014405.png

当count加到65535时说明管道已写满不再写入,等待读端从管道内读取数据之后才继续写入,因此可以知道管道的大小为65536个字节,也就是32K。




    上面的各种分析中,虽然实现了进程间的数据交换,但可以注意到,这种通信机制只适用于父子之间或者有血缘关系的进程之间,因为需要从父进程那里继承得到相同的文件描述符来对同一个管道进行读写操作,虽然可以通信,但难免是有些限制的。

    为了解决这种限制,让不同的进程不管有没有血缘关系都可以访问同一个管道自由的进行通信,就有了另一种管道———命名管道(FIFO)。命名管道不同于上面所述的匿名管道的是,它是存在于系统中的一个文件,任何进程只要可以访问该文件的路径,就能够通过FIFO进行相互通信。这里要提出,FIFO总是按照先进先出的原则,即第一个写入的数据总是第一个被读出。

    用mkfifo函数创建一个命名管道:

wKioL1cMjfGA1rO4AAAIg5ujAbo659.png

函数参数中,第一个为文件的相对路径或绝对路径,表明在系统中创建一个管道文件,第二个参数为文件的权限信息。

    

    创建好管道文件之后,就可以使用了,因为是文件,所以可以打开,在一个程序中打开文件,如果以读写方式打开就不会阻塞,如果以只读方式打开就会阻塞直到有以写方式打开此文件为止,同样,如果以只写方式打开就会阻塞直到有读方式打开文件为止。


程序示例:

wKiom1cMjT3hHX6NAAEmBoU1SIw167.png

上面创建了两个文件client.c和server.c,在client.c中用mkfifo函数创建了一个在当前目录下的一个隐藏文件.tmp,然后以只写方式打开文件,并且用write函数向文件中写入数据,然后再server.c程序中同样打开.tmp文件,此时因为文件已经在client.c中被创建了,因此可以用只读方式打开,并用read函数从中获取被写入的数据,这样就可以实现两个进程间的通信,而不需要考虑进程之间是否有关系。

这里需要提出的是,在设置管道文件的权限时,需要考虑umask的影响。


运行client程序,再打开一个终端运行起来server,在client终端输入想要输入的内容,可以看见在server终端接收到,如此实现两个进程间通信。



《完》