管道的概念
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间(父子进程,爷孙进程,兄弟进程),完成数据传输。调用pipe系统函数即可创建一个管道。管道有如下特质
1.其本质是一个伪文件(实为内核缓冲区)
2.有两个文件描述符引用,一个代表读端,一个代表写写端
3.规定数据从管道的写端流入,从读端流出
管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
1.数据一旦被读走,就不再管道中,不可反复读取
2.由于管道采用半双工通信方式。因此,数据只能在一个方向上流动
3.只能在有共有祖先的进程进程间使用
pipe函数
创建管道
函数原型:
int pipe(int pipefd[2]);
参数:
一个大小为2的int数组,如果管道创建成功,下标为0的文件描述符表示写端,下标为1的文件描述符为读端
返回值:
成功:0;失败:-1,设置errno
管道创建成功之后如何通信:
- 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
- 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道
- 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
管道示例代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if(pid == 0){
//son
sleep(3);
close(fd[0]);//关闭读端
printf("child write\n");
write(fd[1],"hello\n",6);
while(1){
sleep(1);
}
close(fd[1]);
}else if(pid > 0){
//parent
close(fd[1]);//关闭写端
char buf[12]={0};
while(1){
printf("parent read\n");
int ret = read(fd[0],buf,sizeof(buf));
if(ret == 0){
printf("read over!\n");
break;
}
if(ret > 0){
write(STDOUT_FILENO,buf,ret);
}
}
int status;
wait(&status);
if(WIFSIGNALED(status)){
printf("killed by %d\n",WTERMSIG(status));
}
//父进程只是关闭读写两端,但是不退出
while(1){
sleep(1);
}
close(fd[0]);
}
return 0;
}
管道的读写行为
使用管道需要注意一下4中特殊情况(假设都是阻塞I/O)
1.如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0,close一次引用计数-1),而仍然有从管道读端写入的数据,那么管道中生于的数据都被读取之后,再次read返回0,就像读到文件末尾一样。
2.如果有指向管道写端的文件描述符没有关闭,而持有管道的写端的进程也没有向管道中写数据,这是有进程冲管道中读数据,那么管道中生于的数据被读取后,再次read会阻塞,知道管道中有数据可读了。
3.如果所有指向读端的文件描述符都关闭了,这是有进程向管道的写端write,那么进程会收到一个SIGPIPE信号,通常会导致进程异常终止。当然也可以对SIGPIPE信号进行捕捉,不终止进程。
4.如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
管道缓冲区大小
可以通过 ulimit -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。
[root@VM_0_11_centos linux]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3893
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 100001
pipe size (512 bytes, -p) 8 //这里 512个字节
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 3893
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
管道的优劣
优点:简单,相比信号,套接字实现进程间通信,简单很多。
缺点:
- 只能单向通信,双向通信需建立两个管道。
- 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。
FIFO
FIFO通常被乘坐有名管道,以区分管道(PIPE)。管道只能用于有血缘关系的进程间通信。但通过FIFO,不想管的进程也能进行数据交换
FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标志一条内核通道。各个进程可以打开这个文件进行read/write,实际上是写在内核通道上。
创建方式
1.通过命令 mkfifo 来创建管道
mkfifo mkfifo(第二个参数为管道名)
[root@VM_0_11_centos linux]# mkfifo mkinfo
[root@VM_0_11_centos linux]# ll
total 28
-rwxr-xr-x 1 root root 8955 May 17 15:34 a.out
prw-r--r-- 1 root root 0 May 17 15:51 mkinfo
2.mkfifo文件也可以通过库函数来创建
函数原型:
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname, 文件路径
mode 文件权限
返回值:
成功:0; 失败:-1
常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。
3.读写示例代码:
fifo_r.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char *argv[])
{
if(argc != 2){
printf("./a.out fifoname\n");
return -1;
}
printf("begin oepn read...\n");
int fd = open(argv[1],O_RDONLY);
printf("end oepn read...\n");
char buf[256];
int ret;
while(1){
//循环读
memset(buf,0x00,sizeof(buf));
ret = read(fd,buf,sizeof(buf));
if(ret > 0){
printf("read:%s\n",buf);
}
}
close(fd);
return 0;
}
fifo_w.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char * argv[])
{
if(argc != 2){
printf("./a.out fifoname\n");
return -1;
}
// 当前目录有一个 myfifo 文件
//打开fifo文件
printf("begin open ....\n");
int fd = open(argv[1],O_WRONLY);
printf("end open ....\n");
//写
char buf[256];
int num = 1;
while(1){
memset(buf,0x00,sizeof(buf));
sprintf(buf,"xiaoming%04d",num++);
write(fd,buf,strlen(buf));
sleep(1);
//循环写
}
//关闭描述符
close(fd);
return 0;
}