システムインタフェース

ファイルとレコードのロッキング

従来のファイル入出力を使用して、ファイル要素をロッキングする必要はありません。『マルチスレッドのプログラミング』で説明している軽量の同期メカニズムは、マッピングされたファイルと一緒に効果的に使用できるため、この節で説明する旧式のファイルロッキングよりも効率的です。

ファイルまたはその一部をロッキングすると、2 人以上のユーザがファイルの情報を同時に更新しようとした場合に生じるエラーを防止できます。

ファイルロッキングとレコードロッキングは、実際には同じものです。ただし、ファイルロッキングではファイル全体へのアクセスをブロッキングするのに対し、レコードロッキングではファイルの指定されたセグメントへのアクセスだけをブロッキングします。(SunOS 5.0 から 5.8 のシステムでは、すべてのファイルはデータのバイトシーケンスであり、レコードはファイルを使用するプログラムの概念です。)

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

次のタイプのファイルシステム上では、アドバイザリロッキングと強制ロッキングがサポートされます。

ロッキングタイプの選択

強制ロッキングでは、要求されたファイルセグメントが解放されるまで、プロセスは待機します。アドバイザリロッキングでは、ロッキングが取得されたかどうかを示す結果だけが返されます。プロセスは、その結果を無視して入出力を続けることができます。同一のファイルに強制ロッキングとアドバイザリロッキングを同時には適用できません。開いたときのファイルのモードによって、そのファイル上の既存のロッキングが強制ロッキングとして処理されるか、アドバイザリロッキングとして処理されるかが決まります。

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) 関数を使用します。このプログラムは、次のいずれかの状況が発生するまでファイルをロッキングしようとします。


#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)F_GETLK が使用されます。次の例では、ファイル内でロッキングされているすべてのセグメントについてのデータを検索して出力します。


例 5-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 %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) ビットがオンになるだけでなく強制ロッキングが有効になっています。

強制ロッキングについての注意事項