従来のファイル入出力を使用して、ファイル要素をロッキングする必要はありません。『マルチスレッドのプログラミング』で説明している軽量の同期メカニズムは、マッピングされたファイルと一緒に効果的に使用できるため、この節で説明する旧式のファイルロッキングよりも効率的です。
ファイルまたはその一部をロッキングすると、2 人以上のユーザがファイルの情報を同時に更新しようとした場合に生じるエラーを防止できます。
ファイルロッキングとレコードロッキングは、実際には同じものです。ただし、ファイルロッキングではファイル全体へのアクセスをブロッキングするのに対し、レコードロッキングではファイルの指定されたセグメントへのアクセスだけをブロッキングします。(SunOS 5.0 から 5.8 のシステムでは、すべてのファイルはデータのバイトシーケンスであり、レコードはファイルを使用するプログラムの概念です。)
次のタイプのファイルシステム上では、アドバイザリロッキングと強制ロッキングがサポートされます。
ufs - ディスクベースのデフォルトのファイルシステム
fifofs - プロセスが共通の方法でデータにアクセスできるようにする名前付きパイプファイルからなる疑似ファイルシステム
namefs - ファイル記述子をファイルの先頭に動的にマウントするために、主に STREAMS によって使用される疑似ファイルシステム
specfs - 特殊なキャラクタ型デバイスやブロッキング型デバイスにアクセスするための疑似ファイルシステム
NFSTM 上では、アドバイザリファイルロッキングのみがサポートされます。
proc ファイルシステムと fd ファイルシステム上では、ファイルロッキングはサポートされません。
強制ロッキングでは、要求されたファイルセグメントが解放されるまで、プロセスは待機します。アドバイザリロッキングでは、ロッキングが取得されたかどうかを示す結果だけが返されます。プロセスは、その結果を無視して入出力を続けることができます。同一のファイルに強制ロッキングとアドバイザリロッキングを同時には適用できません。開いたときのファイルのモードによって、そのファイル上の既存のロッキングが強制ロッキングとして処理されるか、アドバイザリロッキングとして処理されるかが決まります。
2 つの基本的なロッキング呼び出しのうち、fcntl(2) の方が lockf(3C) よりも移植性が高く高性能ですが、より複雑です。fcntl(2) は POSIX 1003.1 で規格されています。lockf(3C) は、他のアプリケーションとの互換性を保つために用意されています。
この節の後半を読むために役立つ定義の一部を示します。
用語 |
定義 |
---|---|
レコード |
ファイル内のバイトの連続したセット。UNIX オペレーティングシステムでは、ファイルのレコード構造は定義されていない。ファイルを使用するプログラムで、必要なレコード構造を定義できる。 |
共用資源のアクセスを規制するいくつかのメカニズムを使用する 2 つ以上のプロセス |
|
他のプロセスにも読み取りロッキングの適用や読み取りの実行をさせ、他のプロセスの書き込みまたは書き込みロッキングの適用をブロッキングする。 |
|
他のすべてのプロセスの読み取り、書き込み、またはロッキングの適用をブロッキングする。 |
|
ロッキングを保持していないプロセスをブロッキングしないでエラーを返す。アドバイザリロッキングは、creat(2)、open(2)、read(2)、および write(2) の処理には適用されない。 |
|
ロッキングを保持していないプロセスの実行をブロッキングする。ロッキングされているレコードへのアクセスは、creat(2)、open(2)、read(2)、および write(2) の処理に適用される。 |
ロッキングは、有効な記述子を持つファイルに対してだけ要求できます。読み取りロッキングの場合は、少なくとも読み取りアクセスを設定してファイルを開かなければなりません。書き込みロッキングの場合は、書き込みアクセスも設定してファイルを開かなければなりません。次の例では、ファイルは読み取りと書き込みアクセス用に開かれます。
... filename = argv[1]; fd = open (filename, O_RDWR); if(fd < 0) { perror(filename); exit(2); } ... |
ファイル全体をロッキングするには、オフセットを 0 に設定し、サイズを 0 に設定します。
ファイルをロッキングするには、いくつかの方法があります。どの方法を選択するかは、ロッキングとプログラムの他の部分との関係、または性能や移植性によって決まります。次の例では、POSIX 標準互換の fcntl(2) 関数を使用します。このプログラムは、次のいずれかの状況が発生するまでファイルをロッキングしようとします。
ファイルが正常にロッキングされた
エラーが発生した
MAX_TRY 回数を超えたため、プログラムがファイルのロッキングを中止した
#include <fcntl.h> ... struct flock lck; ... lck.l_type = F_WRLCK; /* 書き込みロッキングを設定する */ lck.l_whence = 0; /* ファイルの先頭からのオフセットは l_start */ lck.l_start = (off_t)0; lck.l_len = (off_t)0; /* ファイルの最後まで */ 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) でロッキングできません。詳細は、mmap(2) を参照してください。しかし、(POSIX スタイルまたは Solaris スタイルでの) マルチスレッド指向同期メカニズムをマッピングされたファイルで使用できます。mutex(3THR)、condition(3THR)、semaphore(3THR)、および rwlock(3THR) を参照してください。
レコードのロッキングは、ロッキングセグメントの開始位置と長さが 0 に設定されないこと以外は、ファイルのロッキングと同様に行います。
必要なすべてのロッキングを設定できない場合に備えて、対処方法を用意しておいてください。レコードロッキングを使用するのは、レコードへのアクセスが競合するためです。実行される動作は、プログラムによって次のように異なります。
一定時間待ってから再試行する
手順を中止してユーザに警告する
ロッキングが解除されたことを示すシグナルを受信するまでプロセスを休眠させておく
上記のいくつかを組み合わせて実行する
次の例は、fcntl(2) を使用してレコードをロッキングする方法を示します。
{ struct flock lck; ... lck.l_type = F_WRLCK; /* 書き込みロッキングを設定する */ lck.l_whence = 0; /* ファイルの先頭からのオフセットは l_start */ lck.l_start = here; lck.l_len = sizeof(struct record); /* this に書き込みロッキングを設定する */ lck.l_start = this; if (fcntl(fd, F_SETLKW, &lck) < 0) { /* this の書き込みロッキングが失敗 */ return (-1); ... } |
次の例は、lockf(3C) 関数を示します。
#include <unistd.h> ... /* this をロッキングする */ (void) lseek(fd, this, SEEK_SET); if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) { /* this のロッキングが失敗。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 %8d %8d¥n", lck.l_sysid, lck.l_pid, (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len); /* このロッキングがアドレス空間の終わりまで続いている場合は、 * それ以上探す必要がないのでループは終了する */ if (lck.l_len == 0) { /* それ以外の場合は、見つかったロッキングの後方のロッキングを探す */ lck.l_start += lck.l_len; } } } while (lck.l_type != F_UNLCK); |
F_GETLK() コマンドを伴う fcntl(2) 関数は、サーバの応答を待つ間に休眠することがあり、クライアント側またはサーバ側の資源が不足すると失敗する場合もあります (ENOLCK を戻します)。
F_TEST コマンドを伴う lockf(3C) 関数は、プロセスがロッキングを 保持しているかどうかの検査にも使用できます。ただしこの関数は、ロッキングの位置とそのロッキングを 所有しているプロセスについての情報は戻しません。
(void) lseek(fd, 0, 0L); /* ファイルアドレス空間の終わりまで検索するため、 テスト領域の大きさを 0 に設定する */ 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: /* lockf に渡された引数が不正 */ perror("lockf"); break; default: (void) printf("lockf: unexpected error <%d>¥n", errno); break; } |
プロセスが fork を行うと、子プロセスは親プロセスが開いたファイル記述子のコピーを受け取ります。ただし、ロッキングは特定のプロセスによって所有されるので、子プロセスに継承されません。親と子は、ファイルごとに共通のファイルポインタを共有します。両方のプロセスが、同じファイル内の同じ位置にロッキングを設定しようとすることがあります。この問題は、lockf(3C) と fcntl(2) でも発生します。レコードをロッキングするプログラムが fork を行う場合、子プロセスはファイルを閉じてから開き直して、新しく別のファイルポインタを設定する必要があります。
UNIX のロッキング機能を使用すると、デッドロッキングの検出と防止ができます。デッドロッキングが発生する可能性があるのは、システムがレコードロッキング機能を休眠させようとするときだけです。プロセス A がプロセス B に保持されたロッキングを待つと同時に、プロセス B がプロセス A に保持されたロッキングを待つような状況が発生していないかが検査されます。潜在的なデッドロッキングが検出されると、ロッキング関数は失敗し、デッドロッキングを示す値が errno に設定されます。F_SETLK を使用してロッキングを設定するプロセスは、ロッキングが即座に取得できなくても取得できるまで待たないので、デッドロッキングは発生しません。
強制ロッキングの場合、対象のファイルは set-group 識別 (setgid) グループ ID 設定ビットがオンになっており、グループの実行権がオフになっている通常ファイルでなければなりません。どちらかの条件が満たされなければ、すべてのレコードロッキングはアドバイザリロッキングになります。強制ロッキングを使用するには、次の例のようにします。
#include <sys/types.h> #include <sys/stat.h> int mode; struct stat buf; ... if (stat(filename, &buf) < 0) { perror("program"); exit (2); } /* 現在設定されているモードを取得する */ mode = buf.st_mode; /* グループの実効権をモードから削除する */ mode &= ‾(S_IEXEC>>3); /* set-group 識別 (setgid) ビットをモードに設定する */ mode |= S_ISGID; if (chmod(filename, mode) < 0) { perror("program"); exit(2); } ... |
レコードロッキングが適用されるファイルの場合は、実行権を設定しないでください。これは、オペレーティングシステムはファイルの実行時にレコードロッキングを無視するからです。
ファイルに強制ロッキングを設定するには、chmod(1) コマンドも使用できます。このコマンドを使用すると次のようになります。
$ chmod +l file |
このコマンドはファイルモード内に O20n0 アクセス権ビットを設定します。これはファイルの強制ロッキングを示します。n が偶数の場合、そのビットは強制ロッキングを有効にすると解釈されます。n が奇数の場合、そのビットは実行時に set-group 識別 (setgid) がオンになると解釈されます。
ls(1) コマンドで -l オプションを使用してロングリスト形式を指定すると、この設定が表示されます。
$ ls -l file |
この場合、次のような情報が表示されます。
-rw---l--- 1 user group size mod_time file |
アクセス権の英字 "l" は、set-group 識別 (setgid) ビットがオンになっていることを示します。したがって、通常の set-group 識別 (setgid) ビットがオンになるだけでなく強制ロッキングが有効になっています。
強制ロッキングは、ローカルファイルだけで利用できます。NFS を介してファイルにアクセスするときは利用できません。
強制ロッキングは、ファイル内のロッキングされているセグメントだけを保護します。ファイルの残りの部分には、通常のファイルアクセス権に従ってアクセスできます。
原子操作的トランザクションのために多重の読み取りや書き込みが必要な場合は、入出力が始まる前に、対象となるすべてのセグメントについてプロセスが明示的にロッキングする必要があります。このように動作するプログラムの場合は、いずれもアドバイザリロッキングで十分です。
レコードロッキングが使用されるファイルについては、どんなプログラムでも無制限のアクセス権を与えないでください。