管道:进程间传递数据(A进程将"hello world"传递给B进程),管道分为有名管道和无名管道。
实现进程间通讯的原理:
就像现实中管道的两端一样,由一个进程进行写操作,其余进程进行读操作,如果管道为空,那么read会阻塞,如果管道为满则write会阻塞。
分类:管道可以分为有名管道和无名管道两类
一、有名管道(命令管道):在磁盘上会存一个管道文件标识,但是管道文件并不占磁盘空间(block),而是占一个inode结点,数据会缓存在内存上。
使用限制:权限,单机(有名管道可以应用于同一台主机上的有权限访问在n个进程间的通信)
实现机制:
1、管道文件使用:
在命令行上创建命名管道:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char * filename , mode_t mode);
int mknod(const char * filename , mode_t mode|S_IFIFO,(dev_t)0);
filename :FIFO文件名;
mode:同open类似。
返回值:成功0,出错-1
一旦mkfifo函数创建了一个FIFO,就可以用一般的文件I/O函数(open打开;int write写;read读;close关闭;unlink 删除管道)都可以用于FIFO。
2、会阻塞 A WRONLY B RDONLY 必须至少同时存在一对读写
(1)如果所有读端关闭,则写端也会关闭,反之亦然;
(2)读写次数没有直接联系------>字节流服务
(3)如果写端保持但并没有写数据,则读端阻塞;
(4)如果读端保持并没有获取数据,写端将在管道内存写满时,写端阻塞。
注:只有在管道的读端存在时,向管道中写入数据才有意义
3、FIFO文件在使用上和普通文件有相似之处,但是也有不有
不同之处:
(1) 读取fifo文件的进程只能以”RDONLY”方式打开fifo文件。
(2)写fifo文件的进程只能以”WRONLY”方式打开fifo
(3)fifo文件里面的内容被读取后,就消失了。但是普通文件里面的内容读取后还存在
其他的都和文件操作一样,
以下是一个例子:
首先:mkfifo FIFO 创建一个有名管道文件FIFO
写端:a.c
读端:b.c
运行结果:
4、提出一个问题为什么管道文件不存在磁盘上?
因为进程是在内存上运行的,若将管道文件存放在磁盘上第一是效率不高,第二是没有必要.因为进程间通信的数据一端写一端读,之后管道文件就不需要了.
二、无名管道:不会存在管道文件标识,其原理是父子进程之间共享fork之前打开的文件描述符,无名管道的数据内容缓存在内存上。
使用限制:仅能用于父子进程之间
实现机制:
使用:
#include<unistd.h>
int pipe(int filedes[2]);
返回值:若成功则返回0,若出错则返回-1;
参数filedes返回两个文件描述符:创建无名管道并打开,使得fds[0]为读端、fds[1]为写端
单个进程中的管道几乎没有任何用处。通常,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程(或反向)的IPC通道。如图显示的情况:
调用fork之后做什么取决于我们想要有的数据流的方向。对于父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程则关闭写端(fd[1])(构造子进程到父进程的管道,父进程关闭fd[1],子进程则关闭fd[0])。下图显示了在此之后描述符的安排:
当管道的一端被关闭后,下列两条规则起作用:
(1)当一个写端已被关闭的管道时,所有数据都被读取后,read返回0,以指示到达了文件结束处。读取一个无效的文件描述符,read将看做一个错误并返回-1。
(2)如果一个读端端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE。
把管道用作标准输入和标准输出
#include<unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one,int file_descriptor_tow);
dup调用目的是打开一个新的文件描述符,与作为参数的那个已有文件描述符指向同一个文件(或管道),新文件描述符总是取最小的可用值。
dup2调用目的创建新的文件描述符或者与参数file_descriptor_tow相同。或者是第一个大于该参数的可用值
标准输入的文件描述符总是0,而dup返回的新的文件描述符又总是使用最小可用的数字。因此我们首先关闭文件描述符0然后调用dup,那么新的文件描述符就将是数字0.因为新的文件描述符是复制一个已有的文件描述符,所以标准输入就会改为指向一个我们传递给dup函数的文件描述符所对应的文件或管道。创建了两个文件描述符,其中一个是标准输入。
例子:
main.c
运行结果:
父子进程必须关闭一对读写,并且不能是统一进程的读写,原因:1、逻辑上保持管道的半双工通信。2、防止一端关闭对应的读或写,二另一端无法感知。
为什么不用全局变量传递数据?
父进程创建子进程,子进程继承父进程的数据,但是不是进程间的通信。
不论是有名管道还是无名管道,都是是半双工的,数据只能向一个方向流动在同一时刻只能一端读一端写;需要双方通信时,需要建立起两个管道;
总结:
●无名管道
主要用于父进程与子进程之间,或者两个兄弟进程之间。在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。因此,每个管道都是单向的,当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。这种传输遵循“先入先出”(FIFO)的规则。
●有名管道
命名管道是为了解决无名管道只能用于近亲进程之间通信的缺陷而设计的。命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。