parseProcessとパイプ記号を解析するparseTermを呼び出すことにより自身の機能を実現している。
ParseResult parseJob(char* pos){
char* bp = pos;
ParseResult s,res;
/* 最初のProcessを解析する。 */
if(PARSEFAILED(s = parseProcess(pos))){
PARSEERROR(s);
}
/* Job構造体を生成する。 */
res.u.job = allocJob(s.u.proc);
/* パイプで結ばれたProcessの並びを解析する。 */
while(1){
pos = s.lastpos;
if(PARSEFAILED(s = parseTerm(s.lastpos, PIPE)))
break;
if(PARSEFAILED(s = parseProcess(s.lastpos))){
/* parse error */
freeJob(res.u.job);
PARSEERROR(res);
}
addProcessToJob(res.u.job, s.u.proc);
}
res.lastpos = pos;
res.u.job->text = ystrndup(bp,pos - bp);
PARSESUCCESS(res);
}
このコード例から分かるように、各解析関数はPARSESUCCESSまたはPARSEERRORマクロにより上位の解析関数に解析の成功または失敗を通知し、呼び出し側はPARSEFAILEDマクロによって下位の解析関数の解析の成否を判別している。
実行
Command,Job,Processのデータ構造に対応してそれらの実行を受け持つ関数
execCommand,execJob,execProcess
を実装している。
パイプ/リダイレクトの処理はおもにexecProcess中でおこなっている。
ジョブ制御
ジョブ制御に関しては、現時点ではバックグラウンドジョブが終了した際に状態を表示する機能しか実装していない。ジョブの停止/再開は実装していない。
- フォアグラウンドジョブを実行する場合
- 各子プロセスをforkした直後に、以下のコードのように、それらに対して
waitpidを呼び出し、すべてが終了するのを待っている。
/* フォアグラウンド実行の場合,Jobに含まれるすべてのProcessが終了するまで待つ。 */
if(0 == job->bg){
item = job->ps->top;
while(item){
proc = (Process*)item->data;
if(proc->pid){
waitpid(proc->pid,&status,0);
/* 子プロセスが終了ではなく停止した場合はどうする? */
}
item = item->next;
}
/* 実行中ジョブリストから削除する。 */
removeItemFromList(ActiveJobs,jitem,(void (*)(void*))freeJob);
}
- バックグラウンドジョブを実行する場合
- コマンドライン入力を受け付ける度に、
checkBGJobStatusを呼び出し、新たに終了したバックグラウンドジョブがないかをチェックしている。終了したジョブを検出した場合、その情報をリストに追加しておき、コマンド入力受付直後に出力している。
while(0 < (childpid = waitpid(-1,&childstatus, WNOHANG))){
/* 終了した子プロセスに対応するProcess構造体を探す。 */
(略)
if(proc->pid == childpid){
if(WIFEXITED(childstatus) || WIFSIGNALED(childstatus)){
/*
プロセスが終了した場合
*/
proc->pid = 0;
if(0 == --job->activechildren){
/*
Job終了の通知メッセージをポストする。
*/
notifyJobStatusChange(job);
}
}
}
}
ユーザインターフェイス
getInput関数においてコマンド入力を受け付ける。入力を受け取った後、checkBGJobStatusを呼び出して状態が変化したジョブの検出を試み、printJobStatusChangeを呼び出してその情報を出力する。
組み込みコマンド
組み込みコマンドを、以下のようなコマンド名と関数ポインタを関連づけるテーブルによって管理している。
typedef void (*BuiltInFunc)(StrVec,FILE*,FILE*,FILE*);
typedef struct t_BuiltIn BuiltIn;
struct t_BuiltIn{
char* name;
BuiltInFunc func;
} ;
/* 組み込みコマンドのテーブル */
BuiltIn BuiltIns[] = {{"exit",builtinExit},
{"jobs",builtinJobs},
{"export",builtinExport},
{"printenv",builtinPrintEnv},
{"cd",builtinCd},
{NULL,NULL}};
組み込みコマンドを実装する関数はいずれも
void builtinCOMMANDNAME(StrVec,FILE*,FILE*,FILE*)
というシグネチャをもつ。StrVecはchar**をtypedefした別名である。三つのFILE*は、標準入力、標準出力、標準エラー出力として使用するファイルが指定される。
たとえばcdコマンドを実装するbuiltinCdは以下のように実装している。
void builtinCd(StrVec args,FILE* infile,FILE* outfile,FILE* errfile){
char* target;
if(args[1]){
target = args[1];
}else{
target = getenv("HOME");
}
if(chdir(target)){
perror("");
}
}
参考
Linux Debian パッケージに含まれる各種シェルのソース(とくにkiss)
『プログラミングLinux』(アスキー)