pipe
头文件:#include<unistd.h>
功能:创建一个无名管道
函数原型:int pipe(int fd[2]);//分别以读、写方式打开,所以有两个文件描述符
参数:
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误码
管道的特点
-管道自带互斥与同步机制
-管道只能单向通信
-只要有血缘关系的两个进程就可以进行进程间通信
-管道也是文件
-管道的生命周期随进程(进程退出管道随即释放)
-提供面向字节流的服务
用fork创建一个子进程(以父进程为模板),子进程PCB的大部分信息都是来源于父进程,子进程也有一个文件表和父进程的文件表保持一致,数组也保持一致,数组中的内容也是相同的,指向也是相同的。进程间通信的本质是让两个不同的进程看到一份公共的资源。file里写的数据肯定会被刷新到硬盘上,但在刷新到内存前,会有一个缓冲区,这个缓冲区就可以被父、子进程同时利用起来。
但管道只能单向通信,若想让父进程读,子进程写,就要关闭父进程的写端,关闭子进程的读端。那为什么不直接以读的方式打开父进程,写的方式打开子进程呢???为什么还要关???若父进程不以写方式打开,子进程就拿不到写方法,所以必须得读写打开。此时我们就有了一个单向数据通信的信道。
实现代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id == 0)
{
//child->w
close(fd[0]);
const char* msg = "hello f,I am c!\n";
while(1)
{
sleep(1);//子进程每隔一秒写一条
write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
}
else
{
//father->r
close(fd[1]);
char buf[64];
while(1)
{
ssize_t s = read(fd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = '\0';
printf("f say : %s\n",buf);
}
}
}
}
}
运行结果:
下面分析匿名管道的五种情况:
1.如果管道的写端一直在写,而读端不关闭自己的读文件描述符,但也不读
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id == 0)
{
//child->w
close(fd[0]);
int count = 0;
const char* msg = "hello f,I am c!\n";
while(1)
{
write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
printf("%d\n",count++);//写一次count++
}
else
{
//father->r
close(fd[1]);
char buf[64];
while(1)
{
}
}
}
}
写满了不能重头覆盖写,一旦覆盖写会出现数据的二异性问题。不能再写子进程就会被卡住,count值不再变化
一瞬间写了4千多次,写满了数字不再变,为保证管道在读端读,写端才写,所以子进程卡住了。
2.让子进程先写满父进程再读
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id == 0)
{
//child->w
close(fd[0]);
int count = 0;
const char* msg = "hello f,I am c!\n";
while(1)
{
write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
printf("%d\n",count++);//写一次count++
}
else
{
//father->r
close(fd[1]);
char buf[64];
while(1)
{
sleep(5);
ssize_t s = read(fd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = '\0';
printf("f say : %s\n",buf);
}
}
}
}
}
此时一瞬间子进程写满,父进程5秒之后读,父进程读完管道就空出来了,父进程继续每隔5秒读一条消息,当父进程在读的时候,子进程也继续再写。
小结
写方一直在写,读端不读,但读端也不关闭文件描述符,写方在把管道写满之后就会停下来,等待别人取数据。
3(1).写方写一条消息后,10秒钟之后再写下一条消息,读方立即把数据读出来,管道里没有数据,读方会阻塞式等待。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id == 0)
{
//child->w
close(fd[0]);
const char* msg = "hello f,I am c!\n";
while(1)
{
write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
sleep(10);
}
else
{
//father->r
close(fd[1]);
char buf[64];
int count = 0;
while(1)
{
ssize_t s = read(fd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = '\0';
printf("f say : %s\n",buf);
}
printf("f read %d\n",count);
}
}
}
}
管道里没有数据,读方会阻塞式等待,直到有信息(10秒钟之后)
如果读写双方都不关文件描述符,一个不写或一个不读都会导致对方要等你,这就叫同步。
3(2).如果写端一直在写,然后不再写并且把写端关闭,而读端一直在读,直到把管道里的数据读完
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id == 0)
{
//child->w
close(fd[0]);
int count = 0;
const char* msg = "hello f,I am c!\n";
while(1)
{
write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
printf("%d\n",count++);
if(count > 10)
{
close(fd[1]);//关闭写端
break;
}
sleep(1);//每隔1秒写一次
}
else
{
//father->r
close(fd[1]);
char buf[64];
while(1)
{
ssize_t s = read(fd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = '\0';
printf("f say : %s\n",buf);
}
else if(s == 0)//将管道里的数据读完后会返回一个0值,代表读到文件结尾
{
printf("pipe done,break!\n");
break;
}
}
}
}
}
读10次后,子进程会关闭自己的写文件描述符,父进程就读到0了
4.写端一直在写,读端不但不读,还关闭掉了自己的读文件描述符
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id == 0)
{
//child->w
int count = 0;
close(fd[0]);
const char* msg = "hello f,I am c!\n";
while(1)
{
write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
printf("%d\n",count++);
sleep(1);//每隔1秒写一次
}
else
{
//father->r
close(fd[1]);
char buf[64];
while(1)
{
ssize_t s = read(fd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = '\0';
printf("f say : %s\n",buf);
sleep(3);
break;
}
close(fd[0]);
}
int status = 0;
wait(&status);
printf("sig : %d\n",status&0x7F);//status数字当中的次7位表示退出信号
}
}
}
写端的进程会立即被操作系统用信号终止
子进程一直在写,3秒钟后父进程读取到子进程以13号信号退出
13号信号为SIGPIPE
当写端一直在写,读端不但不读还把自己的读文件描述符关闭,写端就会因为触发异常而被操作系统直接终止,操作系统向目标进程发送13号信号
总结一下以上四种情况:
(1)写端一直在写,读端不读也不关,写端写满后就会阻塞
(2)读端一直在读,写端也一直在写,可是写端突然不写了,但不关闭写文件描述符,读端一直在读,当管道数据为空时,就会被阻塞
(3)写端不但不写了,还关闭了写文件描述符,读端一直在读,管道内的数据越来越少,最后读到0(即文件结尾)
(4)写端一直在写,读端不但不读而且还关闭了读文件描述符,一旦再写入就会触发操作系统用13号信号(SIGPIPE)异常终止目标进程