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

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

この章では、仮想メモリーサービスのないシステムに対するファイル入出力操作を紹介します。この章ではまた、仮想記憶機能によって向上した入出力方式についても説明します。「ファイルとレコードのロックの使用」では、ファイルとレコードをロックする旧来の方法について説明します。

ファイルと入出力インタフェース

一連のデータが編成されたファイルを「通常ファイル」と呼びます。通常ファイルには、ASCII テキスト、ほかの符号化バイナリデータによるテキスト、実行可能コード、またはテキスト、データ、コードの組み合わせが入っています。

通常ファイルの構成要素は次のとおりです。

Solaris オペレーティングシステムでは、次のような基本的なファイル入出力インタフェースが提供されています。

基本ファイル入出力

次のインタフェースは、ファイルとキャラクタ入出力デバイス上で基本的な操作を実行します。

表 6–1 基本的なファイル入出力インタフェース

インタフェース名 

目的 

open(2)

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

close(2)

ファイル記述子を閉じます 

read(2)

ファイルから読み取ります 

write(2)

ファイルに書き込みます 

creat(2)

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

unlink(2)

ディレクトリエントリを削除します 

lseek(2)

読み取りまたは書き込み用のファイルポインタを移動します 

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


例 6–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) を呼び出してください。close(2) の呼び出しが完了していないファイル記述子に対しては open(2) を呼び出してはなりません。

開いたファイルへのファイルポインタオフセットを変更するには、read(2) または write(2) を使用するか、lseek(2) を呼び出します。次の例では、lseek の使い方を示します。

off_t     start, n;
struct    record    rec;

/* record current offset in start */
start = lseek (fd, 0L, SEEK_CUR);

/* go back to start */
n = lseek (fd, -start, SEEK_SET);
read (fd, &rec, sizeof (rec));

/* rewrite previous record */
n = lseek (fd, -sizeof (rec), SEEK_CUR);
write (fd, (char *&rec, sizeof (rec));

高度なファイル入出力

次の表に、高度なファイル入出力インタフェースが実行するタスクの一覧を示します。

表 6–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)

ファイルへのシンボリックリンクを作成します 

ファイルシステム制御

次の表にあるファイルシステム制御インタフェースを用いて、ファイルシステムに対してさまざまな制御を行うことができます。

表 6–3 ファイルシステム制御インタフェース

インタフェース名 

目的 

ustat(2)

ファイルシステムの統計情報を取得します 

sync(2)

スーパーブロックを更新します 

mount(2)

ファイルシステムをマウントします 

statvfs(2)fstatvfs(2)

ファイルシステム情報を取得します 

sysfs(2)

ファイルシステムの種類の情報を取得します 

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

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

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

ファイルをロックすると、そのファイル全体へのアクセスがブロックされます。レコードをロックすると、そのファイルの指定されたセグメントへのアクセスがブロックされます。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 を使用してロックを設定するプロセスは、ロックがすぐに取得できなくてもそれを待たないので、デッドロックは発生しません。

端末入出力インタフェース

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

表 6–5 端末入出力インタフェース

インタフェース名 

目的 

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 を取得します 

次の例に、DEBUG 以外の操作モードにおいて、サーバーがどのようにその呼び出し元の制御端末との関連付けを解除するかを示します。


例 6–4 制御端末との関連付けを解除する

   (void) close(0);
   (void) close(1);
   (void) close(2);
   (void) open("/", O_RDONLY);
   (void) dup2(0, 1);
   (void) dup2(0, 2);
   setsid();

この操作モードでは、サーバーは制御端末のプロセスグループからシグナルを受信しません。サーバーが関連付けを解除したあと、サーバーはエラーレポートを端末に送信できません。したがって、このサーバーは syslog(3C) を使用してエラーを記録する必要があります。