Linux进程间通信—管道——详解


目录

一.管道

二.匿名管道(即管道)

三.命名管道


一.管道

1.定义

管道是Linux/UNIX系统中比较原始的进程间通信形式(简单的理解为两个进程之间通讯的方式),它将数据以一种数据流的方式,在多进程间流动。在系统中其相当于文件系统上的一个文件,来缓存所要传输的数据。

这里我们要了解三个概念:

  • 单工:只支持数据在一个方向上传输,在同一时间只有一方能接受或发送信息。
  • 半双工:允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,在同一时间只可以有一方接受或发送信息。
  • 全双工:允许数据同时在两个方向上传输,在同一时间可以同时接受和发送信息。

管道是半双工的,如果需要双方通讯时,则需要建立两个管道。

我们知道在Linux中有句话叫做“万物皆文件”,管道也是这样,它也是一个文件,只不过它比较特殊。它单独构成一种独立的文件系统。管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。

2.管道文件和普通文件的区别

我们知道当我们从一个文件中读取完数据后,这些数据依然存在于这个文件中。但当管道中的数据被读出时,管道中就没有数据了。因此从管道读数据是一次性操作数据一旦被读走,它就从管道中被抛弃,释放空间以便后续写入更多的数据。当管道中所有的数据都已被读取时,管道变为空,如果这时继续对管道进行读操作时,读操作将会被阻塞,直到某些数据被写入。

当我们在写入数据时,一般不需要考虑文件是否能够容纳下我们即将写入的数据。但是管道文件就不一样了,当我们在向管道写入文件时,管道可能会变满,当这种情况发生时,随后对管道进行的写入操作将会被阻塞,直到管道中的某些数据被读取,腾出足够的空间后写操作才会继续被执行。

3.管道的大小

管道是内核管理的一个固定大小的缓冲区,该缓冲区的大小为1 页,即4KB。

注:管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式。

4.管道的分类

管道没有名字,所以也称为匿名管道,由匿名管道我们又衍生出另一种管道,即命名管道

二.匿名管道(即管道)

1.匿名管道的特点

  • 匿名管道在系统中是没有实名的,并不可以在文件系统中以任何方式看到该管道。它只是进程的一种资源,会随着进程的结束而被系统清除。
  • 创建一个匿名管道时生成了两个文件描述符,但对于管道中所使用的文件描述符并没有路径名,也就是不存在任何意义上的文件,它们只是在内存中与某一个索引节点相关联的两个文件描述符。
  • 匿名管道只能用于具有亲缘关系的进程间(父子进程或兄弟进程之间)

2.匿名管道的创建与关闭

函数:

#include <unisted>
int pipe(int fd[2]);

功能:创建一个匿名管道

参数:参数fd[2]是一个长度为2的文件描述符数组,其中fd[0]是读出端的文件描述符,fd[1]是写入端的文件描述符。当函数成功返回后,则自动维护了一个从fd[1]到fd[0]的数据通道。

返回值:成功返回 0,失败返回 -1

管道的关闭:close函数

3.管道的读写

我们分别使用read和write函数来对管道进行读和写操作。

注意:管道的两端是固定了任务的。即管道的读出端只能用于读取数据,管道的写入端只能用于写入数据。

管道的读取规则:

  • 如果管道为空时对管道进行读操作,则会发生阻塞。
  • 如果管道非空
    • 管道中数据量小于请求的数据量时,则返回管道中现有的数据字节数。
    • 管道中数据量不小于请求的数据量,则返回请求的字节数。
  • 如果管道的写端不存在,则认为已经读到了管道的末尾,读函数返回的读出字节数为0;
  • 当读操作的返回值为0时,有两种意义
    • 管道中无数据并且写入端已经关闭(即写端不存在)
    • 管道中无数据,写入端依然存活

管道的写入规则

  • 如果管道为空,或者管道中又空闲空间,写操作就会立即试图向管道中写入数据,成功写入后返回实际写入的字节数。
  • 如果管道为满时对管道进行写操作,则会发生阻塞。

4.示例代码

父进程利用管道向子进程发送消息

#include <fcntl.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    int fd[2];
    pipe(fd);

    pid_t pid = fork();
    assert(pid != -1);
    if(pid == 0)
    {
        close(fd[1]);
        char buff[128] = {0};
        read(fd[0],buff,127);
        printf("[child]buff:%s\n",buff);
        close(fd[0]);
    }
    else
    {
        close(fd[0]);
        printf("[parent]input:");
        char buff[128]={0};
        fgets(buff,128,stdin);
        write(fd[1], buff, strlen(buff));
        close(fd[1]);
    }
    exit(0);
}

执行结果:

三.命名管道

1.命名管道的特点

  • 命名管道可以用于任何两个进程间通信,而并不限制只有具有亲缘关系的进程之间。
  • 命名管道作为一种特殊的文件存放于文件系统中,而不是像管道一样存放于内存(使用完毕后消失)。当进程对命名管道的使用结束后,命名管道依然存放于文件系统中,除非对其进行删除操作,否则该命名管道不会消失。创建一个FIFO文件类似于创建一个普通文件,并且FIFO文件和其他文件一样,也是可以通过路径名来访问的。

2.命名管道的创建

函数:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname , mode_t mode);

功能:创建一个命名管道

参数:该函数的第一个参数是一个普通的路径名,也就是创建后FIFO文件的名字。第二个参数与打开普通文件的open函数中的mode参数相同。

pathname:一个Linux路径名,它是FIFO的名字。即每个FIFO与一个路径名相对应;
mode:指定的文件权限位,类似于open函数的第三个参数。即创建该FIFO时,指定用户的访问权限,有以下值:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IWOTH。

返回值:mkfifo函数默认指定O_CREAT | O_EXECL方式创建FIFO,如果创建成功,直接返回0。如果FIFO已经存在,则创建失败,会返回-1并且errno置为EEXIST。对于其他错误,则置响应的errno值; 

另外,我们也可以直接使用系统命令mkfifo直接创建一个管道文件。

3.示例代码

创建一个命名管道

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc , char *argv[])
{
	mode_t mode = 0666;
	if(argc != 2)
	{
		printf("error\n");
		exit(0);
	}
	if(mkfifo(argv[1], mode) < 0 )
	{
		perror("failed to mkfifo\n");
		exit(0);
	}
	else
	{
		printf("Success!\n");
	}
	exit(0);
}

执行结果:

使用命令mkfifo创建命名管道

若要删除一个命名管道,则使用系统调用unlink

4.命名管道的使用

当创建一个FIFO后,它必须以只读方式打开或者只写方式打开,所以可以用open函数,当然也可以使用标准的文件I/O打开函数,例如fopen来打开。也就是说命名管道FIFO比管道多了一个打开操作open。原因很简单,命名管道是Linux下的一种文件类型,而管道不是。

mkfifo的一般使用方式是:通过mkfifo创建FIFO -> 然后调用open -> 以读或者写的方式之一打开FIFO -> 然后进行数据通信。

5.命名管道的读写

从命名管道中读取数据的规则:

  • 如果一个进程为了从FIFO中读取数据而阻塞打开了FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。

  • 如果有进程写打开FIFO,且当前FIFO为空,则对于设置了阻塞标志的读操作来说,将一直阻塞下去,直到有数据可以读时才继续执行;对于没有设置阻塞标志的读操作来说,则返回0个字节,当前errno值为EAGAIN,提醒以后再试。

  • 对于设置了阻塞标志的读操作来说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因是:FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量,只要有数据写入即可。

  • 读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程中有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。

  • 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。

  • 如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数少于请求的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。

从命名管道中写入数据的规则:

  • 如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
  • 对于设置了阻塞标志的写操作,当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。写入的数据长度小于等于PIPE_BUF时,那么或者写入全部字节,或者一个字节都不写入,它属于一个一次性行为,具体要看FIFO中是否有足够的缓冲区。
  • 当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
  • 对于没有设置阻塞标志的写操作,当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
  • 当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。

6.代码

两个进程利用命名管道通讯

write.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char* argv[])
{
	int fd;
	int ret;
	ret = mkfifo("myfifo",0600);
	if(ret != 0)
	{
		perror("mkfifo error");
	}
	fd = open("myfifo",O_WRONLY);
	if(fd < 0)
	{
		perror("open fifo error");
	}
	char buff[128] = "Hello World!";
	write(fd,buff,strlen(buff));
	printf("write to myfifo buff=%s\n",buff);
	while(1);
	close(fd);
	exit(0);
}

read.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char *argv[])
{
	int fd;
	int ret;
	ret = mkfifo("myfifo",0600);
	if(ret != 0)
	{
		perror("mkfifo error");
	}
	fd = open("myfifo",O_RDONLY);
	if(fd < 0)
	{
		perror("open fifo error");
	}
	while(1)
	{
		char buff[128]={
			0
		};
		read(fd,buff,sizeof(buff));
		printf("read from myfifo buff=%s\n",buff);
		sleep(1);
	}
	close(fd);
	exit(0);
}

执行结果:

 

 

 

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值