このスクリプトは、独自のインスタンスが1つだけ実行されることをどのように保証しますか?

このスクリプトは、独自のインスタンスが1つだけ実行されることをどのように保証しますか?

2013年8月19日ランダルシュワルツ解放するこれ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
$

これが私の理解です:

  • スクリプトは、独自<の内容のコピーをサブシェルの$0STDIN(ファイル記述子など)にリダイレクトします()。0
  • flock -n -xサブシェル内では、スクリプトはファイル記述子の非ブロック排他ロック()を取得しようとします0
    • この試みが失敗すると、サブシェルは終了します(他に何もしないので、デフォルトのスクリプトも同じです)。
    • 試行が成功すると、サブシェルが必要なタスクを実行します。

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

  • スクリプトが次のコピーをサブシェルの継承されたファイル記述子にリダイレクトする必要があるのはなぜですか。それ自体のコンテンツ他のファイルの内容ではない? (上記のように他のファイルからリダイレクトして再実行してみましたが、実行順序が変更されました。非バックグラウンドジョブがバックグラウンドジョブよりも先にロックされます。したがって、ファイル自体の内容を使用すると競合状態を回避できます。しかし、どうなりますか?)
  • とにかく、スクリプトがファイルの内容のコピーをサブシェルの継承されたファイル記述子にリダイレクトする必要があるのはなぜですか?
  • あるシェルのファイル記述子の排他的ロックを維持すると、他のシェルで実行されているのと0同じスクリプトのコピーがファイル記述子の排他的ロックを取得できないのはなぜですか0?シェルには、標準のファイル記述子(0、、、1および2STDIN、STDOUT、およびSTDERR)の独自の独立したコピーはありませんか?

答え1

スクリプトが他のファイルの内容ではなく、独自の内容のコピーをサブシェルが継承したファイル記述子にリダイレクトする必要があるのはなぜですか。

スクリプトのすべてのコピーが同じファイルを使用する限り、すべてのファイルを使用できます。 just を使用すると、$0ロックはスクリプト自体にバインドされます。スクリプトをコピーして他の目的に変更する場合は、ロックファイルの新しい名前を作成する必要はありません。これはとても便利です。

スクリプトがシンボリックリンクを介して呼び出されると、リンクではなく物理ファイルにロックが設定されます。

(もちろん、一部のプロセスがスクリプトを実行し、実際のパスではなく0番目の引数で構成された値を指定すると、中断されますが、これはほとんど発生しません。)

(上記のように別のファイルを使ってやり直してみると、実行順序が変わりました)

これはランダムな変更ではなく、使用されたファイルが原因であると確信していますか?パイプと同様に、実際にコマンドがどこにあるかを確認する方法はありませんcmd1 & cmd。これは主にオペレーティングシステムのスケジューラによって異なります。私のシステムにランダムな変更が発生しています。

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

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

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

この文書の内容はここに関連しておらず、コピーは作成されませんでした。サブシェル自体へのリダイレクトは、データをコピーせずにファイルへのハンドルのみを開きます。

あるシェルでファイル記述子 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

答え2

ファイルロック機能を提供到着ファイル経由ファイル説明。要約すると、スクリプトインスタンスの作業順序は次のとおりです。

  1. ロックされているファイル(「ロックファイル」)を開きます。
  2. ロックファイルをロックします。
  3. 働くこと。
  4. ロックファイルを閉じます。これにより、ファイルを開いて生成されたファイル記述に関連付けられたロックが解除されます。

ロックを維持すると、同じスクリプトの他のコピーが実行されるのを防ぐことができます。ロックが実行するアクションだからです。システム内のファイルに排他的ロックが存在する限り、異なるファイル記述を介しても同じロックの2番目のインスタンスを作成することは不可能です。

ファイルを開くと、次のようになります。ファイル説明。これはカーネルオブジェクトであり、プログラミングインターフェイスに直接的な可視性を持っていません。ファイル記述子を介して間接的にファイル記述にアクセスしますが、通常はファイルにアクセスすること(該当する内容やメタデータを読み書きすること)と思います。ロックは、ファイルまたは記述子ではなく、ファイル記述の属性の 1 つです。

最初にファイルを開くと、ファイル記述には単一のファイル記述子がありますが、別の記述子dup(一連のシステム呼び出し)を作成するか、子プロセス(次に親および子プロセス)を分岐してより多くの記述子を作成できます。子供も同じファイルの説明にアクセスできます。ファイル記述子は明示的に閉じられるか、ファイル記述子を持つプロセスが終了したときに閉じることができます。ファイルに添付された最後のファイル記述子が閉じられると、ファイル記述も閉じられます。

上記の作業順序がファイル記述にどのような影響を与えるかは次のとおりです。

  1. リダイレクトは<$0サブシェルでスクリプトファイルを開き、ファイル記述を作成します。この時点で、ファイル記述子が説明に追加されます。サブシェルの記述子番号0。
  2. サブシェルを呼び出しflock、終了するまで待ちます。群れが実行されると、説明に2つの記述子が追加されます。つまり、サブシェルの数字 0 と無理プロセスの数字 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などの一部のネットワークファイルシステムにはバグがある可能性があります)、他のロックファイルを使用すると競合状態がどのように許可されるかがわかりません。私はあなたが間違いを犯したと思います。

答え3

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

ロックが取得される順序は、コンピュータが2つのタスクをどれだけ早く開始するかに応じて、ややランダムです。

必ずしもゼロである必要はありませんが、すべてのファイル記述子を使用できます。ロック保持文書記述子自体ではなくファイル記述子を開きます。

( flock -x 9 || exit 1
  echo 'Locking for 5 secs'; sleep 5; echo 'Done' ) 9>/tmp/lock &

関連情報