シェルもどき「oreshell」を自作している。
前回は、シグナルついて調べた。
今回はフォアグラウンドジョブ/バックグラウンドジョブの実現について。
現状のoreshellはフォアグラウンドジョブしか実行できない。
コマンド末尾に「&」をつけて、バックグラウンドジョブ実行できるようにしたい。
oreshellにバックグラウンドジョブ実行を組み込んだコードを説明するには、oreshellのコードが大きくなりすぎたため難しい。よってoreshellの機能縮小版を用意してそれを使って説明する。
oreshellの機能縮小版として、子プロセスとしてpingコマンドしか実行できないツール、pingstarterを作る。
以下はその仕様。
ベースとしたコードは第一回のこちら
当初は
とすれば変更作業終わりだろうと考えていたが甘かった。
かなり試行錯誤した結果、何とか期待通りに動くようになったコードは以下の通り。
★F、★Bはフォアグラウンドジョブ(プロセスグループ)実行/バックグラウンドジョブ(プロセスグループ)実行に関連する変更点である。
以下に変更内容の説明を示す。
先にも書いたが、フォアグラウンドジョブ(プロセスグループ)/バックグラウンドジョブ(プロセスグループ)の切り替えは以下だけで済むと考えいた。
しかし、この変更だけではフォアグラウンドジョブ実行が期待した動作にならない。
泣きながら3日間ほどぐぐったところ、goのサンプルコード集にunix上でのプロセス生成の例が見つかった。
ここを見ると、goのプログラムの中で子プロセスをフォアグラウンドプロセスグループで起動する例が書いてある。
ポイントは以下のとおり。
gnuのドキュメントのImplementing a Job Control Shell の Initializing the Shellにも同じようなことが書いてある。
bashでもSIGTTIN,SIGTTOUが飛んで来たらそれを無視するらしい。
Commands run as a result of command substitution ignore the keyboard-generated job control signals
SIGTTIN
,SIGTTOU
, andSIGTSTP
.
ジョブ制御が有効な場合 (ジョブ制御を参照) 、BashはSIGTTIN、SIGTTOU、SIGTSTPを無視します。(みらい翻訳)
ubuntuのmanpageによると
とある。
第2引数のpgrp、つまりpingstarterのプロセスグループIDはどうやって取得すればよいか?とぐぐってみると
を使えばよいらしい。
wikipediaではSIGTTIN、SIGTTOUについて以下の通り。
シグナル名 : SIGTTIN
説明 : バックグラウンドプロセスが端末から読もうとした
デフォルト動作 : 一時中断
解説 : バックグラウンドのプロセスグループがユーザー入力待ちとなって停止。シェルの機能を使ってフォアグラウンドにすることで入力が可能。
シグナル名 : SIGTTOU
説明 : バックグラウンドプロセスが端末に書き込もうとした
デフォルト動作 : 一時中断
解説 : バックグラウンドのプロセスグループが端末への表示待ちとなって停止。シェルの機能を使ってフォアグラウンドにすることで表示可能。
ここによると、tcsetpgrp()呼ぶとシグナルSIGTTOUを生成するとある。
このシグナルSIGTTOUを無視しないと、pingstarterが動作を中断してしまう。
どういう理由でtcsetpgrp()がシグナルSIGTTOUを発生するのか理由はさっぱりわからない。
が、前述の通りbashもジョブ制御時にはこの2つのシグナルを無視するらしいので真似することにする。(SIGTTINは無視しなくても今のところ動いているが、無視することにする。)
上記をまとめた図を以下に示す。
バックグラウンド実行はすんなり動いた。
以下は変更内容の概要。
第1回からこれまでのoreshellでは、コマンドの実行をフォアグラウンド実行していたつもりがバックグラウンド実行していたのだろうか?(ただし、コマンドが終わるまで待っていた。)
oreshellにフォアグラウンドジョブ/バックグラウンドジョブ実行を組み込む。あとできればjobコマンドも。