プログラミング第三

#include 
#include 
#include 
#include 
#include 

#define MAX_ARGS 10
#define MAX_LEN  100
#define MAX_LOG  100
#define DEFAULT_PRINT_HISTORIES 10

/* Ctrl + C */
void ctrlC(int sig){
  return;
}

/* 終了処理 */
void goodbye(){
  printf("Goodbye!\n");
  exit(0);
}

/* 実行処理 */
int process(char* argv[MAX_LOG][MAX_ARGS], int n){
  fflush(stdout);
  if(execvp*1;
    strcpy(argv[num_history(n)][i],argv[num][i]);
    printf("%s ", argv[num_history(n)][i]);
    i++;
  }
  printf("\n");
  if(i*2 == NULL){
	charfree(&argv[num_history(n)][argc]);
	break;
      }
      /* ヒストリへ書き込み */
      charfree(&argv[num_history(n)][argc]);
      argv[num_history(n)][argc] = (char*)malloc(strlen(temp));
      strcpy(argv[num_history(n)][argc], temp);

      cp = NULL;
    }

    /* 入力が何もない */
    if(argc == 0){
      continue;
    }

    /* exitコマンドで終了 */
    if(!strcmp(argv[num_history(n)][0],"exit")){
      goodbye();
    }

    /* ヒストリの表示 */
    if(!strcmp(argv[num_history(n)][0],"history")){
      print_history(argv, n, argc);
      continue;
    /* ヒストリの呼び出し */
    }else if (!strcmp(argv[num_history(n)][0],"back")){
      if*3 ==  -1){
	continue;
      }
    }

    /* バックグラウンド実行の際のフラグをたてる */
    if(!strcmp(argv[num_history(n)][argc-1],"&")){
      flagback = argv[num_history(n)][argc-1];
      argv[num_history(n)][argc-1]=NULL;
    }

    /* 実行 */
    pid = fork();
    if(pid==0){

      /* バックグラウンド実行 */
      if(flagback != NULL){
	pidback = fork();
	if(pidback==0){
	  process(argv,n);
	}else if(pidback==-1){
	  perror("fork");
	  exit(1);
	}else{
	  exit(0);
	}
      }

      process(argv,n);

      exit(0);
    }else if(pid==-1){
      perror("fork");
      exit(1);
    }else{
      wait(&status);

      /* ヒストリに&を付加 */
      if(flagback != NULL){
	argv[num_history(n)][argc-1] = flagback;
      }
    }

    ++n;
  }
}

課題2で加えた機能とその実現方法の簡単な説明

////その1 コマンド実行

コマンドの実行はfork関数によって実現される。
fork()の返り値によって、親プロセスか子プロセスかの分岐がなされ、
親プロセスは、wait関数によって子プロセス終了を待つ。
一方子プロセスは、ポインタargvに指された文字列リストを
execvp関数に与え、プロセスの変身によってコマンドの実行を行う。
execvp関数の返り値が-1であったなら、
実行に失敗したということなので、エラーメッセージを表示する。


////その2 exitで終了

入力された文字列を分解し、一つめのトークンが、
exitであるなら、シェルを終了する。
strcmp関数を用いて、文字列の比較を行うだけである。


////その3 バックグラウンド実行

入力された文字列を分解し、最後のトークンが
&であるなら、バックグラウンド実行を行うようにしている。
コマンドの後に、スペースあるいはタブを空けて&を入力する必要がある。
普通のシェルは、コマンドと&の間に空白は必要ないが、
このシェルでは簡単のためにこのような仕様にした。

さて、最後のトークンが&であるなら、
char*型のflagback変数に最後のトークンのポインタを代入している。
flagbackは通常NULLであるので、意味のあるポインタが代入されれば、
フラグが立ったということを意味する。
代わりに、元々&を指していたポインタには、NULLを代入しておく。
&がコマンドとして見なされないようにするためである。
(わざわざ最後のトークンのポインタをフラグに代入している理由は、
 後述するヒストリ機能のために、&をヒストリに戻してやる必要があるからである)

このフラグは、fork関数を実行した後、子プロセスにおいて意味を持つ。
フラグが立っていれば、子プロセスは更にfork関数を呼び出し、
孫プロセスを生成する。
子プロセスはそのままexitする。
親プロセスの動作は通常の実行と変わらず、子プロセスの終了を待つ。
従って、子プロセスのゾンビは残らない。


////その4 その他の拡張-ヒストリ機能

ヒストリ機能を実装するために、まず、ポインタargvの次元を一つ増やした。
本プログラムにおいて、argvの宣言は、char *argv[MAX_LOG][MAX_ARGS];
となっており、MAX_LOGは記録される履歴の最大数+1の数を表している。
つまり、記録される履歴の最大数は、MAX_LOG-1となる。
何故一つ分小さいかというと、コマンドの入力のために一つ分使われるからである。
これは、入力された値をそのままこのポインタが指し示すためである。

ポインタを宣言しただけでは、履歴を保存する空間は確保されない。
そのため、コマンドをstrtokでトークンに分割する際に、
mallocでトークンの大きさ分だけメモリを確保し、
strcpyで文字列をコピーすることによって、コマンドの複製を作っている。

コマンドは、ポインタargv(の一つ目の次元に対して)に順々に保存されている。
その表示の為に、historyコマンドを新しく用意した。
また、その呼び出しおよび実行の為に、backコマンドを新しく用意した。

historyコマンドは、引数を一つ取るが、
それは表示すべき履歴の数である。
引数が無かった際に表示される履歴の
デフォルトの数値は、DEFAULT_PRINT_HISTORIESによって定義されている。

backコマンドも引数を一つ取り、それは呼び出すべき履歴中のコマンドを指すが、
これはhistoryコマンドによって履歴を表示した際、
コマンドの前に付加されている数字であり、それは、
コマンドが実行された時に表示されていたcommand[x]のxの値そのものでもある。
なお、backコマンドの引数はマイナスでもよく、
その場合は、現在からいくつ前のコマンドを呼び出すか指定したことになる。
backコマンドの引数がなかった場合は、自動的に引数が-1であったと見なされる。
つまり、一つ前に実行したコマンドを呼び出す。

ちなみに、ポインタargvの一つ目の次元の数値は、
メインルーチン内のカウンタnが用いられることが多い。
nが単調増加している一方、argvは有限であり、
argvの一つ目の次元の数値は0からMAX_LOG-1の値でなければならないが、
カウンタnがMAX_LOG以上になった場合に備え、
nをMAX_LOGで割った時の余りを使用している。
つまり、キューのように、first-in-first-outの構造を取っており、
履歴は、最も昔に記録されたコマンドから順々に消滅していくことになる。


////その5 その他の拡張-CTRL+Cの処理

シェル内で実行したプログラムを
CTRL+Cを押すことによってSIGINTを送り終了させると、
シェルも同時に終了してしまう、
この問題を解決するために、signal関数を使って、
SIGINTが送出された際に何もしない関数を呼び出すようにしている。
これにより、このシェルはCTRL+Cキーを押しても終了しないようになった。


演習の感想等

C言語のメモリ管理は面倒であることを思い出しました。
cdコマンドやリダイレクトを実装できなかったのが残念です。

*1:char*)argv[num_history(n)][0],argv[num_history(n)])==-1){ printf("File not found.\n"); return -1; } return 0; } /* メモリ解放 */ void charfree(char** p){ if(*p != NULL){ free(*p); *p=NULL; } } /* ヒストリされる位置の決定 */ int num_history(int n){ while(n<0){ n += MAX_LOG; } return n % MAX_LOG; } /* ヒストリの表示 */ int print_history(char* argv[MAX_LOG][MAX_ARGS], int n, int argc){ int line, i, j; if(argc==1){ line = DEFAULT_PRINT_HISTORIES; }else{ line = atoi(argv[num_history(n)][1]); if(line<=0 || MAX_LOG<=line){ printf("Can't print %d histories.\n",line); return -1; } } for(i=n-line; i

*2:temp = strtok(cp,delim

*3:argc = call_history(argv, n, argc