目录
在Linux下的多个进程间的通信机制叫做IPC(进程间通信(Inter-Process Communication)),它是多个进程之间相互沟通的一种方法。在Linux下有多种进程间通信的方法:半双工管道、FIFO (命名管道)、消息队列、 信号量、共享内存等。使用这些通信机制可以为Linux下的网络服务器开发提供灵活而又坚固的框架。
1. 半双工管道
管道是一种把两个进程之间的标准输入和标准输出连接起来的机制。管道是一种历史悠久的进程间通信的办法,自UNIX操作系统诞生,管道就存在了。
1.1 基本概念
由于管道仅仅是将某个进程的输出和另一个进程的输入相连接的单向通信的办法,因此称其为“半双工”。在shell中管道用“|”表示,如图1所示,是管道的一种使用方式。
图1 管道示意图
$ ls -l | grep *.c
把Is-1的输出当做“grep *.c”的输入,管道在前一个进程中建立输入通道,在后一个进程建立输出通道,将数据从管道的左边传输到管道的右边,将Is -l的输出通过管道传给 “grep *.c”。
进程创建管道,每次创建两个文件描述符来操作管道。其中一个对管道进行写操作,另一个描述符对管道进行读操作。如图2所示,显示了管道如何将两个进程通过内核连接起来,从图2中可以看出这两个文件描述符是如何连接在一起的。如果进程通过管道 fda[0]发送数据,它可以从fdb[0]获得信息。
图2 用管道进行进程间的通信
由于进程A和进程B都能够访问管道的两个描述符,因此管道创建完毕后要设置在各个进程中的方向,希望数据向那个方向传输。这需要做好规划,两个进程都要做统一的设置,在进程A中设置为读的管道描述符,在进程B中要设置为写;反之亦然,并且要把不关心的管道端关掉。对管道的读写与一般的IO系统函数一致,使用write()函数写入数据,read()函数读出数据,某些特定的IO操作管道是不支持的,例如偏移函数lseek()。
1.2 pipe()函数介绍
创建管道的函数原型为:
#include <unistd.h>
int pipe(int filedes[2]);
数组中的filedes是一个文件描述符的数组,用于保存管道返回的两个文件描述符。数组中的第1个元素(下标为0)是为了读操作而创建和打开的,而第2个元素(下标为1) 是为了写操作而创建和打开的。直观地说,fd1的输出是fd0的输入。
当函数执行成功时,返回0;失败时,返回值为-1。
建立管道的代码如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int result = -1; /*创建管道结果*/
result = pipe(fd); /*创建管道*/
if ( -1 == result) /*创建失败*/
{
printf (“建立管道失败\11”) ; /*打印信息*/
return -1; /*返回错误*/
}
......
...... /*正常程序处理过程*/
}
只建立管道看起来没有什么用处,要使管道有切实的用处,需要与进程的创建结合起来,利用两个管道在父进程和子进程之间进行通信。如图3所示,在父进程和子进程之间建立一个管道,子进程向管道中写入数据,父进程从管道中读取数据。要实现这样的模型,在父进程中需要关闭写端,在子进程中需要关闭读端。
图3 父子进程之间的通信
1.3 pipe()函数的例子
为了便于理解,建立两个变量write_fd和read_fd,分别指向fd[1]和f[0],代码如下:
如下图所示的模型,在子进程中可以向管道写入数据,而写入的数据可以从父进程中读取出。
完整的代码如下所示,子进程中向管道写入“你好,管道”,父进程中读出这些信息。
运行的结果为:
1.4 管道阻塞和管道操作的原子性
当管道的写端口没有关闭时,如果写请求的字节数目大于阈值PIPE_BUF,写操作的返回值是管道中目前的数据字节数。如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。
注意:PIPE_BUF在include/Linux/limits.h中定义,不同的内核版本可能会有所不同。Posix.1要求PIPE_BUF至少为512字节。
管道进行写入操作的时候,当写入数据的数目小于128K时写入是非原子的,如果把父进程中的两次写入字节数都改为128K,可以发现:写入管道的数据量大于128K字节时,缓冲区的数据将被连续地写入管道,直到数据全部写完为止,如果没有进程读数据,则一直阻塞。
1.5 管道操作原子性的代码
例如,下面的代码为一个管道读写的例子。在成功建立管道后,子进程向管道中写入数据,父进程从管道中读出数据。子进程一次写入128K字节的数据,父进程每次读取10K字节的数据。当父进程没有数据可读的时候退出。
1.6 管道原子性的例子运行结果
将上述代码编译运行,其输出为:
可以发现,父进程每次读取10K字节的数据,读了13次将全部数据读出。最后一次读数据,由于缓冲区中只有8K字节的数据,所以仅读取了8K字节。
子进程一次性地写入128K字节的数据,当父进程将全部数据读取完毕的时候,子进程的write()函数才返回将写入信息(“写入131072个数据,剩余0个数据”)打印出来。
上述操作证明管道的操作是阻塞性质的。