1. 概要

ファイルロックは、ファイルを複数のプロセスが安全に読み書きできるようにするための相互排除メカニズムです。

このチュートリアルでは、複数プロセスシステムでの更新の干渉の問題を理解します。 次に、Linuxに2種類のロックを導入します。

その過程で、例を通してファイルロック関連のコマンドをいくつか学びます。

2. 干渉する更新の問題

介入する更新は、並行システムの典型的な競合状態の問題です。 問題をよりよく理解するために例を見てみましょう。

アカウントの残高を保存するbalance.datファイルがあり、初期値が「100」であるとします。 並行システムには、残高値を更新するための2つのプロセスがあります。

  1. プロセスA:現在の値を読み取り、20を引いて、結果をファイルに保存します。
  2. プロセスB:現在の値を読み取り、80を加算して、結果をファイルに書き戻します。

明らかに、2つのプロセスの実行後、ファイルの値は 100-20 + 80=160であると予想されます。

ただし、この状況では、更新の中断の問題が発生する可能性があります。

  1. プロセスAは、ファイルの現在の値( 100 )を読み取り、さらに計算を行う準備をします。
  2. プロセスBは同じファイルを読み取り、現在の残高( 100 )を取得します。
  3. プロセスAは100-20を計算し、結果80をファイルに保存します。
  4. プロセスBは、最後の読み取り以降に残高が更新されたことを認識していません。 したがって、古い値100を使用して100+ 80 を計算し、結果180をファイルに書き込みます。

その結果、balance.datファイルに期待値160。の代わりに180があります。

3. Linuxでのファイルロック

ファイルロックは、複数のプロセス間でファイルへのアクセスを制限するメカニズムです。 特定の時間に1つのプロセスのみがファイルにアクセスできるため、更新の中断の問題を回避できます。

rm -rf/がLinuxでは非常に危険なコマンドであることは誰もが知っています。 root ユーザーとしてコマンドを実行すると、実行中のシステム内のすべてのファイルが削除されます。 それの訳は Linuxは通常、開いているファイルを自動的にロックしません。 ただし、Linuxは、アドバイザリロックと必須ロックの2種類のファイルロックをサポートしています。

両方のロックタイプをすぐに紹介しますが、この記事では主にアドバイザリロックに焦点を当てます。

3.1. アドバイザリーロック

アドバイザリロックは、強制的なロックスキームではありません。 参加しているプロセスが明示的にロックを取得して協力している場合にのみ機能します。それ以外の場合、プロセスがロックをまったく認識していない場合、アドバイザリロックは無視されます。

例は、協調ロック方式をより簡単に理解するのに役立つ場合があります。 前のバランスの例を確認しましょう。

  1. まず、ファイルbalance.datにまだ初期値「100」が含まれていると仮定します。
  2. プロセスAは、 balance.dat ファイルの排他ロックを取得し、ファイルを開いて読み取り、現在の値100を取得します。

アドバイザリロックはオペレーティングシステムまたはファイルシステムによって設定されたものではないことを理解する必要があります。 したがって、プロセスAがファイルをロックした場合でも、プロセスBは、システムコールを介して、ファイルの読み取り、書き込み、または削除を自由に行うことができます。

プロセスBがロックを取得しようとせずにファイル操作を実行する場合、プロセスBはプロセスAと連携していないと言えます。

しかし、ここで、協調プロセスでロックがどのように機能するかを見てみましょう。

  1. プロセスBは、ファイルを読み取る前に balance.dat ファイルのロックを取得しようとします(プロセスAと協力します)。
  2. プロセスAがファイルをロックしたため、プロセスBはプロセスAがロックを解除するのを待つ必要があります。
  3. プロセスAは100-20を計算し、80をファイルに書き戻します。
  4. プロセスAはロックを解除します。
  5. プロセスBはロックを取得してファイルを読み取り、更新された値 80.を取得します。
  6. プロセスBはロジックを開始し、結果 160 80 + 80 )をファイルに書き戻します。
  7. プロセスBはロックを解除して、他の協調プロセスがファイルの読み取りと書き込みを行えるようにします。

この例は、後のセクションでflockコマンドを使用してどのように実装されるかを確認します。

3.2. 必須のロック

必須のファイルロックを検討する前に、「Linuxでの必須ロックの実装は信頼できない」ということを覚えておく必要があります。

アドバイザリロックとは異なり、 必須のロックには、参加しているプロセス間の協力は必要ありません。 ファイルに対して必須ロックがアクティブ化されると、オペレーティングシステムは、他のプロセスがファイルを読み書きできないようにします。

Linuxで必須のファイルロックを有効にするには、次の2つの要件を満たす必要があります。

  1. mand オプション( mount -o mand FILESYSTEM MOUNT_POINT )を使用してファイルシステムをマウントする必要があります。
  2. set-group-ID ビットをオンにし、ロックしようとしているファイル( chmod g + s、gx FILE )のgroup-executeビットをオフにする必要があります。

4. システム内のすべてのロックを検査する

このセクションでは、実行中のシステムで現在取得されているロックを検査する2つの方法を見てみましょう。

4.1. lslocksコマンド

lslocks コマンドは、 util-linux パッケージのメンバーであり、すべてのLinuxディストリビューションで使用できます。 システムで現在保持されているすべてのファイルロックを一覧表示できます。

出力例を見てみましょう。

$ lslocks
COMMAND            PID   TYPE SIZE MODE  M      START        END PATH
lvmetad            298  POSIX   4B WRITE 0          0          0 /run/lvmetad.pid
containerd         665  FLOCK 128K WRITE 0          0          0 /var/lib/docker/...
chromium        184029  POSIX 9.4M WRITE 0 1073741824 1073742335 /home/kent/.config/chromium/Default/History
nextcloud          961  POSIX  32K READ  0        128        128 /home/kent/Nextcloud/._sync_0e131dbf228b.db-shm
dockerd            630  FLOCK  16K WRITE 0          0          0 /var/lib/docker/buildkit/snapshots.db
dropbox         369159  FLOCK  10M WRITE 0          0          0 /home/kent/.dropbox/logs/1/1-4ede-5e20dd8d.tmp
...

上記のリストでは、システム内で現在ロックされているすべてのファイルを確認できます。 また、ロックの種類やロックを保持しているプロセスなど、各ロックの詳細情報を確認できます。

4.2. / proc / locks

/ proc /locksはコマンドではありません。 代わりに、procfs仮想ファイルシステム内のファイルです。 ファイルは現在のすべてのファイルロックを保持します。 lslocks コマンドも、このファイルに依存してリストを生成します。

/ proc / locks の情報を取得するには、「 cat / proc /locks」を実行します。

$ cat /proc/locks
1: FLOCK  ADVISORY  WRITE 369159 08:12:22417368 0 EOF
2: POSIX  ADVISORY  WRITE 321130 00:2e:30761 0 EOF
3: POSIX  ADVISORY  WRITE 184029 08:12:21760394 0 EOF
4: POSIX  ADVISORY  WRITE 184029 08:12:21633968 1073741824 1073742335
5: POSIX  ADVISORY  WRITE 184029 08:12:21760401 0 EOF
6: POSIX  ADVISORY  WRITE 184029 08:12:21891515 0 EOF
7: POSIX  ADVISORY  WRITE 184029 08:12:21633928 0 EOF
...

最初の行を選択して、 / proc /locksファイルシステムでロック情報がどのように編成されているかを理解しましょう。

1:  FLOCK  ADVISORY  WRITE 369159 08:12:22417368  0  EOF
-1- --2--  ---3---   --4-- ---5-- -------6------ -7- -8-
  1. 最初の列はシーケンス番号です。
  2. 2番目のフィールドは、 FLOCK flock システムコールから)または POSIX lockf、fcntl [から)など、使用されるロックのクラスを示します。 X156X]システムコール)。
  3. この列は、ロックのタイプ用です。 ADVISORYまたはMANDATORYの2つの値をとることができます。
  4. 4番目のフィールドは、ロックがWRITEまたはREADロックのどちらであるかを示します。
  5. 次に、ロックを保持しているプロセスのIDを取得します。
  6. このフィールドには、コロンで区切られた値の文字列が含まれ、ロックされたファイルのIDが「 major-device:minor-device:inode」の形式で表示されます。
  7. この列は、最後の列とともに、ロックされているファイルのロックされた領域の開始と終了を示します。 この例の行では、ファイル全体がロックされています。

5. flockコマンドの概要

flock コマンドは、 util-linux パッケージによっても提供されます。このユーティリティを使用すると、シェルスクリプトまたはコマンドラインでアドバイザリファイルロックを管理できます。

基本的な使用構文は次のとおりです。

flock FILE_TO_LOCK COMMAND

次に、flockコマンドを使用したバランス更新の例を示します。

現在の残高値を含むbalance.datテキストファイルに加えて、ファイル内の残高を更新するには、AとBの2つのプロセスが必要です。

まず、両方のプロセスのバランス更新ロジックを処理するための単純なシェルスクリプトupdate_balance.shを作成します。

#!/bin/bash
file="balance.dat"
value=$(cat $file)
echo "Read current balance:$value"

#sleep 10 seconds to simulate business calculation
progress=10
while [[ $progress -lt 101 ]]; do
	echo -n -e "\033[77DCalculating new balance..$progress%"
	sleep 1
	progress=$((10+progress))
done
echo ""

value=$((value+$1))
echo "Write new balance ($value) back to $file." 
echo $value > "$file"
echo "Done."

プロセスAをシミュレートするための単純なシェルスクリプトa.shを作成します。

#!/bin/bash
#-----------------------------------------
# process A: lock the file and subtract 20 
# from the current balance
#-----------------------------------------
flock --verbose balance.dat ./update_balance.sh '-20'

次に、プロセスAを開始してテストします。

$ ./a.sh 
flock: getting lock took 0.000002 seconds
flock: executing ./update_balance.sh
Read current balance:100
Calculating new balance..100%
Write new balance (80) back to balance.dat.
Done.

出力から、 flockコマンドが最初にファイルbalance.datのロックを取得し、次にupdate_balance.shスクリプトがファイルを読み取って更新したことがわかります。 。

実行中に、lslocksコマンドを使用してロック情報を確認できます。

$ lslocks | grep 'balance'
flock      825712  FLOCK   4B WRITE 0      0      0 /tmp/test/balance.dat

出力は、flockコマンドがファイル/tmp/test/balance.dat全体に対してWRITEロックを保持していることを示しています。

5.1. 非協調的プロセスによる群れのデモンストレーション

アドバイザリロックは、参加しているプロセスが協力している場合にのみ機能することを学びました。 残高を100にリセットし、プロセスAのファイルのアドバイザリロックを取得したが、非協調的な方法でプロセスBを開始した場合にどうなるかを確認しましょう。

次に、簡単なシェルスクリプトb_non-cooperative.shを作成しましょう。

#!/bin/bash
#----------------------------------------
# process B: add 80 to the current balance in a
# non-cooperative way
#----------------------------------------
./update_balance.sh '80'

プロセスBが、残高データファイルのロックを取得しようとせずにupdate_balance.shを呼び出していることがわかります。

このシナリオをGIFアニメーションで示しましょう。

プロセスBがプロセスAと協力せずに開始した場合、プロセスAによって取得されたアドバイザリロックは無視されることがわかります。

したがって、balance.datには160ではなく180があります。

5.2. 協調プロセスによる群れのデモンストレーション

最後に、別の協調プロセスB b.sh を作成して、アドバイザリロックがどのように機能するかを見てみましょう。

#!/bin/bash
#----------------------------------------
# process B: add 80 to the current balance
# in a cooperative way
#----------------------------------------
flock --verbose balance.dat ./update_balance.sh '80'

繰り返しになりますが、デモンストレーションをGIFアニメーションで示します。

デモでは、協力するために2つのプロセスを作成しました。

プロセスBがbalance.datファイルのロックを取得しようとすると、プロセスAがロックを解放するのを待機していることに気付きました。 したがって、アドバイザリロックが機能し、バランスデータファイルで期待される結果160が得られました。

6. 結論

この記事では、更新の中断の問題を理解することから始めました。 次に、Linuxシステムでのさまざまなタイプのファイルロックスキームについて説明しました。

また、システムのロックをチェックする lslocks コマンドと、アドバイザリロックを実装するflockユーティリティについても学びました。

最後に、2つのデモンストレーションを見ました。 1つは、アドバイザリロックとプロセス連携の関係を理解するのに役立ち、もう1つは、アドバイザリロックがどのように機能するかを示しました。