进程间通信:管道

进程间通信(IPC)总结

信号也是进程间通信的一种机制,尽管其主要作用不是这个。但不建议将信号作为进程间通信的常规手段。

线程在Linux中被实现为轻量级的进程,线程之间的同步手段(互斥量和条件等待),本质也是进程间的通信。

进程间通信的手段,大体可以分成以下两类:

  1. 第一类是通信类。这类手段的作用是在进程之间传递消息,交换数据。若细分下来,通信类也可以分为两种,一种是用来传递消息的(比如消息队列),另外一种是通过共享一片内存区域来完成信息交换的(比如共享内存)。
  2. 第二类是同步类。这类手段的目的是协调进程间的操作。某些操作,多个进程不能同时执行,否则可能会产生错误的结果,这就需要同步类的工具来协调。

管道是一个广泛应用的进程间通信手段。日常在终端执行shell命令时,会大量用到管道。但管道的缺陷在于只能在有亲缘关系(有共同的祖先)的进程之间使用。为了突破这个限制,后来引入了命名管道。

管道

1. 管道概述

管道是最早出现的进程间通信的手段。在shell中执行命令,经常会将上一个命令的输出作为下一个命令的输入,由多个命令配合完成一件事情。而这就是通过管道来实现的。

管道的作用就是在有亲缘关系的进程之间传递消息。所谓有亲缘关系,是指有一个共同的祖先。所以管道也并非只能用于父子进程之间,也可以用在兄弟进程之间甚至等等。总而言之,只要共同的祖先曾经调用了pipe函数,打开的管道文件就会在fork之后,被各个后代进程所共享。打开的管道文件,只有家族成员知道和具有使用权利。

管道的特性 ,这个特性是指读取管道内容是消耗型的行为,即一个进程读取了管道内的一些内容之后,这些内容就不会再管道中了。一般来讲管道是单向的。一个进程负责往管道里面写内容,另一个进程读取管道里的内容。如果两个进程之间想双向通信怎么办?可以建立两个管道呗。

管道是一种文件,可以调用read、write和close等操作文件的接口来操作管道。另一方面管道又不是一种普通的文件,它属于一种独特的文件系统:pipefs。管道的本质是内核维护了一块缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区内存的操作。

2. 管道接口

在Linux下可以使用如下的接口创建管道:

#include <unistd.h>
int pipe(int pipefd[2]);

如果成功,则返回值是0,如果失败,则返回值是-1,并且设置errno。

成功调用匹配函数之后,会返回两个打开的文件描述符,一个是管道的读取端描述符pipefd[0],另一个是管道的写入段描述符匹配负担[1]。管道没有文件名与之关联,因此程序没有选择,只能通过文件描述符来访问管道,只有那些能看到这两个文件描述符的进程才能够使用管道。也就是说,只有该进程及该进程的子孙进程才能看到。

从内核的角度看,调用pipe之后,系统给进程分配了两个文件描述符,调用fork之后,子进程也就有了与管道对应的两个文件描述符。和普通文件不同,这两个文件描述符对应的是一块内存缓冲区域。

从中可以看出,任何两个有亲缘关系的进程,只要共同的祖先打开了一个管道,总能通过关闭不相关进程的某些管道文件描述符,来建立起两者之间单向通信的管道。

3. 关闭未使用的管道文件描述符

前面提到过,用管道通信的两个进程,各持有一个管道文件描
述符,不相干的进程应自觉关闭掉这些文件描述符。这么做不仅仅
是为了让数据的流向更加清晰,也不仅仅是为了节省文件描述符,
更重要的原因是:关闭未使用的管道文件描述符对管道的正确使用
影响重大。

4. 管道对应的内存区大小

管道本质是一片内存区域,自然有大小。自从Linux 2.6.11版本起,管道的默认大小是65536字节,可以调用fcntl来获取和修改这个值的大小,代码如下:

获取管道的大小
pipe_capacity = fcntl(fd,?F_GETPIPE_SZ);设置管道的大小
ret = fcntl(fd,?F_SETPIPE_SZ, size);

管道内存区域的大小必须在页面大小(PAGE)和上限值之间,其上限记录在/proc/sys/fs/pipe-max-size里,对于特权用户,还可以修改上限值。

cat /proc/sys/fs/pipe-max-size
1048576

管道的容量可以扩大,自然也可以缩小。缩小管道容量时会遇到一种比较有意思的场景,即当前管道中已存在的内容大于fcntl函数调用中指定的size,此时fcntl函数会返回失败

在使用管道的过程中要意识到:管道有大小,写入须谨慎,不能连续地写入大量的内容,一旦管道满了,写入就会被阻塞;对于读取端,要及时地读取,防止管道被写满,造成写入阻塞。

5. shell管道的实现

shell编程会大量使用管道,我们经常看到前一个命令的标准输出作为后一个命令的标准输入,来协作完成任务。那么管道是如何做到呢?

兄弟进程可以通过管道来传递信息,这并不稀奇。关键是如何使得一个程序的标准输出被重定向到管道中,而另一个程序的标准输入从管道中读取呢?

答案就是复制文件描述符。

对于第一个子进程,执行dup2之后,标准输出对应的文件描述符1,也成为了管道的写入端。这时候,管道就有了两个写入端,需要关闭不相干的写入端,使读取端可以顺利地读取到EOF,所以应将刚开始分配的管道写入端的文件描述符pipefd[1]关闭掉。同样的道理,对于第二个子进程,如法炮制。

6. 与shell命令进行通信(popen)

管道的一个重要作用是和外部命令进行通行。在日常编程中,经常会需要调用一个外部命令,并且需要获取命令的输出。而有些时候,需要给外部命令提供一些内容,让外部命令处理这些输入。Linux提供了popen接口来帮助程序员做这些事情。

简单的说就是通过匹配函数和dup2等函数,也能完成popen

命名管道

无名管道因为没有实体文件与之关联,靠的是世代相传的文件描述符,所以只能应用在有共同祖先的各个进程之间。对于没有亲缘关系的任意两个进程之间无名管道就爱莫能助了。

命名管道就是为了解决无名管道的这个问题而引入的。FIFO与管道类似,最大的差别就是有实体文件与之关联。由于存在实体文件,不相关的没有亲缘关系的进程也可以通过使用FIFO来实现进程之间的通信。

与无名管道相比,命名管道仅仅是批了一件马甲,其核心与无名管道是一模一样的。

1. 创建FIFO文件

创建命名管道的接口定义如下:

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

其中,第二个参数的含义是FIFO文件的读写执行权利,和open函数类似。当然真实的读写执行权限,还需要按照当前进程umask来取掩码

在shell编程中可以使用-p file来判断是否为FIFO文件。在C语言中如何判断是否为FIFO文件呢?通过S_ISFIFO哄可以判断,不过要先通过stat或fstat函数来获取到文件的属性信息,如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
S_ISFIFO(buf->st_mode)

2. 打开FIFO文件

一旦FIFO文件创建好了,就可以把它用于进程间的通信了。一般的文件操作函数如open、read、write、close、unlink等都可以用在FIFO文件上。

FIFO文件和普通文件相比,有一个明显的不同:程序不应该以
O_RDWR模式打开FIFO文件。POSIX标准规定,以O_RDWR模式
打开FIFO文件,结果是未定义的。当然了,Linux提供了对
O_RDWR的支持,在某些场景下,O_RDWR模式的打开是有价值
的(9.4节给出了一个例子)。
对FIFO文件推荐的使用方法是,两个进程一个以只读模式
(O_RDONLY)打开FIFO文件,另一个以只写模式(O_WRONLY)打
开FIFO文件。这样负责写入的进程写入FIFO的内容就可以被负责
读取的进程读到,从而达到通信的目的。
打开一个FIFO文件和打开普通文件相比,又有不同。在没有进
程以写模式(O_RDWR或O_WRONLY)打开FIFO文件的情况下,以
O_RDONLY模式打开一个FIFO文件时,调用进程会陷入阻塞,直
到另一进程以O_WRONY(或者O_RDWR)的标志位打开该FIFO文
件为止。同样的道理,在没有进程以读模式(O_RDONLY或
O_RDWR)打开FIFO文件的情况下,如果一个进程以O_WRONLY
的标志位打开一个FIFO文件,调用进程也会阻塞,直到另一个进程
以O_RDONLY(或者O_RDWR)的标志位打开该FIFO文件为止。也
就是说,打开FIFO文件会同步读取进程和写入进程。
乍看之下,O_RDONLY模式打开不能返回,在等写打开,同样
O_WRONLY打开不能返回,在等读打开,造成死锁,谁都返回不
了。事实上不是这样的。当O_RDONLY打开和O_WRONLY打开的
请求都到达FIFO文件时,两者就都能返回了。
内核之中,维护有引用计数r_counter和w_counter,分别记录
FIFO文件两种打开模式的引用计数。对于FIFO文件,无论是读打开
还是写打开,都会根据引用计数判断对方是否存在,进而决定后续
的行为(是阻塞、返回成功,还是返回失败)。
FIFO文件提供了O_NONBLOCK标志位,该标志位会显著影响
open的行为模式。将O_RDONLY、O_WRONLY及O_NONBLOCK三
种标志位结合在一起考虑

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值