2013年8月19日 Randal L. Schwartz 投稿 this Linuxで「[]]スクリプトのインスタンスが1つだけ実行されていることを確認するためのシェルスクリプト競合状態がない、またはロックファイルをクリーンアップする必要がない」:
#!/bin/sh
# randal_l_schwartz_001.sh
(
if ! flock -n -x 0
then
echo "$$ cannot get flock"
exit 0
fi
echo "$$ start"
sleep 10 # for testing. put the real task here
echo "$$ end"
) < $0
宣伝どおりに動作するようです:
$ ./randal_l_schwartz_001.sh & ./randal_l_schwartz_001.sh
[1] 11863
11863 start
11864 cannot get flock
$ 11863 end
[1]+ Done ./randal_l_schwartz_001.sh
$
これが私が理解していることです:
<
から)をサブシェルのSTDIN(つまり、ファイル記述子$0
)にリダイレクト(0
)します。flock -n -x
。の非ブロッキングの排他ロック(0
)を取得しようとします。ここに私の質問があります:
0
に排他ロックを保持すると、別のシェルで実行されている同じスクリプトのコピーがファイル記述子0
に排他ロックを取得できないのはなぜですか?シェルには、標準のファイル記述子(0
、1
、および2
、つまりSTDIN、STDOUT、およびSTDERR)の独自の個別のコピーはありませんか?なぜスクリプトは、サブシェルによって継承されたファイル記述子に、たとえば他のファイルのコンテンツではなく、独自のコンテンツのコピーをリダイレクトする必要があるのですか?
スクリプトのすべてのコピーが同じファイルを使用している限り、任意のファイルを使用できます。 _$0
_を使用すると、ロックがスクリプト自体に関連付けられます。スクリプトをコピーして他の用途に変更する場合は、ロックファイルに新しい名前を付ける必要はありません。これは便利です。
スクリプトがシンボリックリンクを介して呼び出される場合、ロックはリンクではなく実際のファイルに対して行われます。
(もちろん、一部のプロセスがスクリプトを実行し、実際のパスの代わりにゼロ番目の引数として構成された値を与えると、これは壊れます。しかし、それはめったに行われません。)
(別のファイルを使用して上記のように再実行しましたが、実行順序が変更されました)
ランダムなバリエーションだけでなく、使用されたファイルが原因でしたか?パイプラインと同様に、_cmd1 & cmd
_でコマンドが実行される順序を確認する方法はありません。それは主にOSのスケジューラ次第です。システムでランダムな変動が発生します。
とにかく、スクリプトは、サブシェルによって継承されたファイル記述子に、ファイルの内容のコピーをリダイレクトする必要があるのはなぜですか。
シェル自体が、ロックを保持しているflock
ユーティリティだけでなく、ロックを保持しているファイルの説明のコピーを保持しているようです。 flock(2)
で作成されたロックは、それを持っているファイル記述子が閉じられるときに解放されます。
flock
には2つのモードがあり、ファイル名に基づいてロックを取得し、外部コマンドを実行します(この場合、flock
は必要なオープンファイル記述子を保持します)、またはファイル記述子を取得します。外部からですので、外部プロセスがそれを保持する責任があります。
ファイルの内容はここでは関係なく、コピーは作成されないことに注意してください。サブシェルへのリダイレクトは、それ自体でデータをコピーするのではなく、ファイルへのハンドルを開くだけです。
1つのシェルでファイル記述子0の排他ロックを保持すると、別のシェルで実行されている同じスクリプトのコピーがファイル記述子0の排他ロックを取得できないのはなぜですか?シェルには、標準のファイル記述子(0、1、2、つまりSTDIN、STDOUT、およびSTDERR)の独自の個別のコピーがないのですか?
はい、しかしロックはファイル記述子ではなくfileにあります。一度にロックを保持できるのは、開いているファイルのインスタンス1つだけです。
exec
を使用してロックファイルへのハンドルを開くことにより、サブシェルなしでも同じことができるはずです。
_$ cat lock.sh
#!/bin/sh
exec 9< "$0"
if ! flock -n -x 9; then
echo "$$/$1 cannot get flock"
exit 0
fi
echo "$$/$1 got the lock"
sleep 2
echo "$$/$1 exit"
$ ./lock.sh bg & ./lock.sh fg ; wait; echo
[1] 11362
11363/fg got the lock
11362/bg cannot get flock
11363/fg exit
[1]+ Done ./lock.sh bg
_
ファイルの説明を介して、ファイルにファイルロックが添付されます。高レベルでは、スクリプトの1つのインスタンスでの操作のシーケンスは次のとおりです。
ロックを保持することで、ロックが実行するので、同じスクリプトの別のコピーが実行されなくなります。ファイルの排他ロックがシステムのどこかに存在する限り、別のファイル記述を使用しても、同じロックの2番目のインスタンスを作成することはできません。
ファイルを開くと、ファイルの説明が作成されます。これは、プログラミングインターフェイスで直接の可視性があまりないカーネルオブジェクトです。ファイル記述子にはファイル記述子を介して間接的にアクセスしますが、通常はファイルへのアクセス(コンテンツまたはメタデータの読み取りまたは書き込み)と見なします。ロックは、ファイルや記述子ではなく、ファイルの説明に対するプロパティである属性の1つです。
最初に、ファイルが開かれると、ファイルの説明には単一のファイル記述子が含まれますが、別の記述子(dup
ファミリーのシステムコール)を作成するか、サブプロセスをフォークすることで、記述子をさらに作成できます(後親と子の両方が同じファイル記述にアクセスできます)。ファイル記述子は、明示的に、またはそれが含まれているプロセスが終了したときに閉じることができます。ファイルに添付された最後のファイル記述子が閉じられると、ファイルの説明も閉じられます。
上記の一連の操作がファイルの説明にどのように影響するかを次に示します。
<$0
は、サブシェルでスクリプトファイルを開き、ファイルの説明を作成します。この時点で、説明に添付された単一のファイル記述子があります。サブシェルの記述子番号0です。flock
を呼び出し、終了するのを待ちます。 flockの実行中、説明に2つの記述子がアタッチされています。サブシェルの番号0とflockプロセスの番号0です。 flockがロックを取得すると、ファイルの説明のプロパティが設定されます。別のファイル記述がすでにファイルにロックを持っている場合、flockは排他ロックであるため、ロックを取得できません。スクリプトが$0
からのリダイレクトを使用する理由は、リダイレクトがシェルでファイルを開く唯一の方法であり、リダイレクトをアクティブに保つことがファイル記述子を開いておく唯一の方法だからです。サブシェルは標準入力から読み取ることはなく、開いたままにしておく必要があります。オープンコールとクローズコールに直接アクセスできる言語では、
fd = open($0)
flock(fd, LOCK_EX)
do stuff
close(fd)
組み込みのexec
を使用してリダイレクトを行うと、シェルで同じ操作シーケンスを実際に取得できます。
exec <$0
flock -n -x 0
# do stuff
exec <&-
元の標準入力にアクセスし続けたい場合、スクリプトは別のファイル記述子を使用できます。
exec 3<$0
flock -n -x 0
# do stuff
exec 3<&-
またはサブシェルで:
(
flock -n -x 3
# do stuff
) 3<$0
ロックは、スクリプトファイルにある必要はありません。これは、読み取り用に開くことができる任意のファイル上にある可能性があります(そのため、存在する必要があり、通常のファイルや名前付きパイプなどの読み取り可能なファイルタイプである必要がありますが、ディレクトリではありません。スクリプトプロセスには、それを読む許可)。スクリプトファイルには、存在して読み取り可能であることが保証されているという利点があります(スクリプトが呼び出されてからスクリプトが<$0
リダイレクトに到達するまでの間に外部で削除されたEdgeの場合を除く)。
flock
が成功し、スクリプトがロックにバグのないファイルシステム上にある限り(NFSなどの一部のネットワークファイルシステムにはバグがある可能性があります)、別のロックファイルを使用すると、競合状態。私はあなたの側の操作エラーを疑います。
ロックに使用されるファイルは重要ではなく、スクリプトは$0
存在することがわかっているファイルだからです。
ロックが取得される順序は、マシンが2つのタスクを開始できる速度に応じて、多少ランダムになります。
必ずしも0でなくても、任意のファイル記述子を使用できます。ロックは、記述子自体ではなく、ファイル記述子に対して開かれたfileに対して保持されます。
( flock -x 9 || exit 1
echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &