プログラミング第三
#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