次の表に、アドバイザリロックと強制ロックの両方がサポートされるファイルシステムの一覧を示します。
表 5–4 サポートされるファイルシステム
ファイルシステム |
説明 |
---|---|
ufs |
ディスクベースのデフォルトのファイルシステム |
fifofs |
プロセスが共通の方法でデータにアクセスできるようにする名前付きパイプファイルからなる疑似ファイルシステム |
namefs |
ファイル記述子をファイルの先頭に動的にマウントするために、主に STREAMS によって使用される疑似ファイルシステム |
specfs |
特殊なキャラクタ型デバイスやブロック型デバイスにアクセスするための疑似ファイルシステム |
NFSTM 上では、アドバイザリファイルロックのみがサポートされます。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; /* 書き込みロックを設定する */ 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) ではロックできません。ただし、マルチスレッド指向の同期メカニズムを使用すると、マッピングされたファイルをロックできます。このような同期メカニズムは POSIX スタイルと Solaris スタイルのどちらでも使用できます。mutex(3THR)、condition(3THR)、semaphore(3THR)、mmap(2)、および 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 %8ld %8ld\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) はサーバーが応答するまで待機および休眠できます。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; }
プロセスがフォークを行うと、子プロセスは親プロセスが開いたファイル記述子のコピーを受け取ります。ただし、ロックは特定のプロセスによって所有されるので、子プロセスに継承されません。親プロセスと子プロセスは、ファイルごとに共通のファイルポインタを共有します。両方のプロセスが、同じファイル内の同じ位置にロックを設定しようとすることがあります。この問題は、lockf(3C) と fcntl(2) でも発生します。レコードのロックを保留しているプログラムがフォークを行う場合、子プロセスはまず、そのファイルを閉じる必要があります。ファイルを閉じた後、子プロセスはそのファイルを開き直して、新しい異なるファイルポインタを設定する必要があります。
UNIX のロック機能を使用すると、デッドロックを検出および防止できます。デッドロックが発生する可能性があるのは、システムがレコードロックインタフェースを休眠させようとするときだけです。(このとき)、2 つのプロセスがデッドロック状態であるかどうかを判断する検索が行われます。潜在的なデッドロックが検出されると、ロックインタフェースは失敗し、デッドロックを示す値が errno に設定されます。F_SETLK を使用してロックを設定するプロセスは、ロックがすぐに取得できなくてもそれを待たないので、デッドロックは発生しません。