バックグラウンド実行機能の追加
本日の内容
コマンド実行ループ
環境変数の受け渡し
バックグラウンド実行機能の追加
exitコマンドの実装
コマンド実行ループ
前回作ったコマンド入力を受け取ってそれを実行するプログラムのmain関数内部に無限ループを追加することで実現できます.
ここまでの内容をフローチャートで表すとこんな感じです
https://gyazo.com/0dcba6ee24ad7b56f678185f24428e15
ここまでのプログラム(エラー処理やインクルードは一部省略)
code:read_cmd.c
char **read_cmd(int *argc) {
const int BUFSIZE = 1024;
const char TOK_DELIM[] = " \t\r\n\a";
int arg_cnt = 0, max_argc = 10;
char *input = malloc(sizeof(char) * BUFSIZE);
char **argv = malloc(max_argc * sizeof(char *));
// 標準入力からコマンドを読む
printf("rcsh> ");
if (fgets(input, BUFSIZE, stdin) == NULL) {
return NULL;
}
// パース処理
char *word = strtok(input, TOK_DELIM);
while ((word = strtok(NULL, TOK_DELIM))) {
}
*argc = arg_cnt;
return argv;
}
void free_argv(char **argv) {
free(*argv);
free(argv);
}
code:rcsh.c
int main(void) {
int argc;
while(1) {
char* cmd[] = read_cmd(&argc);
int pid = fork(); // 本来はintではなくsys/types.hのpid_tを用いる
if(pid == 0) {
puts("I am child proc.");
exit(0);
}
int status;
wait(&status);
printf("Child process exited with status %d\n", WEXITSTATUS(status));
free_argv(cmd);
}
return 0;
}
環境変数の受け渡し
現在のrcshは実行するプログラムのパスを渡すようになっています.
したがってコマンド名だけでプログラムを実行するにはPATH変数を作り,コマンド名のファイルが存在するか検索する必要があります.
getenv("PATH");で内容を取ってこれるのでそれをstrtokでパースしてそれぞれのディレクトリに対してstat関数で通常ファイルが存在しているかを判別していくことでそれっぽい挙動を実装できます.
残念ながらC言語班の活動内でそれをやろうとするとそれだけで時間が終わってしまうので今回はexecv関数をexecvp関数に変更することで対応したいと思います.
この関数は渡された第一引数に/が含まれていない場合現在のPATH変数を読んで検索し,実行してくれます.与える引数はexecvと変わりません.
code:rcsh.c
if (pid == 0) {
puts("I am child proc");
perror("rcsh: ");
exit(1);
}
バックグラウンド実行機能の追加
現在のrcshはプログラムをフォアグラウンド実行することしかできないので,コマンドの後に&をつけることでバックグラウンド実行できるようにします.
今後;を実装する可能性もありますが,今回は特に何も考えず入力文字列に&が含まれていればバックグラウンド実行を行うという単純な実装でやっていきましょう.
read_cmd関数内でstrtokの前にstrchrで&を検索するのが早いですね.strchrは第一引数から第二引数の文字を検索し,発見したらそのポインタを返します.引数の&文字は見つかり次第消去しないと実行するコマンドの引数として渡されてしまいます.
bool型を使う場合は忘れずにstdbool.hをインクルードしておきましょう.
code:read_cmd.c
char **read_cmd(int *argc, bool *exec_bg) {
// 省略
// バックグラウンド実行のフラグを設定
// 同時に引数から&記号を削除
char *pos_and;
if ((pos_and = strchr(input, '&')) != NULL) {
*exec_bg = true;
*pos_and = '\0';
}
これでバックグラウンド実行を行うかどうかのフラグを作ることができたので,親プロセス側で条件分岐していきます.
フラグがfalseの場合は今まで通りの処理を行いますが,trueの場合は,waitを行いません.(行えばフォアグラウンド処理になってしまうので.)
注意点として,&がそのままコマンドの引数として渡されると困るのでargv[argc] = NULLを行う前にargcをデクリメントしておきましょう.
code:rcsh.c
int main(void) {
int argc;
bool exec_bg;
while(1) {
char* cmd[] = read_cmd(&argc);
// 中略
int status;
if(!exec_bg) {
wait(&status);
}
free_argv(cmd);
}
}
バックグラウンドプロセスの数をグローバル変数に保存してそれっぽい情報を表示できるようにしましょう.
int proc_cnt = 0;を追加して親プロセス側でこれをインクリメントし,バックグラウンド実行時に表示します.
code:rcsh.c
int status;
if (exec_bg) {
printf("%d %d\n", ++proc_cnt, pid); } else {
wait(&status);
printf("Child process exited with status %d\n",
WEXITSTATUS(status));
}
exitコマンドの実装
組み込みコマンドとしてまずexitを実装しましょう.
入力がexitであればループを抜けて終了するだけなので簡単です.
forkする直前に判定してmain関数から抜けます.
(普通のShellでexitコマンドをバックグラウンド実行するとどうなるかは分からなかったので実装していません.)
code:rcsh.c
// exitコマンド
if (strcmp(argv0, "exit") == 0) { if (argc > 2) {
fprintf(stderr, "exit: too many arguments\n");
continue;
}
if (argc == 2) {
}
return 0;
}