システムインタフェース

第 5 章 入出力インタフェース

この章では、仮想記憶サービスを提供していないシステムに提供されるファイル入出力操作を紹介します。仮想記憶機能によって提供される向上した入出力方式についても言及します。また、ファイルとレコードをロッキングする旧式の重量方式も説明します。

ファイルと入出力

一連のデータとして構成されたファイルを「通常」ファイルと言います。このようなファイルには、ASCII テキスト、他の符号化バイナリデータによるテキスト、実行可能コード、またはテキスト、データ、コードの組み合わせが入っています。ファイルは、次の 2 つのコンポーネントに分かれています。

Solaris は、次の 3 つの基本的なファイル入出力インタフェースを用意しています。

基本ファイル入出力

表 5-1 に示している関数は、ファイルで基本操作を実行します。

表 5-1 基本的なファイル入出力関数

関数名 

目的 

open(2)

読み取りまたは書き込み用にファイルを開く。 

close(2)

ファイル記述子を閉じる。 

read(2)

ファイルから読み取る。 

write(2)

ファイルに書き込む。 

creat(2)

新しいファイルを作成するか、既存のファイルに上書きする。 

unlink(2)

ディレクトリエントリを削除する。 

lseek(2)

読み取り / 書き込み用のファイルポインタを移動する。 

次のコード例は、基本的なファイル入出力インタフェースの使用方法を示します。read(2)write(2) はどちらも、現在のファイルのオフセットから指定された数を超えないバイト数を転送します。実際に転送されたバイト数が戻されます。ファイルの終わりでは、read(2) の戻り値が 0 になります。


例 5-1


#include			<fcntl.h>
#define			MAXSIZE			256

main()
{
 	int		fd;
		ssize_t	n;
 	char		array[MAXSIZE];
		

 	fd = open ("/etc/motd", O_RDONLY);
 	if (fd == -1) {
 		perror ("open");
 		exit (1);
 	}
 	while ((n = read (fd, array, MAXSIZE)) > 0)
 		if (write (1, array, n) != n)
 			perror ("write");
 	if (n == -1)
 		perror ("read");
 	close (fd);
}

読み取りまたは書き込みを完了したら、必ずファイルを閉じて (close(2)) ください。開いて (open(2)) いないファイル記述子を閉じて (close(2)) はなりません。

開いているファイル内のオフセットは、read(2)write(2)、または lseek(2) を呼び出すことによって変更されます。次に lseek(2) の使用例を示します。


off_t		start, n;
 struct		record		rec;

 /* 現在のオフセットの位置をスタートにセットする */
 start = lseek (fd, 0L, SEEK_CUR);

 /* スタートに戻る */
 n = lseek (fd, -start, SEEK_SET);
 read (fd, &rec, sizeof (rec));

 /* 前のレコードをリライトする */
 n = lseek (fd, -sizeof (rec), SEEK_CUR);
 write (fd, (char *&rec, sizeof (rec));

高度なファイル入出力

高度なファイル入出力関数は、ディレクトリとファイルの作成と削除、既存のファイルへのリンクの作成、ファイル状態情報の取得または変更を行います。

表 5-2 高度なファイル入出力関数
 関数名 目的
link(2) ファイルにリンクする。
access(2) ファイルのアクセス可能性を判定する。
mknod(2) 特殊ファイルまたは通常のファイルを作成する。
chmod(2) ファイルのモードを変更する。
chown(2), lchown (2), fchown(2) ファイルの所有者とグループを変更する。
utime(2) ファイルのアクセス時刻や変更時刻を設定する。
stat(2), lstat(2), fstat(2) ファイルのステータスを取得する。
fcntl(2) ファイル制御機能を実行する。
ioctl(2) デバイスを制御する。
fpathconf(2) 設定可能なパス名変数を取得する。
opendir(3C), readdir(3C), closedir(3C) ディレクトリを操作する。
mkdir(2) ディレクトリを作成する。
readlink(2) シンボリックリンクの値を読みとる。
rename(2) ファイル名を変更する。
rmdir(2) ディレクトリを削除する。
symlink(2) ファイルへのシンボリックリンクを作成する。

ファイルシステム制御

ファイルシステム制御関数を使用して、ファイルシステムを制御できます。

表 5-3 ファイルシステム情報を取得する。
 関数名 目的
ustat(2) ファイルシステムの統計情報を取得する。
sync(2) スーパーブロッキングを更新する。
mount(2) ファイルシステムをマウントする。
statvfs(2), fstatvfs(2) ファイルシステム情報を取得する。
sysfs(2) ファイルシステムの種類の情報を取得する。

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

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

ファイルまたはその一部をロッキングすると、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) ビットがオンになるだけでなく強制ロッキングが有効になっています。

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

端末入出力

端末入出力関数は、次の表に示すように、非同期通信ポートを制御する一般的な端末インタフェースを処理します。termios(3C)termio(7I) も参照してください。

表 5-4 端末入出力関数
 関数名 目的
tcgetattr(3C), tcsetattr(3C) 端末属性を取得または設定する。
tcsendbreak(3C), tcdrain(3C), tcflush(3C), tcflow(3C) 回線制御関数を実行する。
cfgetospeed(3C), cfgetispeed(3C), cfsetispeed(3C), cfsetospeed(3C) ボーレートを取得または設定する。
tcsetpgrp(3C) 端末のフォアグラウンドプロセスのグループ ID を取得または設定する。
tcgetsid(3C) 端末のセッション ID を取得する。