管道
fork()后,父子进程将有各自独立的存储空间,他们之间是无法像同一进程之间通过传参或使用全局变量来进行数据交换的。
怎么办?管道就是一种简单的实现进程间通信的方式。
管道被创建后,将得到两个文件描述符file_des[1]和file_des[0](描述符的名字取决于管道创建的参数)。
file_des[1]用于向管道中写入,file_des[0]用于从管道中读。读的顺序和写的顺序一致。
这两个文件描述符可被子进程继承。所以,父子进程只要操作各自的这两个文件描述符就可以实现彼此通信。
同样,一个进程创建了管道后,所有继承于它的子进程、孙子进程之间都可以通过这个管道来进行通信。
管道的创建方式:
通过系统调用pipe,头文件unistd.h。
int pipe (int file_des[2]); 创建失败返回-1,成功返回0,并得到两个文件描述符file_des[1]和file_des[0]。
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main(){ int file[2]; //文件描述符数组 char buffer[BUFSIZ+1] const char data="hello"; int res = pipe(file); if(res == 0) //pipe调用成功,管道创建 { printf("pipe created!\n"); if(fork()==0){ read(file[0],buffer,BUFSIZ); //子进程从管道中读 printf("read %s\n",buffer); exit(EXIT_SUCCESS); }else{ write(file[1],data,strlen(date)); //父进程向管道中写 printf("write %s\n",data); } exit(EXIT_SUCCESS); }
那么,将会得到看到输出:
write hello
read hello
也有可能上下两行对调(与进程调度有关)。上述例子中fork()后,if 和 else里的内容也可对调,变成父进程读,子进程写。
该图清晰地说明了不同进程通过管道交换数据的方式。注意,父子进程分别有各自用于读写管道的文件描述符。
对于管道的write操作,若管道已满,write会被阻塞,直到另一头有read将管道中数据取走;若所有read端都关闭了,write将返回-1,并设置error为EPIPE。
对于管道的read操作:
1.若管道为空,read将阻塞,直到另一头有write向管道写;
2.若管道不为空,若read调用计划读取的字节数a > 管道中的字节数b ,那么就读取b个字节,返回b;若a < b,则读取a个字节,返回a。
3.管道的写端已关闭(必须是引用这个管道的所有进程的file_des[1]均关闭),read将返回0。
管道应注意的问题:
当两个进程同一时间段要实现双向交换数据时,应该使用两个管道,否则一个进程对同一个管道既读又写,很容易读到自己写进去的内容。
两个进程同一时间段使用两个管道进行双向数据交换时,安排不当有可能产生死锁问题:
例:父进程向管道A写10个数据,子进程从A读1个数据,读到的数据经处理写入管道B,父进程从B获得处理结果。
此时,若父进程向A写入的某个数据过大,管道已满,父进程write将阻塞,此时,子进程向B写入的数据也很大,管道B也满了,子进程write也阻塞,这时会陷入死锁。