进程间通信--管道通信
进程间通信
在两个进程之间,每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到。比如,在父进程中的全局变量,如果在子进程中去改变这个全局变量,则子进程中被改变的这个值不会去影响父进程,因为子进程中的所有数据都是通过写时拷贝拷自父进程的,两个进程的地址空间不同:
以代码为例:
运行代码后我们可以发现:
父进程和子进程之间并没有共享数据,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓
冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
进程间通信——管道通信
管道是一种最基本的IPC机制,有pipe函数创建,pipe的基本格式为int pipe(int fds[2]);
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过fds参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(fds[0]);或者write(fds[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。
让我们来试一下pipe函数:
运行一下后我们发现确实是返回的文件描述符3和4
那么开辟了管道之后如何实现两个进程之间的通信呢?我们可以按照下面的步骤通信:
1.父进程创建管道:
父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2.父进程fork出子进程:
父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道 。
3.父进程关闭fds[0],子进程关闭fds[1]:
父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
接着我们通过代码来演示:
运行代码后可以得到:
我们可以发现“i am child”这句话是在子进程中的,即由子进程写到管道里面的;然后打印是在父进程里面打印的,即由父进程从管道里面读到的。这就完成了父子进程之间的通信,但同时,我们知道在通信前,子进程关闭了写端,父进程关闭了读端;之所以这样处理是因为需要去避免错误的发生,因为两个进程通过一个管道只能实现单向通信 。比
如上面的例子,子进程写父进程读,如果有时候也需要父进程写子进程读,就必须另开一个管道。
这种管道我们又称为匿名管道,它的一些特点是:
①只能进行单向通信;
②管道依赖于文件系统,进程退出,管道随之退出,即生命周期是随进程的;
③常用于父子进程间的通信,这种管道只能用于具有亲缘关系的进程;
④管道是基于流的,是按照数据流的方式读写的;
⑤同步访问,即管道访问是自带同步机制的。
另外,使用管道需要注意一下四种特殊情况(假设都是阻塞IO操作,没有设置O_NONBLOCK标志);
1、如果所有指向管道写端的文件秒描述符都关闭了(管道写端的计数为0),而任然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
运行上述代码得到
我们可以看到这里在运行5次之后的结果。
2、如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),⽽持有管道写端的
进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
运行代码可以看到在父进程读完之后会等待100秒,父进程读完后会去阻塞等待。
3、如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
运行上面的代码可以得到
然后我们可以看到第13的信号就是SIGPIPE。
4、如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的
进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再 次write会阻塞,直到管道中有空位置了才写入数据并返回。
运行代码后得到:
我们会发现,写端在一直写,直到写满缓冲区。
进程间通信——命名管道
命名管道与匿名管道的最大不同就在于它可以用于不具备亲缘关系的进程进行通信。
管道的一个不足之处是没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道( named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据将首先从管道中读出。
Linux下有两种方式创建命名管道。一是在Shell下交互地建立一个命名管道,二是在程序中使用系统函数建立命名管道。Shell方式下可使用mknod或mkfifo命令,下面命令使用mknod创建了⼀个命名管道:mknod
namedpipe
创建命名管道的系统函数有两个: mknod和mkfifo。两个函数均定义在头文件sys/stat.h,
函数原型如下:
#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);
下面我们使用代码验证:
clinet:
server:
然后运行两个代码:
在client这边输入的,都能通过命名管道达到server端。
同时,生成了fifofile文件
管道 |
管道容量
首先我们可以通过ulimit -a这个命令来查看:
接着我们来验证一下管道容量的大小:
得到的结果为: