问题:如何将一个“hello world”从a进程读取,从b进程输出。传统的一个方法:利用文件的读取来进行
但是有一个缺点,我们需要对文件的读写操作进程控制,并且文件是在磁盘中的,读写速度和内存不是一个级别,因此我们需要一个在内存中创建一个像文件一样的容器来解决这样的问题,因此就有了管道。
什么是管道
管道是Linux/UNIX系统中比较原始的进程间通信形式,它实现数据以一种数据流的方式,在多进程间流动。在系统中其相当于文件系统上的一个文件,来缓存所要传输的数据。
管道通信是最常见的通信方式之一,其是在两个进程之间实现一个数据流通的管道,该管道可以是双向或单向的。这个文件是一种“特殊的文件”,它和一般文件有些不同的。
管道分为有名管道和无名管道,
管道是半双工的(至少在大多数系统是这样的),数据只能向一个方向流动。需要双方通信时,需要建立起两个管道。
有名管道:在任意两个进程之间进行使用
无名管道:父子进程之间使用
有名 管道的创建
使用命名管道的操作和使用普通文件十分相似,可以使用系统调用open打开一个命名管道,使用read和write函数对命名管道进行读写,使用close关闭一个命名管道,若要删除一个命名管道,则使用系统调用unlink。
对有名管道进行读写操作
对管道进行写操作,管道写端关闭,读端read返回值为0
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
int main()
{
int fd = open("./fifo",O_WRONLY);
assert(fd!=-1);
printf("fd = %d\n",fd);
while(1)
{
printf("input:\n");
char buff[128] = {0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fd,buff,strlen(buff));
}
close(fd);
exit(0);
}
对管道进行读操作,管道为空,读操作会阻塞。
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
int main()
{
int fd = open("./fifo",O_RDONLY);
assert(fd!=-1);
printf("fd = %d\n",fd);
while(1)
{
char buff[128] = {0};
int n = read(fd,buff,127);
if(n==0)
{
break;
}
printf("n = %d,buff = %s\n",n,buff);
}
close(fd);
exit(0);
}
open有同步,需要一读一写进程才能打开一个管道文件,只有一个的话会阻塞在open位置
从FIFO中读取数据的规则是:
1、如果一个进程为了从FIFO中读取数据而阻塞打开了FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
2、如果有进程写打开FIFO,且当前FIFO为空,则对于设置了阻塞标志的读操作来说,将一直阻塞下去,直到有数据可以读时才继续执行;对于没有设置阻塞标志的读操作来说,则返回0个字节,当前errno值为EAGAIN,提醒以后再试。
3、对于设置了阻塞标志的读操作来说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因是:FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量,只要有数据写入即可。
4、**读打开的阻塞标志只对本进程第一个读操作施加作用,**如果本进程中有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。
5、如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
6、如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数少于请求的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
向FIFO中写入数据的规则是:
1、如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
2、对于设置了阻塞标志的写操作,当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。写入的数据长度小于等于PIPE_BUF时,那么或者写入全部字节,或者一个字节都不写入,它属于一个一次性行为,具体要看FIFO中是否有足够的缓冲区。
3、当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
4、对于没有设置阻塞标志的写操作,当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
5、当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。
无名管道
无名管道创建
pipe()创建无名管道
管道读写规则
管道内没有数据时,读端(read)发生阻塞,等待有效数据进行读取
管道容量被数据填满时,写端(write)发生阻塞,等待进程将数据读走再进行写入
如果所有管道写端对应的文件描述符被关闭,read返回0,但会将之前管道里的数据读完
如果所有管道的读端对应的文件描述符被关闭,write操作会产生信号,SIGPIPE,进而导致write进程退出
当要写入的数据量不大于管道的容量(PIPE_BUF)时,linux将保证写入的原子性
当要写入的数据量大于管道容量(PIPE_BUF)时,linux将不再保证写入的原子性
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
int main()
{
int fd[2];//f[0] r ,f[1] w
pipe(fd);
assert(pid!=-1);
if(pid==0)//子进程
{
close(fd[1]);
char buff[128] = {0};
int n = read(fd[0],buff,127);
if(n == 0)
{
wait(NULL);
exit(0);
}
printf("child buff=%s",buff);
close(fd[0]);
}
else
{
close(fd[0]);
char buff[128] = {0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
exit(0);
}
write(fd[1],buff,strlen(buff));
close(fd[1]);
}
sleep(100);
exit(0);
}