关于 FIFO 和文件描述符的问题

sol*_*wzs 5 bash fifo

我写了一个这样的脚本:

N=5
FIFO=/tmp/$$.fifo
mkfifo $FIFO

loop(){
    for i in $(seq 1 $N); do
        read tmp < $FIFO
        echo "$i out"
    done
}

loop &
LOOP_PID=$!
for i in $(seq 1 $N); do
    echo $i > $FIFO
    echo "$i in"
done
wait $LOOP_PID
Run Code Online (Sandbox Code Playgroud)

当我运行脚本时,它停止wait $LOOP_PID并且无法继续。

所以我用文件描述符修改脚本:

N=5
FIFO=/tmp/$$.fifo
mkfifo $FIFO
exec 3<>$FIFO

loop(){
    for i in $(seq 1 $N); do
        read -u3 tmp
        echo "$i out"
    done
}

loop &
LOOP_PID=$!
for i in $(seq 1 $N); do
    echo $i >&3
    echo "$i in"
done
wait $LOOP_PID
Run Code Online (Sandbox Code Playgroud)

还可以。

当我直接使用 FIFO 时,它无法从 FIFO 中连续读取数据并且会卡死。当我使用文件描述符时,没问题。是什么原因?

Joh*_*024 4

替换这个:

loop(){
    for i in $(seq 1 $N); do
        read tmp < $FIFO
        echo "$i out"
    done
}
Run Code Online (Sandbox Code Playgroud)

有了这个:

loop(){
    for i in $(seq 1 $N); do
        read tmp
        echo "$i out"
    done < $FIFO
}
Run Code Online (Sandbox Code Playgroud)

这使 fifo 保持打开状态,而不是在每个循环中重新打开和重新关闭它。

FIFO 非常棘手:

  1. 除非另一个进程准备好从 FIFO 读取,否则写入 FIFO 的尝试将被阻塞。

  2. 如果从 FIFO 读取的进程关闭了 FIFO,则所有未读信息都会丢失。

这意味着上面的脚本的行为方式可能取决于时间的意外。执行时read tmp < $FIFO,有多少行写入了 FIFO? read将只读取第一个,当 FIFO 关闭时,其余的将被丢弃。

该声明有何exec帮助

让我们比较一下两个脚本。第一种直接使用 FIFO:

#!/bin/sh
fifo=/tmp/$$.myfifo
mkfifo "$fifo"
echo $'1\n2\n3\n4'>"$fifo"

for i in {1..4}
do
    read  tmp
    echo $tmp
done <"$fifo"
Run Code Online (Sandbox Code Playgroud)

上面的脚本将在echo等待进程开始读取 FIFO 的第一个过程中挂起。因为它挂在那里,所以read tmp永远不会到达该语句,并且该脚本不会产生任何输出。

第二个用于exec创建文件句柄:

#!/bin/sh
fifo=/tmp/$$.myfifo
mkfifo "$fifo"
exec 3<>"$fifo"
echo $'1\n2\n3\n4'>&3

for i in {1..4}
do
    read -u3 tmp
    echo $tmp
done
Run Code Online (Sandbox Code Playgroud)

该脚本不会挂起,并将产生四行输出。不同之处在于 shell 在文件句柄上提供缓冲。因此,当第一个echo语句尝试写入 FIFO 时,shell 已准备好从 FIFO 读取。shell 读取的数据可用于该read -u3 tmp语句。