1、管道的概念
本质:
- 内核缓冲区,打开管道时会在内存中分配一块空间
- 伪文件--不占用磁盘空间
特点:
- 两部分:读端,写端,对应两个文件描述符
- 数据写端流入,读端流出
- 操作管道的进程被销毁之后,管道自动被释放
- 管道只读或只写打开时会阻塞。需要两个进程同时打开,一个读,一个写。
- 当写端关闭时,读端不会阻塞
管道的原理:
- 内部实现方式:队列(环形队列)。
- 管道中有头指针:负责写。
- 管道中有尾指针:负责读。
- 刚开始头尾指针都指向头。
- 写数据是头指针不断向后移动,管道写满后又回头部循环写入,遇到尾指针(管道满,写阻塞)则无法写入
- 当尾指针读时遇到头指针(管道空,读阻塞)则读取结束
- 缓冲区大小:默认4k,内核会适当的调整,但有一定范围,不会无限放大
3、管道的局限性:
- 基于队列的数据结构,数据只能读取一次,不能重复读取。
- 半双工(双向,但不能同时工作)
- 匿名管道:适用于有血缘关系的进程
4、常见问题
- 有名管道和无名管道的区别?
无名:父子间进程通信。
有名:任意两个进程间通信。
- 数据在什么地方存放?
在内存中存放,但是主要、有名的管道文件在磁盘
- 管道时半双工还是全双工?
半双工
二、匿名管道
1、创建匿名管道
init pipe(int fd[2])
- fd-传出参数
- fd[0] -- 读端
- fd[1] -- 写端
示例:
# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
exit(0);
}
printf("pipe[0] = %d\n",fd[0]);
printf("pipe[1] = %d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
2、父子进程使用管道通信
- 单个进程可以使用管道完成读写操作
- 先打开管道后fork子进程
- 在父子进程利用管道通信过程中,因为数据只能读取一次,如果某一进程执行写操作,则需关闭该进程的读端,如果某一进程执行读操作,则需关闭读端。
示例:父进程查询当前所有进程信息并写入管道;子进程从管道中查询当前所有进程信息。
# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>
int main()
{
int fd[2];
int ret = pipe(fd);//打开管道
if(ret == -1)//管道打开失败
{
perror("pipe error");
exit(0);
}
pid_t pid = fork();//创建子进程
if(pid == -1)//子进程创建失败
{
printf("fork error");
exit(0);
}
//父进程
//将当前所有进程信息写入管道
//ps默认将信息输入到终端(即标准输出STDOUT),通过dup2将进程信息输入到管道写端
if(pid > 0)
{
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
exit(0);
}
//子进程
//查询管道中数据
//grep默认从当前终端查询数据(即标准输入STDIN),通过dup2函数使其从管道中查询数据
else if(pid == 0)
{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash",NULL);
}
close(fd[0]);
close(fd[1]);
return 0;
}
3、兄弟进程使用管道通信
原理和父子间进程通信相同
- 父进程无需对管道进行操作,关闭读写两端
- 子进程1执行写操作,关闭读端
- 子进程2执行读操作,关闭写端
示例:
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/wait.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
printf("pipe error");
exit(1);
}
int i;
for(i = 0; i < 2; i++)
{
pid_t pid = fork();
if(pid == 0)//子进程不再创建进程
{
break;
}
if(pid == -1)
{
printf("fork error");
exit(1);
}
}
if(i == 0)//子进程1
{
close(fd[0]);//关闭读操作
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
perror("execlp");
exit(1);
}
else if(i == 1)//子进程2
{
close(fd[1]);//关闭写操作
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash",NULL);
perror("grep");
}
else if(i == 2)//父进程
{
//关闭读写操作
close(fd[0]);
close(fd[1]);
pid_t wpid;
while((wpid = waitpid(-1,NULL,WNOHANG)) != -1)//循环回收子进程
{
if(wpid == 0)
{
continue;
}
printf("child died, pid = %d\n",wpid);
}
}
return 0;
}
4、管道的读写行为
1、读操作
- 有数据
read(fd) - 正常读,返回读出的字节数
- 无数据
写端全部关闭时,read接触阻塞,返回0,相当于读文件读到了尾部
写端没有全部关闭时,read阻塞,等待写端写入数据。
2、写操作
- 读端全部被关闭
管道破裂,进程被终止。读端全部被关闭时,内核会给进程发送信号SIGPIPE,进程收到该信号的默认处理是终止进程
- 读端没有被全部关闭
缓冲区写满时,write阻塞,等待读出数据,缓冲区没满时,write继续写数据。
3、如何设置非阻塞
- 默认读写两端都阻塞。
- 设置读端为非阻塞:
fcntl - 变参函数 功能:(1)复制文件描述符 - dup
(2)修改文件属性 - open的时候对应的flag属性
设置方法:(1)获取原来的flags:int flags = fcntl(fd[0],F_GETFL);
(2)设置新的flags:flag |= O_NONBLOCK;
(3)fcntl(fd[0], F_SETFL,flags);
5、查看管道缓冲区大小
- 命令:ulimit -a
- 函数:fpathconf
示例:
# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
exit(0);
}
long size = fpathconf(fd[0],_PC_PIPE_BUF);//查看缓冲区大小
printf("size = %d\n",size);//输出缓冲区大小
close(fd[0]);
close(fd[1]);
return 0;
}
三、fifo(有名管道)
1、特点
- 有名管道
- 在磁盘上存在这样的文件
- 伪文件,该文件在磁盘大小永远为0
- 在内核中有一个对应的缓冲区
- 半双工的通信方式
2、使用场景
- 没有血缘关系的进程间通信
3、创建方式
- 命令:mkfifo 管道名
- 函数:mkfifo
4、fifo文件可以使用IO函数进行操作
- open/close
- read/write
5、进程间通信
- 有名管道进程间的通信和文件操作相同。将管道看做是文件即可。
示例:
1、首先新建一个管道:mkfifo myfifo
2、向管道写入数据
# include <stdio.h>
# include <stdlib.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
# include <string.h>
# include <fcntl.h>
int main()
{
char *p = "hello world";
int fd = open("/home/FUJIA/cy1706/practice/myfifo",O_WRONLY);
if(fd == -1)
{
printf("open error");
exit(0);
}
while(1)
{
write(fd,p,strlen(p) + 1);
sleep(1);
}
close(fd);
}
3、从管道读出数据
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
int main()
{
int fd = open("/home/FUJIA/cy1706/practice/myfifo",O_RDONLY);
if(fd == -1)
{
printf("open error");
exit(0);
}
char buf[20];
while(1)
{
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
char buf[20] = {};
}
close(fd);
return 0;
}