web-dev-qa-db-ja.com

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

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)を取得しようとします。
    • その試みが失敗した場合、サブシェルは終了します(他に何もすることがないため、メインスクリプトも終了します)。
    • 代わりに試行が成功した場合、サブシェルは目的のタスクを実行します。

ここに私の質問があります:

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

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

スクリプトのすべてのコピーが同じファイルを使用している限り、任意のファイルを使用できます。 _$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
_
22
ilkkachu

ファイルの説明を介して、ファイルにファイルロックが添付されます。高レベルでは、スクリプトの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リダイレクトに到達するまでの間に外部で削除されたEdgeの場合を除く)。

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

ロックに使用されるファイルは重要ではなく、スクリプトは$0存在することがわかっているファイルだからです。

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

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

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