この章では、仮想メモリーサービスのないシステムに対するファイル入出力操作を紹介します。この章ではまた、仮想記憶機能によって向上した入出力方式についても説明します。「ファイルとレコードのロックの使用」では、ファイルとレコードをロックする旧来の方法について説明します。
一連のデータが編成されたファイルを「通常ファイル」と呼びます。通常ファイルには、ASCII テキスト、ほかの符号化バイナリデータによるテキスト、実行可能コード、またはテキスト、データ、コードの組み合わせが入っています。
通常ファイルの構成要素は次のとおりです。
「i ノード」と呼ばれる制御データ。この制御データには、ファイルタイプ、アクセス権、所有者、ファイルサイズ、データブロックの位置が含まれます。
ファイルの内容。 区切れのないバイトシーケンス。
Solaris オペレーティングシステムでは、次のような基本的なファイル入出力インタフェースが提供されています。
従来の raw スタイルのファイル入出力については、「基本ファイル入出力」 を参照してください。
標準の入出力バッファリングによって、インタフェースが容易になり、仮想メモリーのないシステムで実行するアプリケーションの効率を改善できます。SunOS オペレーティングシステムのような仮想メモリー環境で動作しているアプリケーションでは、標準のファイル入出力は利用しなくなっています。
メモリーマッピングインタフェースについては、「メモリー管理インタフェース」 を参照してください。マッピングファイルは、SunOS プラットフォームで動作するほとんどのアプリケーションにもっとも効率的なファイル入出力形式です。
次のインタフェースは、ファイルとキャラクタ入出力デバイス上で基本的な操作を実行します。
表 6–1 基本的なファイル入出力インタフェース
インタフェース名 |
目的 |
---|---|
読み取りまたは書き込み用にファイルを開きます |
|
ファイル記述子を閉じます |
|
ファイルから読み取ります |
|
ファイルに書き込みます |
|
新しいファイルを作成するか、既存のファイルに上書きします |
|
ディレクトリエントリを削除します |
|
読み取りまたは書き込み用のファイルポインタを移動します |
次のコード例は、基本的なファイル入出力インタフェースの使用方法を示します。read(2) と write(2) はどちらも、現在のファイルのオフセットから指定された数を超えないバイト数を転送し、実際に転送されたバイト数が返されます。read(2) では、ファイルの終わりは戻り値が 0 になります。
#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 高度なファイル入出力インタフェース
インタフェース名 |
目的 |
---|---|
ファイルにリンクします |
|
ファイルのアクセス可能性を判断します |
|
特殊ファイルまたは通常のファイルを作成します |
|
ファイルのモードを変更します |
|
ファイルの所有者とグループを変更します |
|
ファイルのアクセス時刻や変更時刻を設定します |
|
ファイルの状態を取得します |
|
ファイル制御機能を実行します |
|
デバイスを制御します |
|
設定可能なパス名変数を取得します |
|
ディレクトリを操作します |
|
ディレクトリを作成します |
|
シンボリックリンクの値を読み取ります |
|
ファイル名を変更します |
|
ディレクトリを削除します |
|
ファイルへのシンボリックリンクを作成します |
次の表にあるファイルシステム制御インタフェースを用いて、ファイルシステムに対してさまざまな制御を行うことができます。
表 6–3 ファイルシステム制御インタフェース
インタフェース名 |
目的 |
---|---|
ファイルシステムの統計情報を取得します |
|
スーパーブロックを更新します |
|
ファイルシステムをマウントします |
|
ファイルシステム情報を取得します |
|
ファイルシステムの種類の情報を取得します |
ファイル要素をロックするために従来のファイル入出力を使用する必要はありません。マッピングされたファイルには、より軽量な同期メカニズム (『マルチスレッドのプログラミング』を参照) を使用します。
ファイルをロックすると、複数のユーザーが同時にファイルを更新しようとした場合に生じるエラーを防止できます。ファイルの一部だけもロックできます。
ファイルをロックすると、そのファイル全体へのアクセスがブロックされます。レコードをロックすると、そのファイルの指定されたセグメントへのアクセスがブロックされます。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 の設定ビットの通常の意味論も有効です。
ロックについては、次の点について注意してください。
強制ロックは、ローカルファイルだけで利用できます。NFS を介してファイルにアクセスするとき、強制ロックはサポートされません。
強制ロックは、ファイル内のロックされているセグメントだけを保護します。ファイルの残りの部分には、通常のファイルアクセス権に従ってアクセスできます。
不可分のトランザクションに多重の読み取りや書き込みが必要な場合は、入出力を開始する前に、対象となるすべてのセグメントについてプロセスが明示的にロックする必要があります。このように動作するプログラムの場合は、いずれもアドバイザリロックで十分です。
レコードロックが使用されるファイルについては、全プログラムに無制限のアクセス権を与えてはいけません。
次の表に、アドバイザリロックと強制ロックの両方がサポートされるファイルシステムの一覧を示します。
表 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) インタフェースは、次のいずれかの状況が発生するまでファイルをロックしようとします。
ファイルロックが正常に設定された。
エラーが発生した。
MAX_TRY 回数を超えたため、プログラムがファイルのロックを中止した。
#include <fcntl.h> ... 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 = (off_t)0; lck.l_len = (off_t)0; /* until the end of the file */ 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 スタイルのどちらでも使用できます。
レコードをロックする場合、ロックセグメントの開始位置と長さを 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 が使用されます。
次の例では、ファイル内でロックされているすべてのセグメントについてのデータを検索して出力します。
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) はプロセスがロックを保留しているかどうかを検査できます。このインタフェースは、ロックの位置と所有権についての情報を返しません。
(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 端末入出力インタフェース
インタフェース名 |
目的 |
---|---|
端末属性を取得または設定します |
|
回線制御インタフェースを実行します |
|
cfgetospeed(3C)、cfgetispeed(3C)、cfsetispeed(3C)、cfsetospeed(3C) |
ボーレートを取得または設定します |
端末のフォアグラウンドプロセスのグループ ID を取得または設定します |
|
端末のセッション ID を取得します |
次の例に、DEBUG 以外の操作モードにおいて、サーバーがどのようにその呼び出し元の制御端末との関連付けを解除するかを示します。
(void) close(0); (void) close(1); (void) close(2); (void) open("/", O_RDONLY); (void) dup2(0, 1); (void) dup2(0, 2); setsid();
この操作モードでは、サーバーは制御端末のプロセスグループからシグナルを受信しません。サーバーが関連付けを解除したあと、サーバーはエラーレポートを端末に送信できません。したがって、このサーバーは syslog(3C) を使用してエラーを記録する必要があります。