在Linux下,每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到所以进 程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication).
1、管道(pipe)
管道是一种最基本的IPC机制,由pipe函数创建:
#include <unistd.h>
int pipe(int filedes[2]);
调⽤用pipe函数时在内核中开辟一块缓冲区(称为管道)⽤用于通信,它有⼀一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。 开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信:
在一个进程调用pipe函数创建管道,得到两个文件描述符指向读端和写段。
进程调用fork()创建子进程,那么子进程会继承父进程的文件描述符,所以子进程也有两个文件描述符指向管道两端。因为管道只能进行单向通信,所以关闭父进程的读端和子进程的写端(也可以关闭父进程写端和子进程读端)。如下图
这是创建管道进行通信的过程,管道创建好之后再写端写入,在读端就可以读到写入的数据。下面编写代码进行测试。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5
6 int main()
7 {
8 int _pipe[2];
9 int ret=pipe(_pipe); //创建管道
10 if(ret==-1)
11 {
12 printf("create pipe error!");
13 return 1;
14 }
15
16 pid_t id=fork(); //创建子进程
17 if(id<0)
18 {
19 printf("fork error!");
20 return 2;
21 }
22 else if(id==0) //子进程
23 {
24 close(_pipe[0]); //关闭读端
25 int i=0;
26 char *_mesg_c=NULL;
27 while(i<100)
28 {
29 _mesg_c="i am child";
30 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
31 sleep(1);
32 i++;
33 }
34 }
35 else //父进程
36 {
37 close(_pipe[1]); //关闭写端
38 char _mesg[100];
39 int j=0;
40 while(j<100)
41 {
42 memset(_mesg,'\0',sizeof(_mesg));
43 read(_pipe[0],_mesg,sizeof(_mesg));
44 printf("%s\n",_mesg);
45 j++;
46 }
47 }
48 return 0;
49 }
运行结果如图:
其中“i am child”每隔一秒打印一次。
使⽤用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读后,再次read会返回0,就像读到文件末尾一样。
2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3. 如果所有指向管道读端的⽂文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
4. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写⼊入数据并返回。