4 プロセスの実行


Web サーバには CGI をサポートし、外部のプログラムを実行できるようになっているものが多い。 今回はプログラム中から他のプログラムを実行する exec システムコールについて解説する。


exec システムコール

exec システムコールは、そのシステムコールを呼びだしたプロセス上で、別なプログラムの実行を開始するためのシステムコールである。 このため、そのプロセスでそれまで実行されていたプログラムは破棄され、システムコール呼び出しがリターンすることはありえない。

exec() を使う典型的なプログラムは shell である。 Shell はコマンドとして入力されたプログラムを実行するのに、fork() によって新しいプロセスを作り、そのプロセス上で exec() を実行、そのプログラムを実行する。

プログラムで書くと次のようになる。


本格的な shell を作るためには、子プロセス実行中に、制御端末から ^C (強制中断) を打ったときに、shell ではなく、子プロセスを停止させなければならない。 このためには、子プロセス用に新しくプロセスグループを作り、制御端末をその新しいプロセスグループに入れなければならない。 詳しくは、setpgid(2), ioctl(2), termios(7) について調べよ。


次に exec() の具体的な使い方を説明する。 実は exec という名前のシステムコールはなく、引数の種類によって execl(), execv() などいくつかのシステムコールが存在する。

これらのシステムコールは path で指定されたプログラムを、指定された引数で実行する。 execl() の場合、第 0 引数、第 1 引数 ... がそれぞれ arg0, arg1, ... となる。 引数列の最後は NULL でなければならない。 execv() の場合、引数は配列の形で渡される。 なお、配列の最後の要素は NULL でなければならない。

使い方は例えば次のようである。

このプログラムは /bin/ls -F /usr/local を実行する。 第 0 引数は、通常、呼び出されるプログラムの名前であることに注意。 また execl() で実行されるプログラムは、shell を通さずに実行されるので、シェルスクリプトやホームディレクトリを表す ~ は使えない。

正常にプログラムを起動できたときには、execl() は永遠にリターンしない。 リターンするのは、プログラムの起動に失敗したときである。 上のプログラムでは、 ls の起動に失敗すると、execl() が -1 を返し、perror() でエラーの詳細が表示される。

exec システムコールは、プログラムを起動するときに、新しくプロセスを作るわけではない。 起動されるプログラムは、そのシステムコールを呼んだプロセスを使って実行される。 このためシステムコールを呼んだプログラムは、消去されてしまう。


dup2 システムコール

exec システムコールは、そのシステムコールを呼んだプログラムを消去してしまうが、プログラムが open していたファイル・ディスクリプタは、新しいプログラムに引き継がれる。 どちらも同じプロセスで実行されるからである。 (OS はプロセスごとに、ファイル・ディスクリプタを管理していることを思いだしてほしい。)

Shell のリダイレクトやパイプといった機能は、この特徴を利用して実装されている。 例えば出力をファイル foo にリダイレクトする場合を考える。

この場合、shell は ls を execl() で実行する前に、ファイル foo を open() し、そのファイル・ディスクリプタが標準出力 1 になるように設定する。

標準出力 1 を、open() したファイルに切り替えるシステムコールが dup2() である。 このシステムコールによって、以後、write(1, ...) はファイル foo への書きこみとなる。

パイプの場合、shell はふたつのプログラムを同時に実行するので、動作が多少、複雑になるが、基本はリダイレクトと同じである。 それぞれのプログラムを実行するプロセスで、標準入力 0 と標準出力 1 をそれぞれ、pipe システムコールで作成したストリーム(同一マシンの中でだけ使えるソケットのようなもの)の両端に切り替えればよい。

pipe() を呼ぶと、fildes[0], fildes[1] にそれぞれ、作成したストリームの両端を表わすファイル・ディスクリプタが格納される。


Web サーバによる外部プログラムの起動

exec() を利用して、これまで作成してきた web サーバでも外部のプログラムを実行して、その出力を web browser に返すことができるようにしよう。

いろいろな仕様が考えられるが、外部プログラムに引数を渡す必要がないのなら、実装は容易である。 GET 命令で指定されたファイルが、特定のディレクトリの下におかれている場合、そのファイルを execl() で実行し、出力をブラウザにそのまま送り返すようにすればよい。

例えば、外部のプログラムは ./bin ディレクトリの下におかれ、 web browser から

などの URL を参照しようとしたら、そのプログラム (例では ./bin/a.out) を実行し、出力をそのまま web browser に返すとしよう。

この仕様を実現するには、

を送信した後、もしファイル名が /bin/ で始まっていたら、ファイルの内容を送信するかわりに、そのファイルを execl() で実行し、プログラムの出力を web browser に送信するようにすればよい。

プログラムの出力を web browser に送信するためには、上で解説した shell のリダイレクト機能と同様のことをすればよい。

ヘッダに続けて、a.out の出力が web browser に送信される。


Form の利用

実用的な web サーバの場合、外部プログラムに引数を渡す機能は必須である。 この機能を実装するには、web サーバはブラウザから送られた情報を解析して、引数を取りだせなければならない。

Web サーバに引数を渡すには HTMLファイル中にフォームを埋めこむ。 例えば



のような入力をおこなうには、

などと書けばよい。

このフォームに対して、名前を "Chiba"、性別を男、初心者をオン、感想を "I'm very pleased with the talk." とし、 登録ボタンをクリックすると、ブラウザは

のようなデータを web サーバに送信する。

送信されたデータは GET 命令ではなく、POST 命令である。 この命令はヘッダー情報の後に、情報(この場合はフォームの引数)を付加できる。

フォームの各項目に入力されたデータは、x-www-form-urlencoded と呼ばれる 形式に変換される。 各項目は <項目名>=<値> という形式に変換され、項目の間は & で区切られる。 感想 (commnet) の値については、空白は + に、非英数字は %<16進コード> という形式に置きかえられる。

POST 命令を受けとった web サーバは、付加情報を読み取り、適切な形に変換して外部プログラム (上の例では bin/apply.cgi) に渡さなければならない。 CGI (Common Gateway Interface, http://www.w3.org/CGI/) では、web サーバは読み取った付加情報を標準入力を経由してそのまま外部プログラムに渡すことになっている。 x-www-form-urlencoded 形式から、項目ごとの値を取り出す作業は、外部プログラムの仕事である。

なお、フォームを定義するときに、method として POST でなく、GET を指定することもできる。 GET が指定されると、ブラウザが送信するデータは次のようになる。

POST 命令ではなく、GET 命令になり、フォームの引数は元々の URL の後に ? に続けて付加される。 CGI では、web サーバは引数部分を切り出し、環境変数 QUERY_STRING を使って全体をそのまま外部プログラムに渡すことになっている。


課題 4

exec システムコールを使って、課題 3 で作成した web server が外部のプログラムを起動できるようにせよ。

http://host1/bin/servlet のように、./bin ディレクトリの下にあるファイルが要求されたときは、servlet を実行し、その出力を結果として返すようにせよ。 ./bin 以外のディレクトリの下にあるファイルが要求されたときは、これまでどおり、そのファイルの内容をそのまま返すようにすること。

テストに使う外部プログラムとしては、 servlet.c

のようにコンパイルして用いよ。




目次へ戻る

Copyright (C) 1999-2001 Shigeru Chiba