12日目 14日目

13日目

目標

課題15

ファイルディスクリプタ (低水準入出力)

ここまでで、プロセスと外部とのやり取りには FILE 構造体を用いて fputc, fgetc, fprintf, fscanf などを用いてきた。 (標準入出力を利用する printf等も同様である。) これらの関数は「高水準入出力関数」ともよばれ、 入出力はいったん OS 等が管理するメモリ領域におかれたあと、 プログラムによって指定されたメモリ領域にコピーされる。 これらの関数は OS 等が異なっても同様に使えるよう互換性に配慮されたものとなっている。

一方、実際のデータの入出力には「低水準入出力関数」が用いられる。

低水準入出力関数ではプロセスと外部とのやり取りには 「ファイルディスクリプタ」(file descriptor)とよばれるものを通じて行われる。 ファイルディスクリプタは通常int型の0以上の整数で表される。 プログラムが起動した際には 0 が標準入力、1 が標準出力、2 が標準エラー出力である。 明示、非明示を問わず、オープンしたファイルに対しては、 すでに割り当てられていない0以上の整数のうち、 最小のものがディスクリプタとして割り当てられる。 低水準入出力関数における、ファイルのオープン/クローズには open/closeを用いる。 また、読み書きには、read/write (等)を用いる。

低水準入出力関数を使うと、高水準入出力関数よりも細かな設定/作業が可能になるが、 その分プログラムを作成する際に行わなければならない事が多くなる。

低水準入出力関数は、OS に依存する部分が含まれている可能性があるため、 異なる OS へプログラムを移植する際、なんらかの対応を施さなければならない場合があり、 「高水準入出力関数」が使える場合、あまり使うべきではない。 以下の解説は POSIX.1 に準拠したものである。

open

ファイルを開くために用いる。 引数は、ファイル名(path)、モード、ファイルを作成する場合の属性である。

  int open(char *path, int flags, mode_t mode);

フラグには、次の三つの中から一つを指定しなければならない。

上記の(基本) flag に加えて、次のような flag を組み合わせることが可能である。

このほかにも処理系によって flag が用意されていることがあるので、必要な場合処理系のマニュアルを参照すること。

flag を組み合わせるためには or 演算子「|」を用いる。 論理和演算子「||」に似ているが、論理式ではなく数値の各bitごとの論理和をとる。

第3引数の mode は、ファイルが存在せず作成する場合の許可属性となっている。 0644(8進数である) を指定していれば、たいていの場合問題ない。

ファイルが open できた場合、返り値はファイルディスクリプタとなる。 open できなかった場合は返り値は -1 となり、errno にエラーコードが設定される。

close

指定されたファイルディスクリプタを close する

  int close(int fd);

指定されたディスクリプタが close できた場合、返り値は 0 となる。 close できなかった場合、 返り値は -1 となり、errno にエラーコードが設定される。

read

指定されたファイルディスクリプタからデータを読み込む。 引数は、ファイルディスクリプタ、読み込み先、読み込む(最大)サイズである。

  ssize_t read(int d, void *buf, size_t count);

読み込みが成功した場合、返り値には実際に読み込めたデータのサイズとなる。 ファイル終端にかかった場合は、count より少なくなる。 特に返り値が 0 になるのはファイル終端に達してから read を行った場合となる。 エラーが起きた場合、返り値は -1 となり、errno にエラーコードが設定される。

write

指定されたファイルディスクリプタにデータを書き込む。 引数は、ファイルディスクリプタ、書き込み元データ、書き込むサイズである。

  ssize_t write(int d, void *buf, size_t count);

書き込みができた場合、返り値には実際に書き込んだデータのサイズが返る。 書き込み先により、書き込もうとしたサイズと実際に書き込んだサイズが異なる場合がある(たとえばディスクがいっぱいになった場合)ことに注意。 書き込みができなかった場合、返り値は -1 となり、errno にエラーコードが設定される。

fdopen

fdopen関数を用いると、すでに存在するファイルディスクリプタから 高水準入出力関数で用いられるファイル構造体を得る事ができる。

  FILE * fdopen(int fd, char *mode);

モードは fopen の際のモードと同じであるが、元となるファイルディスクリプタを得たときの open と同じ意味のモードでなければならない。

fileno

fileno関数を用いると、すでに存在するファイル構造体から、それに対応するファイルディスクリプタを得ることができる。

  int fileno(FILE *stream);

strerror

上ででてきた「エラーの場合...」に設定される errno とは、C の標準ライブラリであらかじめ用意されている変数である。 しかしながら、番号では何が起きているか人間にはわかりにくいので、エラーコードを対応する「エラーメッセージ」に変換して表示した方が親切である。

  #include <string.h>
  char *strerror(int errno);

実際に低水準入出力関数を用いて、ファイルをコピーするプログラムを作成すると次のようになる。

/* copy.c */
#include <stdio.h>
#include <fcntl.h>    /* for open() */
#include <unistd.h>   /* for read(), write(), close() */
#include <string.h>   /* for strlen(), strerror() */
#include <stdlib.h>   /* exit() */
#include <errno.h>


int main(int argc, char *argv[])
{
	int fd1, fd2;
	int size_r, size_w;
	char buf[256];
	char *msg;

	if (argc != 3) {
		fprintf(stderr, "使い方: %s コピー元 コピー先\n");
		exit(1);
	}

	/* O_RDONLY:  読み込みモード */
	fd1 = open(argv[1], O_RDONLY, 0666);
	if (fd1 < 0) {
		/* open できなかった場合、返り値は -1 となり、
		 * エラーの理由が errno にセットされる。
		 */
		msg = strerror(errno); /* errno に設定された「エラーコード」を
					* 「メッセージ」に変換する */

		fprintf(stderr, "コピー元ファイルが開けません: %s\n", msg);
		exit (1);
	}

	/* O_WRONLY:   書き込みモード
	 * O_CREAT:    ファイルがなければ作成する
	 * O_TRUNC:    ファイルサイズを 0 にする
	 */
	fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
	if (fd2 < 0) {
		msg = strerror(errno);
		fprintf(stderr, "コピー先ファイルが開けません: %s\n", msg);
		exit (1);
	}
	while(1) {
		size_r = read(fd1, buf, sizeof(buf));
		if (size_r == 0) {
			/* ファイルの終端 */
			break;
		} else if (size_r < 0) {
			/* エラー発生 */
			fprintf(stderr, "read error: %s\n", strerror(errno));
			exit(1);
		}

		/* read で実際に読めただけ、write する */
		size_w = write(fd2, buf, size_r);
		if (size_w < 0) {
			/* エラー発生 */
			fprintf(stderr, "write error: %s\n", strerror(errno));
			exit(1);
		}
	}
	close(fd1);
	close(fd2);
}

TCP/IP ネットワークの仕組みに関する資料は WebCT に入れてあります。


12日目 表紙 14日目

tacha@tack.fukui-med.ac.jp
$Id$