进程间通讯-管道,以及open函数打开管道阻塞的原则
管道
队列实现
1.1无名管道
在文件IO中创建一个文件或打开一个文件是由open函数来实现的,它不能创建管道文件。只能用pipe
函数来创建管道。
int pipe(int fd[2])//unistd.h
功能:创建管道,为系统调用
参数:就是得到的文件描述符。可见有两个文件描述符:fd[0]和fd[1],管道有一个读端fd[0]用来读和一个写端fd[1]用来写,这个规定不能变。相当于管道两边各有一个指针。
执行完该函数后,fd[0]和fd[1]各会放置两个文件描述符,一头一尾。一般情况下,若没有动标准输入输出,f[0] = 3,f[1] = 4.因为linux会对每个进程默认分配三个文件描述符。
返回值:成功是0,出错是-1;
注意:
Ø 管道中的东西,读完了就删除了;
Ø 如果管道中没有东西可读,则会读阻塞。同样 写堵塞同样存在。
例子:父进程先运行,子进程后运行,用pipe实现
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
int main(){
int fd[2] = {0};
int ret = pipe(fd);
if(ret>0) printf("create pipe success\n");
pid_t pid;
pid = fork();
//在fork()前后无所谓,因为都是在两个不同的栈里
char process_inter = 0;
//child
if(pid == 0){
read(fd[0],&process_inter,1);
while(process_inter==0);
for(int i = 0; i < 10;i ++)
printf("child run\n");
close(fd[0]);
close(fd[1]);
}else if(pid>0){
process_inter = 1;
write(fd[1],&process_inter,1);
for(int i = 0; i < 10;i ++)
printf("parent run\n");
}else{
printf("fork wrong\n");
}
while(1);
return 0;
}
这里注意,process_inter设置为char比较好,因为char是一个字节长度大小,可以很方便的write。其次,若不加while(1),则是父进程先运行完,该进程退出之后,子进程再运行结束。
注意:pipe()函数必须在fork前,要让父子进程均继承主进程的fd管道
无名管道的缺点:只能实现父子进程(有亲缘关系进程)之间的通信。
1.2有名管道
也是一种linux文件类型。
概述:
-
创建这个文件节点,不可以通过open 函数,open 函数只能创建普通文件,不能创建特殊文件(管道-mkdifo,套接字-socket,字符设备文件-mknod,块设备文件-mknod,符号链接文件-ln –s,目录文件mkdir)
-
管道文件只有inode号,不占磁盘块空间,和套接字、字符设备文件、块设备文件一样,只有文件节点。普通文件和符号链接文件及目录文件,不仅有inode号,还占磁盘块空间。
mkfifo 用来创建管道文件的节点,没有在内核中创建管道。
只有通过open 函数打开这个文件时才会在内核空间创建管道。
创建有名管道
mkfifo
int mkfifo(const char *filename,mode_t mode);
功能:创建管道文件
参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。
返回值:创建成功返回0,创建失败返回-1。
mkfifo只会生成节点,并不会真正在内存里创建管道队列,在使用open函数打开该节点的时候,才会创建一个管道。
1、open FIFO文件阻塞的坑
与打开其他文件一样,FIFO文件也可以使用open调用来打开。注意,mkfifo函数只是创建一个FIFO文件,要使用命名管道还是将其打开。
但是有两点要注意:
1、就是程序不能以O_RDWR模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程就会读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递。
2、就是传递给open调用的是FIFO的路径名,而不是正常的文件。
打开FIFO文件通常有四种方式,
open(const char *path, O_RDONLY); // 1
open(const char *path, O_RDONLY | O_NONBLOCK); // 2
open(const char *path, O_WRONLY); // 3
open(const char *path, O_WRONLY | O_NONBLOCK); // 4
在open函数的调用的第二个参数中,你看到一个陌生的选项 O_NONBLOCK,选项 O_NONBLOCK 表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。
open调用的阻塞是什么一回事呢?很简单,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。
如:下面的程序先打开second,再打开first。但要求是first先执行。
因为在second里int fd = open("./mypipe",O_WRONLY);
会阻塞,直到first程序执行int fd = open("./mypipe", O_RDONLY);
才会执行。
second.c
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
int main(){
int res = mkfifo("./mypipe",0777);
if(res<0){
printf("create pipe failed\n");
}
printf("wait for first open the pipe\n");
int fd = open("./mypipe",O_WRONLY);
if(fd<0){
printf("open piple faied");
return -2;
}
for(int i = 0; i<20;i++) printf("process 1 run\n");
close(fd);
return 0;
}
first.c
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
int main(){
char process_inter = 0;
printf("open function will bloked until process1 build the pipe and close it\n");
int fd = open("./mypipe", O_RDONLY);
close(fd);
for(int i = 0; i<20;i++) printf("process2 run\n");
return 0;
}