プロセスを生成します。DAQシステムで最も簡単にプロセスを生成するのは 単にシェルからプログラムをフォアグラウンドなりバックグラウンドで走らせ るものです。標準出力やエラー出力はそのままその端末に表示されます。これ ではあまり面白みがありません。DAQシステムに関する様々なプロセスを起動・ 監視・制御するプログラムを作りたい場合、プログラムの中からシステムコー ルを用いてプロセスを起動する方法を作る必要があります。
UNIXではどのようにしてプロセスが生成されるのでしょうか。まずあるプロ セスが存在するとします。例えばシェルかも知れません。ユーザプログラムで も構いません。それが新しいプロセスを作る手順は次のようになります。まず 自分自身のコピーを作ります。標準入出力や環境などプロセスコンテキストは すべて同じものを使います。違うのはプロセス番号だけです。この時自分がコ ピー(子)かオリジナル(親)か区別がつくようになっています。コピーの方は自 分がなんのためにコピーされたかを知っているはずなのでそのように振る舞い ます。このために使われるシステムコールはforkといいます。ナイフとフォー クのforkです。1本だったプロセスの流れが2本に分れるわけです。
int pid; pid = fork( ); /* forkを呼び出します。*/ if( pid ) { /*0でないpidが返されるのは親の方です。pidは子のプロセス番号です。*/ } else { /*0が返されるのは子の方です。ここには子としてやることが書かれます。*/ }
次にあるプログラムを実行させましょう。自分自身を終了して別のプログラム を起動するシステムコールを呼びます。引数の与え方の違いなどでいくつか用 意されています。
execl( path, arg0 [ , arg1,... , argn ] , ( char * )0 ) execv( path, argv ) execle( path, arg0 [ , arg1 ,... , argn ] , (char *)0, envp ) execve( path, argv, envp ) execlp( file, arg0 [ , arg1 ,... , argn ] , ( char * )0 ) execvp( file, argv ) char *path, *arg0, *arg1,... *argn, *argv[], *envp[], *file;
違いは引数をリストに並べて渡すか(l)配列にいれて渡すか(v)、環境を配列で 渡すか(e)、プログラムを直接指定するか(path)、パスから探すか(file)です。 基本になっているのはexecveです。execveがシステムコールで他は実行時関数 です。argvの構造はC言語のmain関数に引き渡されるargvと同じ構造でなけれ ばなりません。第一引数はプログラムの名前を与えることになります。上の例 ではarg0もしくは*argvの指す値はともにpathもしくはfil eと同じものにしま す。実質的な引数は2番目以降に与えます。当然ですがexecシステムコールは 成功すると戻ってきません。execの次の行に戻ってきたということは失敗した ということです。このようにexecシステムコールを使うと自分専用のシェルを 作ることが出来ます。DAQランコントローラなどは実際シェルのようなもので す。例題をやってみましょう。
main( argc, argv ) int argc; char ** argv; { char *cmd; if( fork( ) == 0 ) /* 戻り値が0は子プロセスの方 */ { argv++; /* 最初の引数(呼び出した名前)をスキップ */ cmd = *argv; /* コマンドラインに与えられた最初の引数 */ execv( cmd, argv ); /* 二番目以降の引数をつけて実行 */ printf( "Failed to exec %s\n", cmd ); /*execに成功したらここには来ないはず */ }
argvはmainが呼び出されるときに渡される引数リストです。このプログラムは、 その第1引数をコマンドと解釈し、第2引数から後をコマンドへの引数として渡 すものです。
forkで生成された子プロセスは制御端末を持たなくても走れます。ps -xで 表示させてみましょう。TTが?になっているものがあります。親のプロセスが 消滅しても走り続けることが出来ます。いわゆるデーモンプロセスとして走り ます。この場合プロセスからのメッセージは制御端末を持たないのでどこかの ファイルに書き出すよう標準出力・エラー出力をリダイレクトしておく必要が あるかも知れません。入出力の項を見てください。
int fd; char * argv[ ]; if( fork( ) == 0 ) { fd = open( "output.dat", O_WRONLY | O_CREAT, 0644 ); close( 1 ); /* 標準出力を閉じて、そこに */ dup( fd ); /* 書き込み用に開いたファイルを割当 */ execv( * argv, argv ); /* 実行 */ }
これによってexecvで起動されたコマンドの標準出力はoutput.datに書き込ま れることになります。
例えばアプリケーションの中からシェルを呼び出したりコマンドを実行した りしたとき、プログラムの側でそのコマンドの実行終了を待ちたいことがあり ます。そうでないと子プロセスも親プロセスも同時に標準出力を使うと混乱し てしまいます。子プロセスの完了を待つにはwaitシステムコールを使います。
if( fork( ) == 0 ) { /* こちらは子プロセス。execvなどを実行 */ } else { wait( 0 ); /* こちらは親プロセス。*/ }
引数0の替わりに整数へのポインターを渡すと子プロセスの終了コードを得る ことができます。
非常に寿命の長い(長時間にわたって走る)デーモンのようなプログラムの中 でいくつも子プロセスを発生させるときは注意が必要です。子プロセスは親プ ロセスがいつwaitシステムコールを発行するかわからないので自分の実行を終 了してもゾンビプロセスとなって残ります。psでみると状態Z、イメージが <defunct>と表示されます。親がwaitを発行してはじめて昇天し、リソースを 開放します。ですから親がwaitを発行せずにつぎつぎに子プロセスをforkする と山ほどゾンビが発生します。怖いですね。恐ろしいですね。この問題の解決 の仕方は9.7.シグナルの所で解説します。