![命名管道:一些实验导致混乱](https://linux22.com/image/61927/%E5%91%BD%E5%90%8D%E7%AE%A1%E9%81%93%EF%BC%9A%E4%B8%80%E4%BA%9B%E5%AE%9E%E9%AA%8C%E5%AF%BC%E8%87%B4%E6%B7%B7%E4%B9%B1.png)
我读过各种文章和问题,但我仍然对我每天使用的东西感到困惑,但从未意识到它有多么令人困惑。我正在 Linux 中试验(命名)管道。
第一名 尝试很简单:弄清楚管道缓冲区是如何工作的:
#1
mkfifo /tmp/mypipe
#2
echo "Hello World" >/tmp/mypipe
ctrl+c
#3
cat /tmp/mypipe
观察:当我在读取数据echo
之前杀死时cat
,没有任何内容写入管道(cat
继续运行,但没有从管道中读取任何内容)。我假设当您键入producent >named_pipe
并且退出时producent
,与管道缓冲区大小匹配的部分数据将被写入named_pipe
并保留在这里,直到被读取consument
(现在我知道这不是它的工作原理)。所以我接下来做的是:
第二名
尝试连接consument
到管道的另一端:
#1
mkfifo /tmp/mypipe
#2
echo "Hello World" >/tmp/mypipe
#3
cat /tmp/mypipe
观察:
cat
命令显示"Hello World"
消息并且两个进程都结束。这里有趣的发现是,在第 2 步中ps -elf
不显示该echo
命令。似乎echo
正在等待有人从管道中读取数据,这就是为什么在我的第一次尝试中没有向管道打印任何内容的原因。
第三名 尝试是管道命令将“永远”运行并不断写入管道,看看会发生什么:
#1
mkfifo /tmp/mypipe
#2
yes >/tmp/mypipe
#3
cat /tmp/mypipe
观察:这按预期工作并cat
打印出yes
转发到管道的内容。不过我尝试用cat
替换tail -f
。当我这样做时,直到命令被终止tail
才打印任何内容。yes
第四名 尝试是最大的谜团:
# 1#
mkfifo /tmp/mypipe
# 2#
for i in $(seq 1 10000); do echo -n $i"|"> /tmp/mypipe; done
# 3#
for i in $(seq 1 10); do echo "${i}# Read:"; cat /tmp/mypipe && echo ""; done
之后 3# 命令开始输入类似的内容:
1# Read:
1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|
2# Read:
109|
3# Read:
110|
4# Read:
111|
5# Read:
112|
6# Read:
113|114|115|
7# Read:
116|
8# Read:
117|
9# Read:
118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255|256|257|258|259|260|261|262|263|264|265|266|267|268|269|270|271|272|273|274|275|276|277|278|279|280|281|282|283|284|285|286|287|288|289|290|291|292|293|294|295|
10# Read:
296|297|298|299|300|301|302|303|304|305|306|307|308|309|310|311|312|313|314|315|316|317|318|319|320|321|322|323|324|325|326|327|328|329|
问题:
第一次和第二次尝试:
- 在这种特殊情况下,命名管道是否等同于
|
bash 中已知的经典管道? - 生产者总是等待消费者吗?如果是,那么管道缓冲区的目的是什么?这种行为是否称为阻塞通信?
Linux 如何知道消费者何时连接到管道以及何时可以进行通信?我已经尝试过,
lsof named_pipe
但它没有给我任何信息,这些信息存储在哪里?我也尝试过以下操作,结果是cat
无法从管道读取。#1 mkfifo /tmp/mypipe #2 echo 1 >/tmp/mypipe #3 rm /tmp/mypipe #4 mkfifo /tmp/mypipe #5 cat /tmp/mypipe
是打字:
producent >/tmp/mypipe
相当于打字command |
我的意思是当有人想要将一个命令通过管道传输到另一个命令但忘记在管道后键入另一个命令(ps
在这种情况下也没有首先显示command
)的情况?
第三次尝试:
- 在这种特殊情况下
cat
和之间有什么区别?tail -f
第四次尝试:
这里发生了什么?为什么读取的数据块大小不准确?我期望输出为:
1# 阅读:1| 2# 阅读:2| 3# 阅读:3|
PS:我还尝试了不同的启动命令顺序(先读后写),但结果是相同的。
PPS:我希望这一点很清楚,但是:生产者=写入管道的进程。消费者=从管道读取数据的进程。
这是否可以向那些主要掌握一点 C 脚本知识的人解释?非常感谢。
编辑回复:乔·休厄尔
- 确定 清除 2.
据我了解,两者是并行运行的,或者换句话说,以下两个是不一样的:
find | less
与
find > /tmp/file && less /tmp/file
我进一步观察发现,当我运行以下命令时,HDD 不工作,似乎它已停止,直到less
命令有足够的数据可以显示
find | less
当我点击shifg+g
(转到文件末尾less
)时,硬盘立即开始工作并开始输出数据。这是否意味着当less
命令有足够的数据来显示时,它会以某种方式告诉find
不要生成更多数据?这就是你所说的同步?写入管道的数据量也与其缓冲区大小相对应吗?我还注意到,在我点击后,它find
的状态(ps aux
-统计列)发生了变化S+ to D+
shift+g
less
S interruptible sleep (waiting for an event to complete)
D uninterruptible sleep (usually IO)
+ is in the foreground process group.
┌─[wakatana@~] [63 files, 178Mb]
└──> ps aux | egrep -w 'less|find'
wakatana 6071 0.0 0.0 12736 1088 pts/5 S+ 23:15 0:00 find
wakatana 6072 0.0 0.0 7940 928 pts/5 S+ 23:15 0:00 less
wakatana 6183 0.0 0.0 7832 892 pts/6 S+ 23:20 0:00 egrep --color=auto -w less|find
┌─[wakatana@~] [63 files, 178Mb]
└──> ps aux | egrep -w 'less|find'
wakatana 6071 0.0 0.0 12808 1304 pts/5 D+ 23:15 0:00 find
wakatana 6072 0.0 0.0 9556 2508 pts/5 S+ 23:15 0:00 less
wakatana 6193 0.0 0.0 7832 892 pts/6 S+ 23:21 0:00 egrep --color=auto -w less|find
谁向生产者发送这个信号?如果是,那么消费者如何知道他已连接到已经有产品的管道(例如我的 rm 管道示例)?
确定 清除
确定 清除
我认为新线路并不是让我感到困惑的情况。根据我之前的观察(并且您确认:“是的,两端互相等待。”)。我期待这个:
I. 第一个循环中的第一次迭代将写入管道,并且因为没有人正在读取,所以它将在这里等待。
二.当发出第二个循环时,将读取第一次迭代中第一个循环写入的数据,这里没有写入任何内容,因此无法读取更多内容。
三.第二个循环将等待第一个循环写入下一个数据,或者(因为顺序无关)第一个循环将等待直到第二个循环读取写入的数据,依此类推。
因此,我期望一次写入对应一次读取。我还在验证循环是否未运行,因此我修改了一些原始命令,以查看即使消费者不会读取内容,是否也会将某些内容打印到 STDOUT,但没有打印任何内容。
for i in $(seq 1 10000); do
if [ $(( $i % 5 )) -eq 0 ]; then
echo $i;
else
echo -n $i"|"> /tmp/mypipe;
fi;
done
“由于写入过程不会发送任何换行符,因此读者只需阅读,直到被告知“足够了”。”
- 谁会告诉消费者他已经够了?
“在第一种情况下,可能是因为 fifo 的缓冲区已满,”
- 如果通信被阻止(如上所述),我该如何填充缓冲区?
“因此被冲到了读者面前。”
- 你这是什么意思?对不起我的英语不好。
“虽然有办法使通信异步......”
- 您能否简要描述一下这种情况下异步和同步的区别是什么?
答案1
要按编号回答您的问题列表:
命名管道,又名 fifo,本质上等同于 shell 生成的无名管道。最大的区别在于,对于 shell 版本来说,两端之间的同步是直观的,而您似乎使用的命名管道需要一些有关 shell 为您做什么的知识。
是的,两端都在等待对方。 fifo 的用途与 shell 管道一样,是将一个进程的输出传递到另一个进程的输入。他们是不是临时文件。我怀疑这就是你感到困惑的地方。对于像这样的 shell 命令
cat somefile.txt | less
,两个都命令作为分叉进程同时运行,管道用于同步这两个进程。如果没记错的话,可以在 C 语言中修改它,但使用 shell 命令就不那么容易了。当管道的另一端获得连接时,进程可以接收信号,但如上所述,整个意图通常是保持两个进程同步。写入器发送一些内容,并且它知道写入操作完成后可以继续。
bash
并且tcsh
不会让你“遗忘”。该命令甚至没有运行。tail -f
必须读取整个流,直到获得 EOF(在本例中为 )stdin
,然后才能显示任何内容。在你的实验中,结局从未出现。cat
另一方面,可以立即开始处理其输入。由于写入过程不会发送任何换行符,因此读者只需阅读,直到被告知“足够了”。在第一种情况下,可能是因为 fifo 的缓冲区已满,因此被刷新到读取器。随后的输出可能类似,并且可能会根据系统时序而有所不同。
让我在这里解决另一个令人困惑的问题。 shell 处理重定向前运行命令。这意味着您不会cat
在进程列表中看到,因为bash
卡住了等待 fifo 的另一端连接前跑步cat
或任何涉及作家的事情。同样,在连接写入器之前它不会执行读取命令。
我认为你在这里最大的误解是命名管道是不是临时文件。两者都不是无名管道。虽然有多种方法可以使通信异步,但看起来您最好在 下创建实际的临时文件/tmp
,除非您做希望两个进程同时运行。
答案2
find | less
当我点击shift+g
(转到文件末尾less
)时,硬盘立即开始工作并开始输出数据。这是否意味着当less
命令有足够的数据来显示时,它会以某种方式告诉find
不要生成更多数据?
不可以。管道适用于read()
s 和write()
s。less
告诉find
没有什么- 但是只要有一个read()
与之关联的进程,拥有该管道的系统内核就不会刷新它。如此find
write()
直到它不能write()
再继续,因为管道缓冲区已满并且内核不接受另一个缓冲区write()
。find
也是如此堵塞编辑。当您跳到管道输入的末端时,less
read()
管道会排出一些 -(全部)- 该管道缓冲区find
现在可以write()
再次使用 - 确实如此。它恢复对磁盘的搜索,并尝试再次填充该缓冲区或完成其工作。
同样的道理命名管道- 这是完全相同的事情 - 除了内核同意将管道的两端与伪文件关联 - a关联- 文件系统中的某处。当你:
mkfifo pipe
echo > pipe &
rm pipe
mkfifo pipe
cat pipe
...您正在与两个不同的管道。第一个与unlink()
ed by的文件系统名称相关联rm
- 但echo
仍在其管道上等待read()
er - 尽管现在任何进程都不太可能找到另一端。事实上,您基本上可以通过这种方式使命名管道匿名,同时仍然使用它 - 出于安全原因,这通常是最佳实践。
尝试:
mkfifo pipe
exec 3<>pipe 4<>pipe
cat <&3 >out.log &
rm pipe
echo read this, cat\! >&4