管道的概念:
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1.其本质是一个伪文件(实为内核缓冲区)。
2.由两个文件描述符(fd[0]、fd[1])引用,一个表示读端,一个表示写段。
3.规定数据从管道的写端流入管道,从读端流出。
管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
①数据不能进程自己写,自己读。
②管道中数据不可反复读取。一旦读走,管道中不再存在。
③采用半双工通信方式(对讲机),数据只能在单方向上流动
④只能在有公共祖先(亲缘关系)的进程间使用管道
常见
管道:
实现原理:内核借助环形队列机制,使用内核缓冲区实现的。
特性: ①伪文件
②管道中的数据只能一次读取。
③数据在管道中,只能单向流动。
局限性:1.自己写,不能自己读
2.数据一读走,管道就没有了
3.半双工通信
4.血缘关系进程可用
父子进程之间共享文件描述符
创建管道:
pipe函数: 创建并打开管道
int pipe(int pipefd[2]);
参数: fd[0]:读段
fd[1]:写段
返回值: 成功:0
失败:-1
例程
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int ret;
int fd[2];
pid_t pid;
char buf[1024];
char *str = "hello pipe\n";
//创建管道
ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
}
//创建父子进程
pid=fork();
if(pid>0)
{
close(fd[0]);
write(fd[1],str,strlen(str));
sleep(1);
close(fd[1]);
}
else if(pid==0)
{
close(fd[1]);
ret = read(fd[0],buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
close(fd[0]);
}
return 0;
}
执行结果如下:
管道的读写行为:
读管道:
1.管道有数据,read返回实际读到的字节数。
2.管道无数据。(1)无写段,read返回0(读到文件尾)
(2)有写段,read阻塞等待。
证明例程如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int ret;
int fd[2];
pid_t pid;
char buf[1024];
char *str = "hello pipe\n";
ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
}
pid=fork();
if(pid>0)
{
close(fd[0]);
sleep(3);
close(fd[1]);
}
else if(pid==0)
{
close(fd[1]);
ret = read(fd[0],buf,sizeof(buf));
printf("son read ret =%d\n",ret);
write(STDOUT_FILENO,buf,ret);
close(fd[0]);
}
return 0;
}
执行结果如下:
写管道:
1.无读端,异常终止。 (SIGPIPE导致的)
2.有读端: (1)管道已满,阻塞等待。
(2)管道未满,返回写出的字节个数。
用管道实现父子进程ls | wc -l
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fd[2];
int ret;
pid_t pid;
ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
}
pid=fork();
if(pid == -1)
{
perror("fork error");
}
else if(pid>0)
{
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
sleep(1);
execlp("ls","ls",NULL);
}
else if(pid==0)
{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("wc","wc","-l",NULL);
}
return 0;
}
执行结果如下:
练习:使用管道实现兄弟进程间通信。兄:ls 弟:wc -l 父:等待回收子进程。
要求:使用“循环创建N个子进程”模型创建兄弟进程,使用循环因子i标示。注意管道读写行为。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int fd[2];
int ret;
pid_t pid;
ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
}
for(int i=0;i<2;i++)
{
pid=fork();
if(pid == -1){
perror("fork error");
}
else if(pid==0){
break;
}
if(i==2){
close(fd[0]);
close(fd[1]);
wait(NULL);
wait(NULL);
}else if(i==0){
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ls","ls",NULL);
perror("execlp error");
}else if(i==1){
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("wc","wc","-l",NULL);
perror("execlp error");
}
}
return 0;
}
测试:是否允许,一个pipe有一个写端,多个读端呢?
是否允许有一个读端多个写端呢?
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
int fd[2],i,n;
char buf[1024];
int ret = pipe(fd);
if(ret == -1){
perror("pipe error");
exit(1);
}
for(i=0;i<2;i++){
if((pid=fork())==0)
break;
else if(pid==-1){
perror("fork error");
exit(1);
}
}
if(i==0){
close(fd[0]);
write(fd[1],"1.hello\n",strlen("1.hello\n"));
}else if(i==1){
close(fd[0]);
write(fd[1],"2.world\n",strlen("2.world\n"));
}else{
close(fd[1]);
n=read(fd[0],buf,1024);
write(STDOUT_FILENO,buf,n);
for(i=0;i<2;i++)
wait(NULL);
}
return 0;
}
执行结果如下:
管道缓存区大小
ulimit -a
pipe size (512bytes,-p)8
管道的优劣
优点:简单,相比信号,套接字实现进程间通信,简单很多。
缺点:1.只能单向通信,双向通信需要建立两个管道。
2.只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用FIFO有名管道解决。
FIFO
FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。
FIFO是Linux基础类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。
(它存在的意义就是解决无亲缘关系的进程之间的通信)
命名管道fifo的创建和原理
创建方式:
1.命名:mkfifo 管道名
2.库函数:int mkfifo(const char *pathname,mode_t mode);
成功:0;
失败:-1;
一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可使用于fifo。如:close、read、write、unlink等。
fifo实现非亲缘关系进程间通信
fifo_w.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd,i;
char buf[4096];
if(argc<2){
printf("please input fifoname:");
return -1;
}
fd=open(argv[1],O_WRONLY);
if(fd<0){
perror("open error");
}
i=0;
while(1){
sprintf(buf,"hello fanfan %d\n",i++);
write(fd,buf,strlen(buf));
usleep(10000);
}
close(fd);
return 0;
}
fifor.c
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd,len;
char buf[4096];
if(argc<2){
printf("please input fifoname\n");
return -1;
}
fd=open(argv[1],O_RDONLY);
if(fd<0){
perror("open error");
}
while(1){
len=read(fd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,len);
sleep(1);
}
close(fd);
return 0;
}
执行结果如下:
vsp fifor.c