ファイル要素をロックするために従来のファイル入出力を使用する必要はありません。マッピングされたファイルには、より軽量な同期メカニズム (『マルチスレッドのプログラミング』を参照) を使用します。
ファイルをロックすると、複数のユーザーが同時にファイルを更新しようとした場合に生じるエラーを防止できます。ファイルの一部だけもロックできます。
ファイルをロックすると、そのファイル全体へのアクセスがブロックされます。レコードをロックすると、そのファイルの指定されたセグメントへのアクセスがブロックされます。SunOS では、すべてのファイルはデータのバイトシーケンスであり、 レコードはファイルを使用するプログラムの概念です。
強制ロックでは、要求されたファイルセグメントが解放されるまで、プロセスは保留されます。アドバイザリロックでは、ロックが取得されたかどうかを示す結果だけが返されます。プロセスはアドバイザリロックの結果を無視できます。同一のファイルに強制ロックとアドバイザリロックを同時に適用することはできません。開いたときのファイルのモードによって、そのファイル上の既存のロックが強制ロックとして処理されるか、アドバイザリロックとして処理されるかが決まります。
2 つの基本的なロック呼び出しのうち、fcntl(2) は lockf(3C) よりも移植性が高く高性能ですが、より複雑です。fcntl(2) は POSIX 1003.1 で規格化されています。lockf(3C) は、ほかのアプリケーションとの互換性を保つために用意されています。
強制ロックの場合、対象のファイルはグループ ID の設定ビットがオンになっており、グループの実行権がオフになっている通常ファイルでなければなりません。どちらかの条件が欠けていると、すべてのレコードロックはアドバイザリロックになります。
次のように強制ロックを設定します。
#include <sys/types.h> #include <sys/stat.h> int mode; struct stat buf; ... if (stat(filename, &buf) < 0) { perror("program"); exit (2); } /* get currently set mode */ mode = buf.st_mode; /* remove group execute permission from mode */ mode &= ~(S_IEXEC>>3); /* set 'set group id bit' in mode */ mode |= S_ISGID; if (chmod(filename, mode) < 0) { perror("program"); exit(2); } ...
ファイルを実行するとき、オペレーティングシステムはレコードロックを無視します。レコードロックが適用されるファイルには実行権を設定しないでください。
ファイルに強制ロックを設定するには、次のように chmod(1) コマンドも使用可能です。
$ chmod +l file |
このコマンドはファイルモード内に O20n0 アクセス権ビットを設定します。これはファイルの強制ロックを示します。n が偶数の場合、そのビットは強制ロックを有効にすると解釈され、n が奇数の場合、そのビットは「実行時グループ ID 設定」として解釈されます。
この設定を表示するには、ls(1) コマンドに -l オプション (ロングリスト形式) を指定して実行します。
$ ls -l file |
すると、次のような情報が表示されます。
-rw---l--- 1 user group size mod_time file |
アクセス権の文字「l」は、グループ ID の設定ビットがオンであることを示します。グループ ID の設定ビットがオンであるので、強制ロックは有効です。グループ ID の設定ビットの通常の意味論も有効です。
ロックについては、次の点について注意してください。
強制ロックは、ローカルファイルだけで利用できます。NFS を介してファイルにアクセスするとき、強制ロックはサポートされません。
強制ロックは、ファイル内のロックされているセグメントだけを保護します。ファイルの残りの部分には、通常のファイルアクセス権に従ってアクセスできます。
不可分のトランザクションに多重の読み取りや書き込みが必要な場合は、入出力を開始する前に、対象となるすべてのセグメントについてプロセスが明示的にロックする必要があります。このように動作するプログラムの場合は、いずれもアドバイザリロックで十分です。
レコードロックが使用されるファイルについては、全プログラムに無制限のアクセス権を与えてはいけません。
次の表に、アドバイザリロックと強制ロックの両方がサポートされるファイルシステムの一覧を示します。
表 6–4 サポートされるファイルシステム
ファイルシステム |
説明 |
---|---|
ufs |
ディスクベースのデフォルトのファイルシステム |
fifofs |
プロセスが共通の方法でデータにアクセスできるようにする名前付きパイプファイルからなる疑似ファイルシステム |
namefs |
ファイル記述子をファイルの先頭に動的にマウントするために、主に STREAMS によって使用される疑似ファイルシステム |
specfs |
特殊なキャラクタ型デバイスやブロック型デバイスにアクセスするための疑似ファイルシステム |
NFS 上では、アドバイザリファイルロックのみがサポートされます。proc ファイルシステムと fd ファイルシステム上では、ファイルロックはサポートされません。
ロックを要求できるのは、有効な開いたファイル記述子を持つファイルだけです。読み取りロックの場合は、少なくとも読み取りアクセスを設定してファイルを開く必要があります。書き込み用ロックの場合は、書き込みアクセスも設定してファイルを開く必要があります。次の例では、ファイルは読み取りと書き込みの両方のアクセス用に開かれます。
... filename = argv[1]; fd = open (filename, O_RDWR); if (fd < 0) { perror(filename); exit(2); } ...
ファイル全体をロックするには、オフセットを 0 に設定し、サイズを 0 に設定します。
ファイルをロックする方法はいくつかあります。どの方法を選択するかは、ロックとプログラムのほかの部分との関係、または性能や移植性によって決まります。次の例では、POSIX 標準互換の fcntl(2) インタフェースを使用します。fcntl(2) インタフェースは、次のいずれかの状況が発生するまでファイルをロックしようとします。
ファイルロックが正常に設定された。
エラーが発生した。
MAX_TRY 回数を超えたため、プログラムがファイルのロックを中止した。
#include <fcntl.h> ... struct flock lck; ... lck.l_type = F_WRLCK; /* setting a write lock */ lck.l_whence = 0; /* offset l_start from beginning of file */ lck.l_start = (off_t)0; lck.l_len = (off_t)0; /* until the end of the file */ if (fcntl(fd, F_SETLK, &lck) <0) { if (errno == EAGAIN || errno == EACCES) { (void) fprintf(stderr, "File busy try again later!\n"); return; } perror("fcntl"); exit (2); } ...
fcntl(2) を使用すると、構造体の変数を設定し、ロック要求のタイプと開始を設定できます。
マッピングされたファイルは flock(3UCB) ではロックできません。ただし、マルチスレッド指向の同期メカニズムを使用すると、マッピングされたファイルをロックできます。このような同期メカニズムは POSIX スタイルと Solaris スタイルのどちらでも使用できます。
レコードをロックする場合、ロックセグメントの開始位置と長さを 0 に設定してはなりません。それ以外、レコードのロックはファイルのロックと同じです。
レコードロックを使用するのは、データが競合するためです。したがって、必要なすべてのロックを設定できない場合に備えて、次のような対処方法を用意しておく必要があります。
一定時間待ってから再試行する
手順を中止してユーザに警告する
ロックが解除されたことを示すシグナルを受信するまでプロセスを休眠させておく
上記のいくつかを組み合わせて実行する
次の例に、fcntl(2) を使用してレコードをロックする方法を示します。
{ struct flock lck; ... lck.l_type = F_WRLCK; /* setting a write lock */ lck.l_whence = 0; /* offset l_start from beginning of file */ lck.l_start = here; lck.l_len = sizeof(struct record); /* lock "this" with write lock */ lck.l_start = this; if (fcntl(fd, F_SETLKW, &lck) < 0) { /* "this" lock failed. */ return (-1); ... }
次の例に、lockf(3C) インタフェースを示します。
#include <unistd.h> { ... /* lock "this" */ (void) lseek(fd, this, SEEK_SET); if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) { /* Lock on "this" failed. Clear lock on "here". */ (void) lseek(fd, here, 0); (void) lockf(fd, F_ULOCK, sizeof(struct record)); return (-1); }
ロックの解除は設定と同じように行います。ロックタイプが異なるだけです (F_ULOCK)。ロックの解除は別のプロセスによってブロックされず、そのプロセスが設定したロックに対してだけ有効です。ロック解除は、前のロック呼び出しで指定されたファイルのセグメントに対してだけ有効です。
どのプロセスがロックを保留しているかを判断できます。ロックは前述の例のように設定され、fcntl(2) で F_GETLK が使用されます。
次の例では、ファイル内でロックされているすべてのセグメントについてのデータを検索して出力します。
struct flock lck; lck.l_whence = 0; lck.l_start = 0L; lck.l_len = 0L; do { lck.l_type = F_WRLCK; (void) fcntl(fd, F_GETLK, &lck); if (lck.l_type != F_UNLCK) { (void) printf("%d %d %c %8ld %8ld\n", lck.l_sysid, lck.l_pid, (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len); /* If this lock goes to the end of the address space, no * need to look further, so break out. */ if (lck.l_len == 0) { /* else, look for new lock after the one just found. */ lck.l_start += lck.l_len; } } } while (lck.l_type != F_UNLCK);
F_GETLK コマンドを指定すると、fcntl(2) はサーバーが応答するまで待機および休眠できます。fcntl(2) はまた、クライアントまたはサーバー側の資源が不足すると失敗して、ENOLCK を返すことがあります。
F_TEST コマンドを指定すると、lockf(3C) はプロセスがロックを保留しているかどうかを検査できます。このインタフェースは、ロックの位置と所有権についての情報を返しません。
(void) lseek(fd, 0, 0L); /* set the size of the test region to zero (0). to test until the end of the file address space. */ if (lockf(fd, (off_t)0, SEEK_SET) < 0) { switch (errno) { case EACCES: case EAGAIN: (void) printf("file is locked by another process\n"); break; case EBADF: /* bad argument passed to lockf */ perror("lockf"); break; default: (void) printf("lockf: unexpected error <%d>\n", errno); break; } }
プロセスがフォークを行うと、子プロセスは親プロセスが開いたファイル記述子のコピーを受け取ります。ただし、ロックは特定のプロセスによって所有されるので、子プロセスに継承されません。親プロセスと子プロセスは、ファイルごとに共通のファイルポインタを共有します。両方のプロセスが、同じファイル内の同じ位置にロックを設定しようとすることがあります。この問題は、lockf(3C) と fcntl(2) でも発生します。レコードのロックを保留しているプログラムがフォークを行う場合、子プロセスはまず、そのファイルを閉じる必要があります。ファイルを閉じたあと、子プロセスはそのファイルを開き直して、新しい異なるファイルポインタを設定する必要があります。
UNIX のロック機能を使用すると、デッドロックを検出および防止できます。デッドロックが発生する可能性があるのは、システムがレコードロックインタフェースを休眠させようとするときだけです。(このとき)、2 つのプロセスがデッドロック状態であるかどうかを判断する検索が行われます。潜在的なデッドロックが検出されると、ロックインタフェースは失敗し、デッドロックを示す値が errno に設定されます。F_SETLK を使用してロックを設定するプロセスは、ロックがすぐに取得できなくてもそれを待たないので、デッドロックは発生しません。