ファイル入出力命令を書いてみる

今回はC言語UNIXシステムコールを動作させ、Cのライブラリー関数の一つであるファイル入出力命令をオリジナルで実装してみる。

OSの機能のうちで、最も基本なのは入出力である。
これを担うシステムコールがwrite / read システムコールである。
詳細の説明は省くが、これらのシステムコールは共通して3つの引数を保持しており

第一引数がファイルディスクリプタ
第二引数が入出力される文字を格納するバッファ
第三引数が入出力される文字数

である。
以下は単純な標準出力をするプログラム(printf関数の実態?)

#include <stdio.h>
#include <stdlib.h>

main(){
 write(1,"I am a student.\n"16);
 write(2,"I major in computer science.\n",29);
 exit(0);
}

標準入出力の場合は予め、0,1,2というファイルディスクリプタが用意されているのだが
今回はファイルへの入出力を実装するため、
ファイルディスクリプタを少々いじる必要がある。

そこで使うのがopen / close システムコールである。
openシステムコールによって入出力するファイル・ディスクリプタを得て
それを参照して入出力を行い、ファイル・ディスクリプタが不要になった時点で
closeシステムコールによって削除する。

以下は単純な、指定サイズのファイルを作成するプログラム。
第一引数がサイズ、第二引数がファイル名である。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#define BSIZE 512
char buf[BSIZE];

main(int argc,**char argv){
 int fd,size;
 if(argc != 3){
  fprintf(stderr,"usage: %s size filename\n",argv[0]);
  exit(1);
 }

 size = atoi(argv[1]);
 fd = open(argv[2],O_WRONLY|O_CREAT_O_TRUNC.0644);

 printf("size = %d,fd = %d\n",size.fd);

 if(fd < 0){
  perror(argv[2]);
  exit(1);
 }

 mkfile(fd,size);

 close(fd);
 exit(0);
}

mkfile(int fd,int size){
 while(size > BSIZE){
  write(fd,buf,BSIZE);
  size -= BSIZE;
 }

 write(fd,buf,size);
}

これによってopenとcloseの役割が分かる。
さて、これをもとにしてUNIXのcpコマンドを実行するプログラムを作ってみた。
以下、引数は実際のcpコマンドと同じだが
今回はシステムコールを用いた場合と標準入出力ライブラリ関数を用いた場合を比較するため
そのモード変換スイッチとして第三引数に0 or 1を入れるものとした。

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

#define BSIZE 154714

char buf[BSIZE];


main(int argc,char **argv){
  /*argment error*/
  if(argc != 4){
    printf("argment error\n");
    exit(1);
  }

  if(atoi(argv[3])){ /*system call mode*/
    int fd;
    printf("## SYSTEM CALL MODE ##\n");

    /*get input file discripter*/
    fd = open(argv[1],O_RDONLY);

    /*file discripter error*/
    if(fd < 0){
      perror(argv[1]);
      exit(1);
    }

    /*read file*/
    read(fd,buf,BSIZE);
    close(fd);

    /*get output file  discripter*/
    fd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC);

    /*file discripter error*/
    if(fd < 0){
      perror(argv[2]);
      exit(1);
    }

    /*write file*/
    write(fd,buf,BSIZE);
    close(fd);
  } else { /*defoult library mode*/
    FILE *input,*output;
    printf("## DEFOULT LIBRARY MODE ##\n");
    if((input = fopen(argv[1],"r")) == NULL){
      printf("input argument error\n");
      exit(0);
    }

    if((output = fopen(argv[2],"w")) == NULL){
      printf("output argment error\n");
      exit(0);
    }

    char c;
    int n = BSIZE;
    while(n--){
      c = fgetc(input);
      fputc(c,output);
    }

    fclose(input);
    fclose(output);
  }
  printf("buffer size: %d\n",BSIZE);
  exit(0);
}

さて、このプログラムをシステムコールモードで実行すると
実際にcpコマンドと同様の動作が確認できたが
この処理をライブラリ関数を使って行うとどうなるか。

timeコマンドを使って処理速度を比較してみると
読み込みバッファが大きければ大きいほど
ライブラリ関数モードのほうが処理に時間がかかることが分かった。

これについての考察はあえて割愛するが
やっぱりプログラミングは奥が深い。
OSについてもっと学びたいなぁ〜。