将 fifo 用于守护进程

将 fifo 用于守护进程

在学习 Richard Stevens 的《Unix 网络编程》一书时,我遇到了以下几行内容,其中谈到在客户端和服务器之间使用 FIFO。

客户端进程启动,它们打开 FIFO 进行写入,写入其请求,然后退出。发生的情况是每次客户端进程终止时读取都会向守护进程返回零。然后,守护进程必须再次打开 FIFO(只读),并在此等待,直到客户端进程打开它进行写入。

我不明白最后一行。为什么服务器进程必须再次打开 FIFO,它只需要在客户端进程写入后再次读取,对吧?

答案1

为什么服务器进程必须再次打开 FIFO,它只需要在客户端进程写入后再次读取,对吗?

有趣的是,让我们尝试一下你的建议。以下结果是在 Linux 4.9.0-6-amd64(Ubuntu Linux 内核)上生成的。

$ mkfifo t
$ (cat; cat) < t &    # run a "server" as a background job
[1] 4856
$ echo 1 > t
1
[1]+  Done                    ( cat; cat ) < t

它没有按我们想要的方式工作。第一个cat按预期读取 EOF,然后退出。问题是第二个cat 立即读取 EOF,这样我们的“服务器”就完成了。这是不可能的等待对于新客户端(无需重复调用 read() 并浪费 CPU 时间)。

如果您知道如何在 shell 中操作文件描述符 (FD),我们可以通过另一种方式来帮助确认。

$ echo 1 > t &
$ exec 3 < t    # open "t" for reading, as FD 3.
$ cat <&3
1
[1]+  Done                    echo 1 > t
$ cat <&3
$

$ echo 2 > t &
[1] 5102
$ cat <&3
2
[1]+  Done                    echo 2 > t
$ cat <&3
$ 

答案是,重新open()设置 fifo 的目的是阻止其他人打开它进行写入。如果没有该步骤,所有后续read()对 fifo 的调用都将立即返回 0 (EOF)。


当我注意到这一点时,我想知道如何systemd-initctl运作。该程序模拟 systemd 下的旧/dev/initctlfifo。 (免责声明:测试这一点并不容易;我不会费心记录如何进行)。答案是 systemd-initctl 打开 fifo 以进行读取和写入。 (从技术上讲,是 systemd 根据 systemd-initctl.socket 指定打开 fifo,并将其传递给 systemd-initctl)。打开 fifo 进行同时读写是 Linux 特有的功能。但通过这样做,systemd 正在实现与 Stevens 接下来提到的相同的技巧:

为了避免这种情况,一个有用的技术是守护进程打开 FIFO 两次 - 一次用于读取,一次用于写入。返回用于读取的文件描述符用于读取客户端请求,而用于写入的文件描述符从不使用。通过让 FIFO 始终打开以进行写入(只要守护进程存在),读取不会返回 EOF,而是等待下一个客户端请求。

相关内容