标签:
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。如下图所示。
目前进程通信的方式有:
管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
#include <unistd.h> int pipe(int filedes[2]); // 返回值:成功返回0,出错返回-1
该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。
从管道中读取数据:
向管道中写入数据:
下边是APUE提供的一个例子:
父进程调用pipe
开辟管道,得到两个文件描述符指向管道的两端。
父进程调用fork
创建子进程,那么子进程也有两个文件描述符指向同一管道。
父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读。数据从写端流入从读端流出,这样就实现了进程间通信。
具体程序如下:
1 #include <stdlib.h> 2 #include <unistd.h> 3 #define MAXLINE 1024 4 5 int main(void) 6 { 7 int n; 8 int fd[2]; 9 pid_t pid; 10 char line[MAXLINE]; 11 12 if (pipe(fd) < 0) 13 { 14 perror("pipe"); 15 exit(1); 16 } 17 18 pid = fork(); 19 if (pid < 0) 20 { 21 perror("fork"); 22 exit(1); 23 } 24 else if (pid > 0) /* parent */ 25 { 26 close(fd[0]); // 关闭读端 27 write(fd[1], "hello world\n", 12); 28 } 29 else /* child */ 30 { 31 close(fd[1]); // 关闭写端 32 n = read(fd[0], line, MAXLINE); 33 write(STDOUT_FILENO, line, n); 34 } 35 36 return 0; 37 }
但通过管道来实现进程通信有两个局限性:
1. 历史上,管道是半双工的(即数据只能在一个方向上流动)。现在,某些系统提供全双工管道,但是为了最佳的可移植性,我们决不能预先假定系统提供此特性。
2. 管道只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
管道的主要局限性正体现在它的特点上:
管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
创建FIFO原型函数如下:
#include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); // 返回值:若成功返回0,出错则返回-1
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。 如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等等。
一个简单的程序实例如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 6 int main() 7 { 8 int res = mkfifo("/tmp/my_fifo", 0777); 9 if(res == 0) 10 printf("FIFO created!\n"); 11 else 12 { 13 printf("Create FIFO fails!\n"); 14 return 1; 15 } 16 17 return 0; 18 }
编译并执行该程序后,我们可以查看得到所创建的管道:
$ ls -lF /tmp/my_fifo prwxr-xr-x 1 root root 0 Aug 25 14:19 /tmp/my_fifo|
注意:ls命令的输出结果中的第一个字符为p,表示这是一个管道。最后的|符号是由ls命令的-F选项添加的,它也表示是这是一个管道。
下边的程序包括了对FIFO的删除(也可直接用命令rm):
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 7 int main() 8 { 9 int res = mkfifo("/tmp/my_fifo", 0777); 10 if(res == 0) 11 printf("FIFO created!\n"); 12 else 13 { 14 printf("Create FIFO fails!\n"); 15 return 1; 16 } 17 18 res = unlink("/tmp/my_fifo"); 19 if(res == 0) 20 printf("FIFO deleted!\n"); 21 else 22 { 23 printf("Delete FIFO fails!\n"); 24 return 1; 25 } 26 27 return 0; 28 }
有名管道比管道多了一个打开操作:open。
FIFO的打开规则:
如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
从FIFO中读取数据:
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
注:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
向FIFO中写入数据:
约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
对于设置了阻塞标志的写操作:
对于没有设置阻塞标志的写操作:
关于FIFO读写规则验证程序实例请参照博文Linux环境进程间通信(一)之管道。
管道常用于两个方面:(1)在shell中时常会用到管道(作为输入输入的重定向),在这种应用方式下,管道的创建对于用户来说是透明的;(2)用于具有亲缘关系的进程间通信,用户自己创建管道,并完成读写操作。
FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。
管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息。
要灵活应用管道及FIFO,理解它们的读写规则是关键。
APUE
Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)
标签:
原文地址:http://www.cnblogs.com/xiehongfeng100/p/4756961.html