このスクリプトは、それ自体の1つのインスタンスのみが実行されていることをどのように確認しますか?


22

2013年8月19日に、Randal L. Schwartzこのシェルスクリプトを投稿しました。これは、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
$

ここに私が理解していることがあります:

  • スクリプト<は、自身のコンテンツのコピー(から$0)を0サブシェルのSTDIN(ファイル記述子)にリダイレクト()します。
  • サブシェル内で、スクリプトはflock -n -xファイル記述子で非ブロッキングの排他ロック()を取得しようとします0
    • その試みが失敗した場合、サブシェルは終了します(他に何もすることがないため、メインスクリプトも終了します)。
    • 代わりに試行が成功した場合、サブシェルは目的のタスクを実行します。

私の質問は次のとおりです。

  • スクリプトがサブシェルによって継承されたファイル記述子に、たとえば他のファイルのコンテンツではなく、独自のコンテンツのコピーをリダイレクトする必要があるのはなぜですか?(別のファイルからリダイレクトして、上記のように再実行しようとしましたが、実行順序が変更されました。バックグラウンドタスクの前に非バックグラウンドタスクがロックを取得しました。
  • とにかく、スクリプトが、サブシェルによって継承されたファイル記述子に、ファイルの内容のコピーをリダイレクトする必要があるのはなぜですか?
  • 0あるシェルでファイル記述子の排他ロックを保持すると、別のシェルで実行されている同じスクリプトのコピーがファイル記述子の排他ロックを取得できなくなるのは0なぜですか?シェルは、(標準のファイルディスクリプタの独自の、独立したコピーを持っていない012、すなわちSTDIN、STDOUT、およびSTDERR)?

別のファイルからリダイレクトする実験を試みたときの正確なテストプロセスは何でしたか?
フライハイト

1
このリンクを参照できると思います。stackoverflow.com/questions/185451/...
デブPaikar

回答:


22

スクリプトがサブシェルによって継承されたファイル記述子に、たとえば他のファイルのコンテンツではなく、独自のコンテンツのコピーをリダイレクトする必要があるのはなぜですか?

スクリプトのすべてのコピーが同じものを使用する限り、任意のファイルを使用できます。を使用$0すると、ロック自体がスクリプト自体に結び付けられます。スクリプトをコピーして他の用途に変更する場合、ロックファイルの新しい名前を考える必要はありません。これは便利です。

スクリプトがシンボリックリンクを介して呼び出された場合、ロックはリンクではなく実際のファイルにあります。

(もちろん、何らかのプロセスがスクリプトを実行し、実際のパスの代わりにゼロ番目の引数として構成された値を与えると、これは壊れます。しかし、それはめったに行われません。)

(私は別のファイルを使用して上記のように再実行しようとしましたが、実行順序が変更されました)

これはランダムなバリエーションではなく、使用されたファイルによるものであると確信していますか?パイプラインの場合と同様に、コマンドを実行する順序を確認する方法はありませんcmd1 & cmd。それは主にOSスケジューラー次第です。システムにランダムな変動があります。

とにかく、スクリプトが、サブシェルによって継承されたファイル記述子に、ファイルの内容のコピーをリダイレクトする必要があるのはなぜですか?

シェル自体が、ロックをflock保持しているユーティリティだけでなく、ロックを保持しているファイル記述のコピーを保持しているように見えます。で作成されたロックは、flock(2)それを持つファイル記述子が閉じられると解放されます。

flockファイル名に基づいてロックを取得し、外部コマンドを実行する(この場合flock、必要なオープンファイル記述子を保持する)、または外部からファイル記述子を取得するための2つのモードがあります。それ。

ファイルの内容はここでは関連せず、コピーは作成されないことに注意してください。サブシェルへのリダイレクトは、それ自体のデータをコピーしません。ファイルへのハンドルを開くだけです。

あるシェルでファイル記述子0の排他ロックを保持すると、別のシェルで実行されている同じスクリプトのコピーがファイル記述子0の排他ロックを取得できなくなるのはなぜですか?シェルには、標準ファイル記述子の独自の別個のコピー(0、1、2、つまりSTDIN、STDOUT、およびSTDERR)がありませんか?

はい。ただし、ロックはファイル記述子ではなくファイルにあります。一度にロックを保持できるファイルのインスタンスは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
{ }代わりに( )を使用しても機能し、サブシェルを回避します。
R ..

G +投稿のコメントのさらに下で、そこの誰かがを使用してほぼ同じ方法を提案しましたexec
デビッドZ

@R ..、ああ、確かに。しかし、実際のスクリプトの周りに余分な波括弧が付いているため、まだugいです。
イルカチュウ

9

ファイルロックは、ファイル記述を介してファイルに付加さます。高レベルでは、スクリプトの1つのインスタンスでの操作のシーケンスは次のとおりです。

  1. ロックが添付されているファイル(「ロックファイル」)を開きます。
  2. ロックファイルをロックします。
  3. 物事を行います。
  4. ロックファイルを閉じます。これにより、ファイルを開いて作成されたファイルの説明に関連付けられているロックが解除されます。

ロックを保持することで、同じスクリプトの別のコピーが実行されるのを防ぐことができます。これがロックの役割だからです。ファイルの排他ロックがシステム上のどこかに存在する限り、異なるファイル記述を使用しても、同じロックの2番目のインスタンスを作成することはできません。

ファイルを開くと、ファイルの説明が作成されます。これは、プログラミングインターフェイスで直接的な可視性をあまり持たないカーネルオブジェクトです。ファイル記述子を介して間接的にファイルの説明にアクセスしますが、通常はファイルにアクセスする(コンテンツまたはメタデータの読み取りまたは書き込み)と考えます。ロックは、ファイルや記述子ではなくファイルの説明に対するプロパティである属性の1つです。

最初に、ファイルが開かれると、ファイル記述には単一のファイル記述子がありますが、別の記述子(dupシステムコールのファミリー)を作成するか、サブプロセスをフォークすることで(その後、親と子は同じファイル記述にアクセスできます)。ファイル記述子は、明示的に閉じることも、プロセスが停止したときに閉じることもできます。ファイルに添付された最後のファイル記述子が閉じられると、ファイルの説明が閉じられます。

上記の一連の操作がファイルの説明にどのように影響するかを次に示します。

  1. リダイレクト<$0により、サブシェルでスクリプトファイルが開き、ファイルの説明が作成されます。この時点で、説明に添付されている単一のファイル記述子があります。サブシェルの記述子番号0です。
  2. サブシェルが起動しflock、終了するのを待ちます。flockの実行中、説明には2つの記述子が付加されます。サブシェルの番号0とflockプロセスの番号0です。flockがロックを取得すると、ファイル記述のプロパティが設定されます。別のファイル記述がすでにファイルにロックを持っている場合、flockは排他ロックであるため、ロックを取得できません。
  3. サブシェルは何かをします。ロック付きの説明に開いているファイル記述子がまだあるため、その説明は既存のままであり、誰もロックを削除しないため、ロックを保持します。
  4. サブシェルは閉じ括弧で終了します。これにより、ロックのあるファイル記述の最後のファイル記述子が閉じられるため、この時点でロックは消えます。

スクリプトがリダイレクトを使用する理由$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リダイレクトされるまでの間に外部から削除されたエッジの場合を除く)。

flock成功し、スクリプトがロックのバグがないファイルシステム上にある限り(NFSなどの一部のネットワークファイルシステムはバグがあるかもしれません)、別のロックファイルを使用すると競合状態がどのように発生するかわかりません。私はあなたの側の操作エラーが疑われる。


競合状態があります:あなたがコントロールすることはできませんどのロックを取得するスクリプトのインスタンス。幸いなことに、ほとんどすべての目的のために、それは重要ではありません。
マーク

4
@Markロックへの競合がありますが、競合状態ではありません。競合状態はタイミングが、このような2つのプロセスが同時に同じクリティカルセクションにあるものとして、何か悪いが起こることを許可することができるときです。どのプロセスがクリティカルセクションに入るかわからないことは非決定性であると予想され、競合状態ではありません。
ジル「SO-悪であるのをやめなさい」

1
参考までに、「ファイルの説明」のリンクは、コンセプトの具体的な説明ではなく、Open Groupの仕様のインデックスページを指します。または、古い回答もこちらにリンクしてください。unix.stackexchange.com/ a / 195164/85039
Sergiy Kolodyazhnyy

5

ロックに使用されるファイルは重要ではありません。スクリプトは$0、それが存在することがわかっているファイルであるため使用します。

ロックが取得される順序は、マシンが2つのタスクを開始できる速度に応じて、多少ランダムになります。

必ずしも0ではない任意のファイル記述子を使用できます。ロックは、記述子自体ではなく、ファイル記述子に対して開かれたファイルに対して保持されます

( flock -x 9 || exit 1
  echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.