一つ目「free_node()」内
110 free_node(d)//作成したノードの解放...
111 node *d;
112 {
113 int i;
114 if(SHELL_DEBUG) for(i=3;i<32;i++) close(i); <--ここ
115 if(d->left) {
マクロSHELL_DEBUGによって制御される。標準入力、標準出力、エラー出力の三つ
以外のファイルディスクリプタを全て閉じる。デバッグ用の処理と思われ、
デフォルトでSHELL_DEBUGは「0」なので基本的に機能していない。
よってこれを消しても通常実行時に影響は無い。
SHELL_DEBUGが「1」の時のみ、最終的に閉じられていないファイルディスクリプタがあれば、
それが蓄積する可能性がある。
二つ目「command()内その1」
142 case '|':
143 /* パイプの場合。
144 * パイプを用意し、fork()で子プロセスを作る。*/
145 pipe(pfd);
146 if((pid = fork()) != 0){
147 save_in = dup(0);
148 dup2(pfd[0],0);
149 close(pfd[1]); <--ここ
150 command(d->right);
課題に関連するclose()である。
パイプを作成した後、親プロセスがパイプへ書き込む側のディスクリプタを閉じる。
このclose()を消去するとパイプの書き込み側ディスクリプタが一つ余ってしまう。
そうするとパイプの読み出し側ディスクリプターからはEOFを見つける事ができない。
読み出している側がEOFを見つける事で動作するプログラムの場合(less、cat等)、
この事で動作がフリーズ(readがずっとEOF待ちをしている状態)してしまう。
三つ目「command()内その2」
158 }else{//子処理
159 if(SHELL_DEBUG) fprintf(stderr,"forked pid %d\n",getpid());
160 dup2(pfd[1],1);
161 close(pfd[0]); <--ここ
162 command(d->left);
二つ目のclose()と役割が似ており、
子プロセスが親プロセスから受け継いだパイプへのディスクリプタのうち、
読み込む側のディスクリプタを閉じる。
このclose()を消去すると読み込む側ディスクリプタが一つ余ってしまう。
もし、パイプへ書き込む側のプログラム(実際は同じプロセス)が、
パイプが読み出し側が0になるとwrite()に返すシグナルをもとに
動いている場合は、この事で動作が終わらない
(writeがシグナル待ちでずっと書き込んでいる状態)可能性がある。
四つ目~六つ目「execute()内前半」
336 if (SHELL_DEBUG) fprintf(stderr,"done %d\n",pid);
337 if(in != 0) { close(in);} -+
338 if(out != 1) { close(out);} |-これら
339 if(err != 2) { close(err);} -+
340 } else {//子プロセスの処理
execute()は実際にコマンドの実行を行う。この三つのclose()は、
その際に親プロセス側が標準入力「0」、標準出力「1」、標準エラー出力「2」
以外に用意されたディスクリプタを閉じる部分である。
ファイルディスクリプタがこれら以外に存在するのはパイプ使用時と
リダイレクト使用時であるが、パイプの場合はこの段階で既に
in,out,errがディスクリプタ0,1,2にコピーされた状態で渡されるので、
このcloseが機能するのはリダイレクトを使用したときのみになる。
このclose()を消去すると、リダイレクトを利用したときに作成したファイルへの
ディスクリプタが残ったままになる。
これは親プロセスの処理なので、閉じられなかったディスクリプタは
my-shell実行中ずっと保持される事になる。
七つ目~九つ目「execute()内後半」
344 /* 同じdescriptor があった時に前のものをclose...
345 * でcloseする必要がある。
346 * closeするのは、開いているディスクリプタが余分になるか...
347 if(in != 0) { close(in); } -+
348 if(out != 1) { close(out); } |-これら
349 if(err != 2) { close(out); } -+
この三つのclose()は子プロセスがデフォルト以外に用意されたディスクリプタを
閉じる部分である。
子プロセスでは実際にコマンドの実行を行う必要があり、このときに用意された
ディスクリプタを0、1、2、にコピーしてからcloseを行う。
このclose()を消去すると、前半と同じようにリダイレクト使用時に
ディスクリプタが残ったままになる。実際はこの場所は子プロセス内であり、
動作が終了すれば解放時に処理される。
最後「execute内最後」
350 if(0) for(i=3;i<32;i++){close(i);} <--ここ
351 execvp(com[0], &com[0]); //コマンドの探索&実行...
352 perror(com[0]); //エラー出力
353 _exit(0);
354 }
役目としては一つ目のclose()と同じで、標準(0,1,2)以外のディスクリプタを
全て閉じるものであるが、if(0)で囲われており、全く機能していない。
デバッグ用に用意されたものだと思われる。
一つ目のclose()と同じく、消去しても通常実行時に影響は出ない。
また、この場所は子プロセスの内部であり、ディスクリプタが残っていたとしても
解放時に回収されるため、一つ目のcloseよりも影響が少ない。
追加分
150 command(d->right);
151 close(pfd[0]);//追加分 - 親のファイルディスクリプタ
152 if(SHELL_DEBUG) fprintf(stderr,"waiting %d\n",pid);
153 dup2(save_in,0);
154 close(save_in);//追加分 - 親のファイルディスクリプタ
155 waitpid(pid,NULL,0);
156 if(SHELL_DEBUG) fprintf(stderr,"done %d\n",pid);
157 return;
158 }else{//子処理
159 if(SHELL_DEBUG) fprintf(stderr,"forked pid %d\n",getpid());
160 dup2(pfd[1],1);
161 close(pfd[0]);
162 command(d->left);
163 close(pfd[1]);//追加分 - 子のファイルディスクリプタ
164 exit(0);
165 }
元のプログラムではパイプを利用したときに三つのファイルディスクリプタが
開いたままになっている。内一つは子プロセス内のもので解放時に処理されるので
影響は少ないが、その他の二つは最初の親プロセスが保持しっぱなしになる。
一つのプロセスが利用できるディスクリプタには上限があリ、
上限に達すると新たにディスクリプタを作成できなくなる。
ここまでのclose()に関する考察でもディスクリプタが残る可能性をいくつか
あげたが、パイプを利用するたびにそれが蓄積されていくのはよくない。
追加した分はこれらのディスクリプタを利用後に閉じるように設定するためのものである。
作成後に閉じられていないファイルディスクリプタは以下の三つで、
全てcommand()内のswitchのパターン'|'つまりパイプ処理の中、
fork()後にそれぞれのプロセスに処理をわけた後である。
- 親プロセス内の pfd[0] - パイプの読み出し側。
- 親プロセス内の save_in - 標準入力のバックアップ
- 子プロセス内の pfd[1] - パイプの書き込み側。