2014年1月28日火曜日

シェルとプロセスとプログラムの呼び出し

結構長いことLinuxを使っている自分だけれども、アプリ開発の仕事をずっとやってきたために、OSの機能については疎いところがあったりする。

先日、プログラミング言語から別のプログラムを呼ぶときに「シェル経由で呼び出す」方法と「シェルを経由せずに呼び出す」方法がある、という話を聞いた時によくわからなかったので、調べてみた。

・プロセスの概念
まず、プロセスの概念について。言葉だけは何となく使っちゃてるけど。プロセスとは、プログラムを実行するときにメモリ上にロードしてIDを割り当てたもの。たとえ同じプログラムでも、別々のメモリ領域を与えてアドレス空間を分離することによって、互いに干渉せず同時に実行することが可能になる。

・プロセスの複製と親子関係
UNIX系OSにはfork()システムコールがあり、これはプロセスを環境変数や実行位置なども含め現在の状態をまるごとコピーしてIDを付け替えたプロセスを生成する。fork()を呼び出した側が親プロセスで、fork()によって生成されたプロセスが子プロセスとなる。fork()の戻り値として親プロセス側には子プロセスのプロセスIDが渡され、子プロセス側には0が渡されるので自分が親か子かを判別できる。

・子プロセスの置き換え
あるプログラムから別のプログラムを呼び出す場合、まずfork()システムコールで子プロセスを生成し、子プロセス側でexec()システムコールを呼び出す。exec()システムコールは、自分自身を別のプログラムに置き換える(一旦メモリ上のアドレス空間を破棄して作りなおす)。プロセスIDは維持されるので、親子関係は保たれる。

・プロセスの終了
プロセス内でexit()システムコールを呼び出すと、そのプロセスは終了するが、メモリ上に管理情報は残っている。親プロセスからwait()システムコールを呼ぶことによって親は子プロセスの終了ステータスを取得した上で子プロセスの管理情報を削除する。親プロセスがwait()を呼ぶタイミングは、子プロセスを生成してすぐか、あるいは子プロセスから終了シグナルを受け取った時点になる。

・プロセスツリー
UNIX系OSでは、プログラムの実行は fork() → exec() を利用して、次々と子プロセスを生成していくことで行われる。一番最初のプロセスは、カーネル自身が init というプロセスを生成し、全てのプロセスの親となる。init を頂点に全てのプロセスがツリー状に親子関係でつながっている。ちなみに、子プロセスの終了前に親プロセスでが終了した場合、子プロセスの親は自動的に init になる。この場合の子プロセス終了時にwait()を呼ぶ処理は init が担当することになる。

・シェルはプログラム実行時に何をしているのか?
シェルは、ユーザからプログラム実行のコマンドを受け付けると、自分自身をfork()によって複製し、子プロセス側でexec()を呼び出して指定されたプログラムを実行する。あとは、wait() を呼んで子プロセスの終了を待つ(フォアグラウンドジョブ)。あるいは、wait()をすぐには呼ばず、にユーザからのコマンド受付状態に戻るようにも指定できる(バックグラウンドジョブ)。この場合終了した子プロセスに対して wait() を呼ぶのはシェルが適宜行ってくれると思う(詳しい方いたら教えてください)。

・シェルを経由するかしないかの違いとは
要するに fork() → exec() (+ wait()の待機) の一連の流れができれば、あるプログラムから別のプログラムを呼び出すことができる。この処理をシェルに任せるのが「シェル経由で呼び出す」方法であり、独自に行うのが「シェルを経由せずに呼び出す」方法だということ。

だいたいこんなもんかな。上に書いていない知識も色々整理できたが(親プロセスが異常終了した場合の子プロセス=ゾンビプロセスについてとか)、その辺は下の参考URLを参照。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

※参考URL
http://fumiyas.github.io/2013/12/21/dont-use-shell.sh-advent-calendar.html
http://itpro.nikkeibp.co.jp/article/COLUMN/20080502/300565/?ST=oss&P=1
http://www.gadgety.net/shin/tips/unix/ipc/fork.html
http://www.fireproject.jp/feature/c-language/process/fork-wait.html

0 件のコメント:

コメントを投稿