ファイルロック
ファイルロックとは、コンピュータのへのアクセスを一時的に1人のユーザーや1つのプロセスに制限する機構。このロックの目的はいわゆる 「仲裁更新 interceding update」のシナリオを防ぐことである。
仲裁更新問題とは次のような例で表される。プロセスAおよびプロセスBがある顧客の口座残高を含むレコードをファイルから読み込み、各プロセスがその値を別々のメモリに保持している状況下で、次の事象が順番に発生した場合を考える。
プロセスAは自分が読み込んだレコードの口座残高欄を変更し、ファイルにそのレコード全体を書き戻す。
プロセスBも同様に自分が保持するレコード内の情報を変更し、ファイルにレコード全体を書き戻す。
プロセスBが書き戻したレコードには、プロセスAが加えた変更が一切反映されていないため、口座残高はBによって上書きされ、内容が失われてしまう。
ファイルロックは指定されたファイルについてプロセスによる更新をシリアライズ(逐次化)することでこの問題を防ぐ。多くのオペレーティングシステムはレコードロックの概念をサポートしている。これはファイル内のレコード単位にロックする機構であり、同時に更新に関わることができるプロセス数を増加させる効果がある。
他のロックと同様、ファイルロックの使用法を間違うと性能低下やデッドロックを招く。ロックをかける時間は、可能な限り最小にすることが推奨される。
UNIXで電子メールをディスクに保存するときに伝統的に使われるフォーマットmboxは、一つのファイルに複数の電子メールメッセージを記録している。そのため、mboxに対し複数のプロセスが互いを考慮しないまま同時に書き込みや削除を行うと、仲裁更新問題が発生して消したはずのメールが復活してしまったり、逆に新たに受信したメールが消えてしまう。
これを防ぐため、各OSのファイルロック機構やロックファイルでmboxをロックすることでこの問題を回避している。
データベースを構成している物理ファイル全体についてシリアライズ(逐次化)を施す。これで他のプロセスがファイルにアクセスするのを防ぐことができるし、関連するレコード群をロックするよりも事実上効率的かもしれない(多数のレコードロックをかけたり外したりするオーバヘッドを考慮した場合)。
Windowsでの共用ファイルアクセスは以下の3つの機構で管理される。
共用アクセス制御を使用して、アプリケーションにファイル全体へのリード/ライト/削除を許可する。
バイト単位のロックを使用して、ファイル内の一部分へのリード/ライトを裁定する。
Windowsのは、実行中ファイルについて、ライトまたは削除のためにオープンすることを拒否する。
Windowsでの共用アクセス制御はMS-DOS 3.3で導入されたものを受け継いでいる。アプリケーションは明示的に共有を許すか、排他的なリード/ライト/削除をファイルに対して行う。その他のアクセス種別として、ファイル属性へのアクセスを許すなどがある。
共用アクセスのあるファイルについて、アプリケーションがバイト単位のロックを使ってファイルの特定の領域へのアクセスを制御する場合もある。バイト単位のロックはファイルの一部(オフセットと長さ)とロック種別(共有か排他か)を指定する。ロックされる領域が実際のファイルの大きさに収まっている必要はなく、中にはこの機能を利用したアプリケーションもあることに注意が必要である。
Windows上でファイルのリード/ライトAPIを使うアプリケーションについては、Windows内のファイルシステムが強制的にバイト単位ロックを設定する。Windows上でファイルマッピングAPIを使用するアプリケーションの場合、バイト単位ロックは強制されない。バイト単位ロックには他の副作用もある。例えば、Windowsのファイル共有機構では、クライアントがファイルアクセス時にバイト単位ロックをかけると、自動的に全クライアントのファイルキャッシュを無効化する。このため、リード/ライト要求が全てファイルサーバ上の実際のファイルに対して行われるので、ファイルアクセスが遅くなったように感じられるのである。
アプリケーションのエラー処理にバグがあるとファイルがロックされて(共有アクセスでもバイト単位ロックでも)、他のアプリケーションからアクセスできなくなる。このとき、不正動作しているプログラムを手動で終了させることでファイルアクセスが可能になるだろう。これは一般に「タスクマネージャ」を使って行う。
ファイル共有はCreateFile
関数でファイルをオープンするときの共有モード引数(dwShareMode)で指定する。ファイルをオープンする際にリード/ライト/削除のアクセスに関してファイルを共有することを許可する。その後の同じファイルのオープンは、それ以前のオープンで許可されたタイプしか許されない。ファイルをクローズすると、そのクローズに対応したオープンの設定した共有アクセス制限が外される。
バイト単位のロック種別はLockFileEx関数でファイルの一部領域をロックするときに dwFlags 引数で指定する。Windows API 関数 LockFileはファイルの一部を排他的にロックするのに使うことができる。
プログラムとして実行されているファイル(例えば、EXE、COM、DLL、CPLなど)は、一般にライト/削除アクセスのためのオープンができないようファイルシステムが制限し、共有違反(sharing violation)が報告される。しかし、ある種のアクセスは許されている。例えば、実行中アプリケーションのファイルを改名したりコピー(リード)したりすることは可能である。
マイクロソフトが配布しているProcess Explorerを使うと、動作中の各アプリケーションが利用しているファイルハンドルを表示したり、アプリケーションを終了させることなく利用中のファイルハンドルを解放することが出来る。
Windows XP以降では、NTFSにボリュームスナップショット機能が導入された。これはオープン中で排他ロックされているファイルも含めてバックアップソフトウェアがアクセスできるようにする機能である。ただし、この機能に対応するよう書き換えられたソフトウェアでないと、生成されるスナップショットは一貫性がないものとなる(例えば、ファイルの一部だけ書き換えられているなど)。この機能を正しくサポートしたバックアップアプリケーションはオペレーティングシステムの機能を使って「処理上の一貫性; transactionally consistent」を保ったスナップショットを生成できる。他にロックされたファイルにアクセスできる商用ソフトとして File Access Manager や Open File Manager がある。
Windows上で、ロックがかけられるのは、削除とファイル内容の読み書きに対してのみであり、それ以外の、フォルダ内のファイル一覧やファイルの属性の読み書きといった操作などに対しては、ロックはかけられない。Vista以降では、トランザクションNTFS (TxF)があり、複数ファイルに対する一貫した操作のための機能が提供されている。
UNIXではオープン中のファイル(および実行中プログラム)は自動的にロックされない。UNIXの系統によってファイルロックの機構は異なり、多くのUNIX系システムは互換性のために複数のファイルロック機構をサポートしている。最も共通して使われているのは、fcntl と flock である。強制的にロックをかける設定にできる場合もあるが、UNIXのファイルロックは基本的に advisory(助言、補助)である。つまり、プロセスはロックに従ってファイルアクセスを抑制することもできるが、プログラムによってはロックを無視してファイルにアクセスすることも可能である。
ロックには共有ロックと排他ロックがある。<tt>fcntl</tt>では、同一ファイル内で一部を共有ロックにしたり、別の部分を排他ロックにしたり、あるいはファイル全体をロックしたりできる。共有ロックは任意の個数のプロセスが同時にかけることができるが、排他ロックは1つのプロセスしかかけられず、(ファイルの同じ領域で)共有ロックとも共存できない。共有ロックをかける際に、先に排他ロックがかかっていたら、その排他ロックが外されるまで待たされる。排他ロックをかける際には、その領域に何のロックもかかっていない状態になるまで待たされる。
共有ロックを「リードロック」、排他ロックを「ライトロック」と呼ぶこともある。しかし、UNIXのロックは強制力がないので、リード専用/ライト専用ということではない。データベースにも「共有書き込み」と「排他書き込み」というコンセプトがある。例えば、あるフィールドの内容の書き換えは共有書き込みで可能だが、ガベージコレクションのようなものやデータベース全体の書き換えは排他書き込みになるだろう。
inodeと非強制的ロックの組合せにより、多くのプロセスがファイルに自由にアクセスすることができる。一方、ロックに従わないプロセスが簡単にファイルアクセスできてしまうという問題もある。このため、UNIX系オペレーティングシステムには「強制ロック; mandatory lock」もサポートしているものもある。
<tt>flock</tt>にも<tt>fcntl</tt>にも、他のオペレーティングシステムに慣れたプログラマから見ると奇異な点がある。
<tt>flock</tt>は NFS などのネットワークファイルシステムでは実装により機能しない場合がある。BSDでは何も起きないし、Linuxではエラーとなる。同じファイルへの(ひとつのプロセスからの)<tt>flock</tt>呼び出しは、ロックの状態を変更する(共有から排他へ、あるいは排他から共有へ)。しかし、このとき一時的に古いロックを外してから新しいロックをかける。例えば、排他ロックから共有ロックにかけ変えようとしたとき、一瞬の隙をついて別のプロセスが排他ロックをかけたら、元々ロックをかけていたプロセスが締め出されてしまう。
プロセスはひとつのファイルに対応するを複数個持つことができる。このうちのいずれかを使って<tt>fcntl</tt>でロックをかけていたとする。このとき、同じファイルに対応する別のファイル記述子をクローズすると、そのファイル上にそのプロセスが設定した全ロックが解除されてしまう。また、<tt>fcntl</tt>のロックは子プロセスに受け継がれない。この<tt>fcntl</tt>のクローズに関連した動作は、ライブラリのサブルーチン内でファイルにアクセスすることが多いアプリケーションなどで問題を発生することが多い。
Linux 2.4 とその後の版では "dnotify" 機構によってファイルの外見上の変化を通知してもらうことができる(fcntl で F_NOTIFY を指定)。ただし、この機構は Linux 2.6.13 では inotifyで置き換えられた。
また、強制ロックもサポートしていて "mount -o mand" というファイルシステムをマウントする時のパラメータを設定しておき、個別のファイルディスクリプタにfcntlを設定して使用する。ただし、多数のバグが存在し、滅多に使われない。
BTRONには、他の主なOSとは根本的に異なるを採用しており、厳密にはファイルロックという概念は無い。このファイルシステムはTADと呼ばれ、仮身や実身と呼ばれる要素からなる。
仮身はWindowsなどの環境におけるアイコンのような概念であるが、一つの仮身は二重に開くことはできない。一方、実身とはファイルの実体であり、一つの実身に複数の仮身を存在させることができる。もし実身の中に自らの仮身を作るとループができ、この場合、個々のBTRON仕様OSの実装を別とすれば理論上はメモリが許す限り無制限に開くことも可能になる。こうして別々の仮身から開かれた同一実身は、いずれのプロセスより更新が行われると、この更新情報を記録し、同時に開かれていた「同一実身」の「別の仮身」が改めて保存を行おうとするとアラートを返し、確認する仕組みになっている。
Javaでは、ファイルの読み書き時は、Windows上では、java.io.FileInputStream は削除ロックが、java.io.FileOutputStream では、削除ロックと書き込みロックがかかる。明示的に、読み書きロックを使用するには、java.nio.channels.FileChannel の lock() メソッドを使用する。
ロックファイルはシェルスクリプトなどのプログラムでファイルロックの代替として使われる手法である。ロックファイルは内容は無関係で(ただし、ロック保持者のプロセス識別子を書き込んでおくことが多い)、存在することで他者に対して何らかの資源がロックされていることを知らせるのに使われる。通常ファイル以外のリソースを排他制御したときなどに主に使われる。
ロックファイルを使う場合、その操作がアトミックであることを保証するよう注意しなければならない。ロックを作成するとき、ロックファイルが存在しないことを確認してから作成するが、その途中で他のプロセスがロックファイルを先に作成してしまうかもしれない。これを防ぐ様々な手法がある。例えば、そのために設計された専用システムコールを使ったり(しかしシェルスクリプトから直接そのようなシステムコールを使うことはできない)、一時的な名前でロックファイルを作っておいて確認してから本来の名前にするなどの手法がある。