->back to toppage

各closeについて

一つ目「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()後にそれぞれのプロセスに処理をわけた後である。


back