プログラミングインタフェース

ファイルとレコードのロックの使用

ファイル要素をロックするために従来のファイル入出力を使用する必要はありません。マッピングされたファイルには、より軽量な同期メカニズム (『マルチスレッドのプログラミング』を参照) を使用します。

ファイルをロックすると、複数のユーザーが同時にファイルを更新しようとした場合に生じるエラーを防止できます。ファイルの一部だけもロックできます。

ファイルをロックすると、そのファイル全体へのアクセスがブロックされます。レコードをロックすると、そのファイルの指定されたセグメントへのアクセスがブロックされます。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 の設定ビットの通常の意味論も有効です。

強制ロックについての注意事項

ロックについては、次の点について注意してください。

サポートされるファイルシステム

次の表に、アドバイザリロックと強制ロックの両方がサポートされるファイルシステムの一覧を示します。

表 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) インタフェースは、次のいずれかの状況が発生するまでファイルをロックしようとします。

レコードロックの設定と解除

レコードをロックする場合、ロックセグメントの開始位置と長さを 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 が使用されます。

次の例では、ファイル内でロックされているすべてのセグメントについてのデータを検索して出力します。


例 6–2 ファイル内でロックされているセグメントの出力

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) はプロセスがロックを保留しているかどうかを検査できます。このインタフェースは、ロックの位置と所有権についての情報を返しません。


例 6–3 lockf によるプロセスの検査

(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 を使用してロックを設定するプロセスは、ロックがすぐに取得できなくてもそれを待たないので、デッドロックは発生しません。