システムプログラミング 第7回、8回 ファイルシステム関連の システムコール 情報工学科 篠埜 功
ディレクトリファイル ディレクトリファイルには、ファイル名からiノード番号(iノードはindex nodeの略)への対応がファイルの数だけ格納されている。(実際のデータ構造は、ハッシュテーブルや線形リストなど。) ファイルのiノード番号は $ ls –li 等、lsに-iオプションを与えると確認できる。
ファイルシステムの実装 ファイルシステムは、ブートブロック、スーパーブロック、iノードリスト、データブロックの4つの領域から構成される。 ブートブロック --- OSを起動するためのプログラムを格納。 スーパーブロック --- ファイルシステムの大きさ、ブロック数、ブロックサイズ、空きブロック、iノードリストの大きさ、空きiノードリストの情報などを格納。 iノードリスト --- iノードのリスト。iノードには各ファイルの種類、所有者、permission、変更時刻、ファイルサイズ、データブロック内の場所などが格納されている。 データブロック --- ファイルの中身が格納されている。
openシステムコール ファイルからのデータの読み込み、ファイルへのデータの書き込みをするには、まずファイルをオープンする。これを行うのがopenシステムコールである。 openシステムコールを使う場合、types.h, stat.h, fcntl.hをインクルードする。
openシステムコールの引数 openシステムコールは、パス名、フラグの2引数あるいは、これらにモードを加えた3引数で呼び出す。 パス名で指定されたファイルを、フラグに従ってオープンし、ファイル記述子(file descriptor, int型)を返す。フラグがO_CREATの場合、モードが必要。 ファイル記述子は、利用者ファイル記述子表の 何番目かを表す。 オープンするとは、データ入出力用のバッファを確保し、利用者ファイル記述子表中の1つの構造体を割り当て、構造体の各メンバーに初期値を設定することをいう。 利用者ファイル記述子表についてはdupシステムコールの説明時に説明する。
openシステムコールの代表的なフラグ O_RDONLY --- 読みだしのみ O_WRONLY --- 書き込みのみ O_RDWR --- 読み出し、書き込みの両方を行う O_CREAT --- ファイルが存在しない場合作成する。第3引数のモードでファイルのpermission等を設定する。 これらのフラグはfcntl.hに記述されているので、インクルードして使う。
モード openシステムコールの第3引数に与えられるモードでは、ファイルのpermissionおよびセットユーザIDビット、セットグループIDビット、stickyビット(/tmpなどで使用)を12桁の2進数で表す。代表的な数値はstat.hでマクロとして提供されているが、数値で直接指定してよい。 (例)S_IRWXU --- 所有者はread, write, executeができる。 その他にもあるが、 $ man –S 2 open で確認。
readシステムコール ファイルからデータを読み出すためのシステムコールがreadシステムコールである。 unistd.hをインクルードする。 ファイル記述子、データ格納領域へのポインタ、読み出しバイト数を引数に与える。返り値は読み出したバイト数。これが0のときはファイルの最後まで読み終わっているということになる。 readシステムコールが正常終了しなかった場合は-1が返ってくる。このときはperrorでエラーメッセージを表示する。
writeシステムコール ファイルへデータを書き込むためのシステムコールがwriteシステムコールである。 unistd.hをインクルードする。 ファイル記述子、書き込むデータが格納されている領域へのポインタ、書き込みバイト数を引数に与える。返り値は書きこんだバイト数。 writeシステムコールが正常終了しなかった場合は-1が返ってくる。このときはperrorでエラーメッセージを表示する。
closeシステムコール closeシステムは、ファイル記述子を引数に受け取り、そのファイルをクローズする。 クローズするとは、入出力用バッファを解放し、利用者記述子表内の構造体を解放することをいう。 (同時に開けるファイル数に制限があるので、閉じるのがよい。閉じなければプロセス終了時に閉じられる。)
例1:テキストファイルの先頭にaを書き込む #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main (void) { int fd, n; char c = 'a'; if ((fd = open ("test", O_WRONLY)) == -1) { perror ("open"); exit(1); } /* 続き */ if (write (fd, &c, 1) != 1){ perror ("write"); exit(1); } if (close (fd) == -1) { perror ("close"); return 0;
演習課題2 テキストファイル(ファイル名はtestなど)の先頭文字を読み取り、その文字を2文字目に書きこむ。Openシステムコールの第2引数(フラグ)はO_RDWRにする。 read, writeシステムコールを呼ぶたびに、読み書きのためのポインタ(システムコール内部のポインタ)が1つ進むので、readで読み取ったあとにwriteで書き込めば、2文字目に書きこまれることになる。
例2 /* 続き */ if ((fd = open (argv[1], O_RDONLY)) == -1) { perror ("open"); exit(1); } while ((n = read (fd, &c, 1) ) > 0) printf ("%c", c); if (n==-1) { perror ("read"); if (close (fd) == -1) { perror ("close"); return 0; /* テキストファイル全部表示 */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> int main (int argc, char * argv []) { int fd, n; char c; if (argc!=2) { fprintf (stderr, "Usage: %s filename\n", argv[0]); exit(1); }
複数バイトずつ読み込む readシステムコールで、複数バイト単位で読み込むこともできる。 readシステムコールの返り値は、 (1) 正の場合、読み込んだバイト数を表す。 (2) 0 の場合、ファイルの内容を既に全部読み終わっていたことを表す。 (3) -1の場合、システムコールが何らかの理由で正常終了しなかったことを表す。この場合はライブラリ関数perrorでエラー内容を表示するべき。 例えば、ファイルサイズ260バイトのファイルを100バイト単位で読み込むと、最後の回は60バイト(返り値も60)になり、その後は返り値は0となる。
例3 /* 続き */ if ((fd = open (argv[1], O_RDONLY)) /* テキストファイル全部表示 */ == -1) { perror ("open"); exit(1); } while ((n = read (fd, c, 100) ) > 0) if (write (1, c, n) != n) { perror ("write"); exit(1); }; if (n == -1) { perror ("read"); if (close (fd) == -1) { perror ("close"); return 0; } /* テキストファイル全部表示 */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main (int argc, char * argv []) { int fd, n; char c[100]; if (argc!=2) { fprintf (stderr, "Usage: %s filename\n", argv[0]); exit(1); } 1は標準出力
例4 /* 標準入力を標準出力へ書きだす*/ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> int main (int argc, char * argv []) { int fd, n; char c[100]; while ((n = read (0, c, 100) ) > 0) if (write (1, c, n) != n) { perror ("write"); exit(1); }; /* 続き */ if (n == -1) { perror ("read"); exit(1); } return 0; 0は標準入力 1は標準出力
新しいファイルの作成 新しいファイルの作成は、openシステムコールの第2引数のフラグにO_CREATを指定する。(ファイルが存在していたらそのファイルを使う。存在しなければファイルを新しく作る。) 他のフラグと組み合わせて指定できる。組み合わせるときはビットのor演算子|を用いる。 例えば、write onlyで開きたい場合は、openシステムコールの第2引数に O_WRONLY | O_CREAT を指定する。さらに、存在しているときに内容を消したいときは O_TRUNCをさらに追加で指定する。
例5 /* 続き */ if ((fd = open (argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) { perror ("open"); exit(1); } while ((n = read (0, c, 100) ) > 0) if (write (fd, c, n) != n) { perror ("write"); exit(1); }; if (n == -1) { perror ("read"); if (close (fd) == -1) { perror ("close"); return 0; } /* 入力をファイルへ書きだす*/ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> int main (int argc, char * argv []) { int fd, n; char c[100]; if (argc!=2) { fprintf (stderr, "Usage: %s filename\n", argv[0]); exit(1); } 0は標準入力
演習課題3 実行ファイルの引数に2つのファイル名を受けとり、1つ目のファイル(存在するファイル)のコピーを2つ目のファイルに作成するプログラムを作成せよ。(コピーコマンド) コピー先ファイルのopen時の引数 第2引数 --- O_WRONLY | O_CREAT | O_TRUNC 第3引数 --- 0644(8進表記) とせよ。これにより、コピー先のファイル名が存在していたら、内容が消去されてから書き込まれる。
レポート課題3 catコマンドの以下の機能を、システムコール(open, close, read, write)を使って実装せよ。 引数無しの場合 --- 標準入力を標準出力へコピー 引数がある場合(1個以上のファイル名を引数にとる) --- それらのファイルの内容を結合したものを標準出力に書き出す。 catコマンドを使った場合と挙動を比較し、同じであることを確認したのち提出すること。
レポートの提出方法 □ 下記のファイルを作成し、提出 kadai3.c, kadai3.txt □ 提出方法 システムプログラミング講義用の課題提出用フォルダ内にあるkadai3というフォルダの中に自分の学籍番号を名前とするフォルダを作成し、その中に上記ファイルを置く。kadai3.txt内に学籍番号、氏名、日付、および作成したプログラムの簡単な説明を記載する。 □ 提出期限 12月7日の講義開始時間まで。締め切り後に提出した場合、成績への反映を保証しない。
参考: ライブラリ関数を使った場合 int main (int argc, char * argv []) { FILE *fp; if (argc==1) filecopy (stdin, stdout); else while (--argc > 0) if ((fp = fopen (*++argv, "r")) == NULL) { printf ("cat: can't open %s\n", *argv); return 1; } else { filecopy (fp, stdout); fclose (fp); } return 0; #include <stdio.h> void filecopy (FILE *fpin, FILE *fpout) { int c; while ((c = getc(fpin)) != EOF) putc (c, fpout); } この実装では1バイトずつコピーしている。複数バイトまとめて読み込んで書き込んだ方が速い。