为什么“是|睡眠10”管道不会失败

时间:2018-09-22 12:22:00

标签: bash pipe

在考虑如何在自己的程序中实现某个功能时,我一直想知道bash如何在内部处理以下性质的管道:

FXMLLoader

这显然什么也不做,但是我不明白这怎么不会导致错误。我会以为:

  • 因为yes | sleep 10 没有从stdin读取,连接两个进程的管道将填满并导致sleep在尝试写入现在已满的管道时无限期阻塞

  • 如果使用非阻塞IO,则如果首先执行yes并在yes进程运行之前将其写入管道,则不会发生任何错误,因此会发生错误。读取管道末端

我想这是我的主要误解。我已经尝试过查看bash源代码,但这已经超出了我的脑袋。

1 个答案:

答案 0 :(得分:10)

这是运行shell命令yes | sleep 10时实际发生的情况。

首先,shell使用anonymous pipe创建一个pipe system callpipe系统调用将打开两个文件描述符,它们是管道的读取端和写入端。写入到写入端的所有内容都可以从读取端读取。

此后,shell使用fork system call创建两个子进程。两个孩子并行运行。

  • 在一个子进程中,外壳程序将管道的写入端连接到标准输出,并关闭读取端。然后,shell调用execve system call,用yes的代码映像替换此过程中的代码映像。
    程序yes会尽可能长时间地写入管道。如果在管道的读取端没有有效的read调用,则write调用只会阻塞。 (实际上有一个write缓冲区会被阻塞之前填满,但这并不重要。)
  • 在另一个子级中,外壳程序将管道的读取端连接到标准输入,并关闭写入端。然后,shell调用execve系统调用,以用sleep的代码映像替换此过程中的代码映像。 程序sleep在10秒钟内什么也不做。
  • 原始shell进程关闭管道的两端,并等待其两个子进程退出(使用wait system call)。

10秒一结束,运行sleep的进程就会退出。此时,管道的读取端在任何过程中都不再打开。当某个进程试图写入在任何进程中未打开读取端的管道时,内核会向写入进程发送SIGPIPE信号。因此,运行yes的进程被SIGPIPE信号杀死。

这时,shell检测到它在管道两侧的子进程已经退出。 pipeline命令返回右侧的状态,该状态为0(sleep成功退出)。

  

由于无法从stdin中读取睡眠,因此连接两个进程的管道将填满,并在尝试写入当前已满的管道时导致yes无限期阻塞

这是正确的。

  

如果使用非阻塞IO,则首先执行yes并在睡眠进程甚至运行之前就将其写入管道,因此没有进程连接到管道的读取端,则应该发生错误

在某些地方这是不正确的。 yes不使用非阻塞IO。它与sleep并行执行,而不是首先执行。从来没有任何时间点,没有进程连接到管道的读取端,直到sleep退出。根据时间的不同,yes可能会在sleep开始执行之前开始写操作,甚至可能在sleep程序的子进程被派生之前开始,但是当在写端打开的同时,pipe调用返回了。