rm可以用于同步锁定文件检查吗?

yog*_*212 4 linux shell synchronization race-condition

我一直在尝试改进一些外壳代码,以实现破坏的锁定机制。

我的想法是通过在文件上调用rm来仅允许一个调用者完成同步。

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

echo $$ > $PIDFILE
# this should succeed only for one process
rm $flag || exit
echo $$ > $PIDFILE
Run Code Online (Sandbox Code Playgroud)

我已经进行了一些并发呼叫,并为此投入了很多精力,但并没有遇到失败。

但这实际上是安全的吗?

mel*_*ene 5

不安全

假设脚本的三个副本(A,B和C)同时启动,并且/tmp/test.pid最初不存在。

让A和B完成脚本的初始语句:

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi
Run Code Online (Sandbox Code Playgroud)

切换到A并让它运行另外两个语句:

echo $$ > $PIDFILE
rm $flag || exit
Run Code Online (Sandbox Code Playgroud)

这成功了;$PIDFILE现在包含A的PID。

切换到B并让其运行相同的语句。rm失败,因此B退出,但$PIDFILE现在包含B的PID。

切换到C。C才刚刚开始运行,因此它要做的第一件事是重新创建$flag

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag
Run Code Online (Sandbox Code Playgroud)

现在进行PID检查:

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi
Run Code Online (Sandbox Code Playgroud)

这样通过是因为$PIDFILE包含B的PID,但是B不再运行。

现在我们去

echo $$ > $PIDFILE
rm $flag || exit
Run Code Online (Sandbox Code Playgroud)

这也通过了,因为C刚刚重新创建了$flag文件。

现在我们同时运行A和C,相互竞争以$PIDFILE再次覆盖。


除此之外,还有一个“误报”问题:

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi
Run Code Online (Sandbox Code Playgroud)

您可能已经过时$PIDFILE,但是其中包含的PID已被其他进程重用。在这种情况下,您不会遇到竞争(脚本实例过多),而会拒绝服务(脚本实例过多:0)。您的脚本将看到正在运行的进程,恰好恰好具有“错误的” PID并退出。