进程间通讯-管道,以及open函数打开管道阻塞的原则

管道

队列实现

1.1无名管道

img

在文件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文件类型。

概述:

  1. 创建这个文件节点,不可以通过open 函数,open 函数只能创建普通文件,不能创建特殊文件(管道-mkdifo,套接字-socket,字符设备文件-mknod,块设备文件-mknod,符号链接文件-ln –s,目录文件mkdir)

  2. 管道文件只有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;
}
posted @ 2020-12-22 10:34  lsxkugou  阅读(498)  评论(0编辑  收藏  举报