引入

上一篇文章介绍了 Linux 中通过pipe创建匿名管道,并实现父子进程间通信的功能;当时我就提到了 Linux 中的另一种管道通信方式——命名管道,下面就来详细介绍一下;

命名管道

什么是命名管道

命名管道(Named Pipe),也叫FIFO(First In First Out),是一种用于进程间通信(IPC)的机制。与匿名管道不同,命名管道是存在于文件系统中的特殊文件,具有持久性,可以用于不相关的进程间的数据传递;

命名管道的特点:

  • 持久性:命名管道存在于文件系统中,一旦创建,即使创建管道的进程结束,管道仍然存在,直到手动删除。
  • 半双工通信:在一个时刻数据只能单向传输,发送和接收通常需要在两个不同的进程中操作。
  • 阻塞行为:当读取端没有准备好时,写入命名管道的操作会被阻塞,反之亦然,除非使用非阻塞方式。
  • 文件系统挂载点:命名管道是通过文件系统的路径标识的,可以像普通文件一样打开、读写,但它不存储数据,只是用来传递数据。
  • 跨进程通信:命名管道支持同一主机上的不同进程之间的通信,甚至可以在父子进程之外的进程中通信。 这篇文章会详细介绍命名管道作为文件进行跨进程通信的功能;

基本操作

创建

Linux 系统中,可以使用mkfifo命令创建命名管道:

mkfifo my_pipe
# 创建一个命名管道

使用(文件属性)

之前说过,命名管道具有文件属性,所以它适用于文件的相关操作,比如:

  1. 写入进程:
echo "hello world" > my_pipe
# 向创建的命名管道中写入数据
  1. 读取进程
cat < my_pipe
# 使用 cat 读取文件内容

在这种情况下,echo 命令会把数据写入管道,而 cat 命令会从管道中读取数据。由于管道是 FIFO 的,所以数据按写入顺序被读取。

代码演示

框架搭建

下面我们搭建一个简单的模型,用于演示在命名管道下进程间的通信:

  1. 首先我们需要了解命名管道可以用于没有相关性的进程之间的通信,所以我们创建两个可执行文件,用于模拟两个不同进程;
  2. 随后调用系统函数writeopenread等对信息写入、读取等工作;
  3. 对于mkfifo函数,可以查到它的接口:
int mkfifo(const char* pathname, mode_t mode);
// pathname,即路径名称,用于指定管道文件的创建位置
// mode,模式选项,这里指文件的权限信息

所以我们需要一个路径名称和权限选项(八进制); 综合上述说明,我们就得到了一个简易的程序框架,下面就是代码实现;

代码实现

这个代码实现和上一篇文章的匿名管道类似,这里不再贴出代码,具体代码细节可以访问我的GitHub主页,位于pipe文件夹下;

匿名和命名管道之间的区别

最后我们再来总结一下这两者之间的区别,便于大家更好的理解管道的内容:

  1. 命名方式
  • 匿名管道:没有名字,存在于内存中,由创建它的进程通过文件描述符来引用。匿名管道只能用于相关的进程之间(例如,父子进程)。
  • 命名管道:有名字,存在于文件系统中。不同的进程可以通过路径名来引用它,可以用于不相关的进程之间的通信。
  1. 创建方式
  • 匿名管道:在 C 语言中,使用 pipe() 系统调用创建匿名管道,它会返回两个文件描述符,一个用于读,一个用于写。例如:
int pipefd[2];
pipe(pipefd); // 创建匿名管道
  • 命名管道:在 C 语言中,可以通过 mkfifo() 系统调用创建命名管道,它会在文件系统中创建一个特殊的 FIFO 文件。例如:
mkfifo("/tmp/mypipe", 0666); // 创建命名管道
  1. 通信进程的关系
  • 匿名管道:仅限于相关进程之间的通信,如父进程和子进程,因为它们继承了同样的文件描述符。匿名管道不适合不相关的进程之间进行通信。
  • 命名管道:可以用于不相关进程之间的通信,因为它通过文件系统路径命名,任何有权限的进程都可以通过路径名打开这个管道进行通信。
  1. 生命周期
  • 匿名管道:管道的生命周期与进程相同,一旦创建管道的进程退出,管道也会销毁。
  • 命名管道:命名管道的生命周期与文件系统中的文件类似,管道文件在手动删除之前会一直存在,即使创建它的进程已经退出。
  1. 可见性
  • 匿名管道:对用户和文件系统不可见,完全存在于内存中。
  • 命名管道:在文件系统中可见,可以通过命令 ls 等查看,也可以通过路径进行操作。
  1. 典型使用场景
  • 匿名管道:用于父子进程或兄弟进程间的简单通信,通常是在进程创建时立即建立通信通道。常用于短期通信,通常不会持久存在。
  • 命名管道:用于需要在不相关进程间进行通信的场景,尤其是当进程可能独立运行,彼此需要通过一个预定义的管道进行通信时。
  1. 跨用户或网络通信
  • 匿名管道:仅限于在同一台计算机上的进程通信,且必须是直接相关的进程(父子或兄弟进程)。
  • 命名管道:尽管主要用于本地进程间通信,但可以通过网络文件系统(NFS)等实现跨网络的间接通信,但并不常用于此类场景。

区别总结表:

特性 匿名管道 命名管道
命名方式 无名称,通过文件描述符标识 有名称,通过文件系统路径标识
创建方式 pipe() 系统调用 mkfifo() 系统调用
使用进程 父子进程、兄弟进程 不相关的进程都可以使用
通信范围 仅限相关进程 不相关的进程可通过文件路径通信
生命周期 与创建进程相同,进程结束即销毁 在文件系统中持续存在,直到手动删除
可见性 不可见,完全在内存中 可见,在文件系统中
典型场景 父子进程间的简单通信 不相关进程间的长时间通信