ファイル・ディスクリプターの使用

ファイル・ディスクリプターは、オープン・ファイルを識別するために、 プロセスが使用する無符号の整数です。

プロセスに使用可能なファイル・ディスクリプターの数は、sys/limits.h ファイル内の /OPEN_MAX 制御によって制限されます。 また、ファイル・ディスクリプターの数は、ulimit -n フラグによっても制御されます。 openpipecreat、 および fcntl サブルーチンはすべて、 ファイル・ディスクリプターを生成します。 ファイル・ディスクリプターは一般的には各プロセスに固有ですが、 fork サブルーチンが作成する子プロセスによって共有することができます。 あるいは、fcntldup、 および dup2 サブルーチンによってコピーすることができます。

ファイル・ディスクリプターは、プロセスごとにカーネルが保守する u_block 領域内のファイル・ディスクリプター・テーブルへの索引です。 プロセスがファイル・ディスクリプターを獲得する最も一般的な方法は、 open または creat 操作を使用するか、 あるいは親プロセスからの継承によります。 fork 操作を行うと、 ディスクリプター・テーブルが子プロセス用にコピーされるので、 子プロセスは親プロセスが使用するファイルへのアクセスを同様に行うことができます。

ファイル・ディスクリプター・テーブルとシステム・オープン・ファイル・テーブル

ファイル・ディスクリプター・テーブルおよびオープン・ファイル・テーブルの構造体は、各プロセスのファイルへのアクセスをトラッキングし、データ保全性を確保します。
テーブル 説明
ファイル・ディスクリプター・テーブル テーブル内の索引番号 (ファイル・ディスクリプター) をオープン・ファイルに変換します。 ファイル・ディスクリプター・テーブルはプロセスごとに作成され、 そのプロセス用に別に設定される u_block 領域に置かれます。 ファイル・ディスクリプター・テーブル内の各エントリーには、 フラグ領域とファイル・ポインターという次のフィールドがあります。 ファイル・ディスクリプターの最大数は OPEN_MAX です。 ファイル・ディスクリプター・テーブルの構造体は次のとおりです。
struct ufd
{
        struct file *fp;
        int flags;
} *u_ufd
システム・オープン・ファイル・テーブル 各オープン・ファイル用のエントリーが入っています。 ファイル・テーブル・エントリーは、ファイルに対するすべての読み取り操作または書き込み操作によって参照される現在のオフセット、 およびファイルのオープン・モード (O_RDONLYO_WRONLY、 または O_RDWR) をトラッキングします。

オープン・ファイル・テーブル構造体には、 ファイルの現在の入出力オフセットが入ります。 システムは、現在のオフセットに対する暗黙のシークとして各読み取り/書き込み操作を処理します。 したがって、x バイトの読み取りまたは書き込みを行うと、 ポインターは x バイト先へ進みます。 lseek サブルーチンを使用すれば、 現在のオフセットを、ランダムにアクセス可能なファイル内の指定の位置へ再び割り当てることができます。 ストリーム・タイプのファイル (例えば、パイプおよびソケット) は、 ファイル内のデータがランダムにアクセス可能でないので、 オフセットを使用しません。

ファイル・ディスクリプターの管理

ファイルは多くのユーザーが共有できるので、 関連するプロセスが共通のオフセット・ポインターを共有できるようにし、 同じファイルにアクセスする独立したプロセス用の別個の現在のオフセット・ポインターを用意する必要があります。 オープン・ファイル・テーブル・エントリーは、 ファイルに割り当てられたファイル・ディスクリプターの数をトラッキングするための参照カウントを持ちます。

1 つのファイルに対する複数参照の原因として以下のものがあります。
  • 別個のプロセスが同じファイルをオープンした。
  • 親プロセスに割り当てられたファイル・ディスクリプターを子プロセスが保持している。
  • fcntl または dup サブルーチンがファイル・ディスクリプターのコピーを作成した。

オープン・ファイルの共有

各オープン操作は、システム・オープン・ファイル・テーブル・エントリーを作成します。 別々のテーブル・エントリーによって、 それぞれのプロセスが必ず別個の現在の入出力オフセットを持つようになります。 独立したオフセットによって、データの保全性が保護されます。

ファイル・ディスクリプターを複製すると、2 つのプロセスは同じオフセットを共有するようになるので、 インターリービングが発生する可能性があります。この場合、バイトは順次の読み取りも書き込みも行われません。

ファイル・ディスクリプターの複製

プロセス間でファイル・ディスクリプターを複製する方法には、dup または dup2 サブルーチン、fork サブルーチン、 および fcntl (ファイル・ディスクリプター制御) サブルーチンがあります。

dup および dup2 サブルーチン

dup サブルーチンは、ファイル・ディスクリプターのコピーを作成します。 複製は、元のディスクリプターが入っているユーザー・ファイル・ディスクリプター・テーブル内の空きスペースに作成されます。 dup プロセスは、ファイル・テーブル・エントリー内の参照カウントを 1 だけ増加させ、 コピーが置かれたファイル・ディスクリプターの索引番号を戻します。

dup2 サブルーチンは、要求されたディスクリプター割り当てをスキャンし、要求されたファイル・ディスクリプターがオープンされていた場合はクローズします。 このサブルーチンによって、 特定のディスクリプター・テーブル・エントリーが必要な場合、 プロセスは、コピーが占有するディスクリプター・エントリーを指定することができます。

fork サブルーチン
fork サブルーチンは、親プロセスに割り当てられているファイル・ディスクリプターを受け継ぐ子プロセスを作成します。 次に、子プロセスは新しいプロセスを実行します。 受け継がれたディスクリプターで、 fcntl サブルーチンによって close-on-exec フラグが設定されているものはクローズします。

 

fcntl (ファイル・ディスクリプター制御) サブルーチン
fcntl サブルーチンは、ファイル構造体を操作し、オープン・ファイル・ディスクリプターを制御します。 このサブルーチンは、 ディスクリプターに対する以下の変更を行うために使用することができます。
  • ファイル・ディスクリプターを複製します (dup サブルーチンと同じです)。
  • close-on-exec フラグを獲得または設定します。
  • ディスクリプターに関する非ブロッキング・モードを設定します。
  • ファイルの終わりへの将来の書き込みを付加します (O_APPEND)。
  • 入出力の実行が可能な場合のプロセスに対するシグナルの生成を可能にします。
  • SIGIO 処理に関するプロセス ID またはグループ・プロセス ID を設定または獲得します。
  • すべてのファイル・ディスクリプターをクローズします。

ファイル・ディスクリプターの値の事前設定

シェルは、プログラムを実行する場合、 ファイル・ディスクリプター 0、1、および 2 を使用して 3 つのファイルをオープンします。 これらのディスクリプターのデフォルトの割り当ては次のとおりです。

ディスクリプター 説明
0 標準入力を表します。
1 標準出力を表します。
2 標準エラーを表します。

これらのデフォルトのファイル・ディスクリプターが端末に接続されるので、 プログラムがファイル・ディスクリプター 0 を読み取り、 ファイル・ディスクリプター 1 および 2 を書き込む場合、 プログラムは端末から入力を収集し、端末へ出力を送信します。 プログラムが他のファイルを使用するときに、 ファイル・ディスクリプターは昇順で割り当てられます。

< (より小) または > (より大) 記号を使用して入出力をリダイレクトすると、シェルのデフォルトのファイル・ディスクリプター割り当てが変更されます。 例えば、ファイル・ディスクリプター 0 および 1 のデフォルトの割り当てを、端末から適切なファイルへ変更するには、次のようにします。
prog < FileX > FileY

この例で、ファイル・ディスクリプター 0 は現在 FileX を参照し、 ファイル・ディスクリプター 1 は FileY を参照しています。 ファイル・ディスクリプター 2 は変更されていません。 ファイル・ディスクリプター 0 が入力ファイルを表し、 ファイル・ディスクリプター 1 と 2 が出力ファイルを表している限り、 プログラムは入力元または出力先を知る必要がありません。

次のサンプル・プログラムは、標準出力のリダイレクトを示しています。
#include <fcntl.h>
#include <stdio.h>

void redirect_stdout(char *);

main()
{
       printf("Hello world¥n");       /*this printf goes to
                                      * standard output*/
       fflush(stdout);
       redirect_stdout("foo");        /*redirect standard output*/
       printf("Hello to you too, foo¥n");
                                      /*printf goes to file foo */
       fflush(stdout);
}

void
redirect_stdout(char *filename)
{
        int fd;
        if ((fd = open(filename,O_CREAT|O_WRONLY,0666)) < 0)
                                        /*open a new file */
        {
                perror(filename);
                exit(1);
        }
        close(1);                       /*close old */
                                        *standard output*/
        if (dup(fd) !=1)                /*dup new fd to
                                        *standard input*/
        {
                fprintf(stderr,"Unexpected dup failure¥n");
                exit(1);
        }
        close(fd);                       /*close original, new fd,*/
                                         * no longer needed*/
}

ファイル・ディスクリプター・テーブル・ファイルで、ファイル・ディスクリプター番号は、 ディスクリプターが要求された時点で使用可能な最も小さいディスクリプター番号が割り当てられます。 ただし、dup サブルーチンを使用すれば、 ファイル・ディスクリプター・テーブルで任意の値を割り当てることができます。

ファイル・ディスクリプターのリソース制限

プロセスに割り当てることができるファイル・ディスクリプターの数は、 リソースの制限によって制御されます。 デフォルト値は /etc/security/limits ファイルで設定され、一般的には 2000 に設定されます。 この制限は、ulimit コマンドまたは setrlimit サブルーチンによって変更することができます。 最大サイズは定数 OPEN_MAX によって定義されます。