# ジョブってなに? - シェルもどきをgoで自作する#17 ## おさらい これまで - [シェルってなに?コマンドラインインタプリタってなに? - シェルもどきをgoで自作する#1](https://hackmd.io/@jyami/HJzohRn2D) - [コマンドと引数の分解、環境変数PATHから探索、外部コマンドと内部コマンド - シェルもどきをgoで自作する #2](https://hackmd.io/@jyami/HyeSkkThP) - [字句解析その1 - シェルもどきをgoで自作する #3](https://hackmd.io/@jyami/Hk3bWSMQO) - [字句解析その2 - シェルもどきをgoで自作する #4](https://hackmd.io/@jyami/S1BkltxQu) - [リダイレクションってなに? - シェルもどきをgoで自作する #5](https://hackmd.io/@jyami/S113NQx8u) - [リダイレクションの種類 - シェルもどきをgoで自作する #6](https://hackmd.io/@jyami/rJ3XmClqd) - [リダイレクションの構文 - シェルもどきをgoで自作する #7](https://hackmd.io/@jyami/BJ04J2Upd) - [コマンドプロセスを作成する際のファイルディスクリプタの操作 - シェルもどきをgoで自作する #8](https://hackmd.io/@jyami/Hy7nSciMt) - [構文解析と抽象構文木 - シェルもどきをgoで自作する #9](https://hackmd.io/@jyami/ByrK1ajIK) - [パイプってなに? - シェルもどきをgoで自作する #10](https://hackmd.io/@jyami/SkXR3iltK) - [ワイルドカードってなに? - シェルもどきをgoで自作する #11](https://hackmd.io/@jyami/SJghKSl3F) - [環境変数ってなに? - シェルもどきをgoで自作する #12](https://hackmd.io/@jyami/H12wYg4L9) - [環境変数の設定と環境変数の展開 - シェルもどきをgoで自作する #13](https://hackmd.io/@jyami/Hy3EefRgj) - [シェル変数との違いからみた環境変数 - シェルもどきをgoで自作する #14](https://hackmd.io/@jyami/ByxFrm6Si) - [シェル変数を実装 - シェルもどきをgoで自作する #15](https://hackmd.io/@jyami/rJf5GFRhs) - [プロセスグループ/セッションってなに? - シェルもどきをgoで自作する #16](https://hackmd.io/@jyami/Hk0J0E7vn) シェルもどき「[oreshell](https://github.com/jyami/oreshell)」を自作している。 前回は、oreshellにジョブ制御を実装しようとしたが、ジョブ制御を実現する前にプロセスグループを理解しておいたほうが良いらしいので、プロセスグループ/セッションについて調査した。 今回はoreshellの実装をプロセスグループに対応する。 また、oreshellをジョブ制御に対応するために、まずはジョブについて調べる。 ## oreshellをプロセスグループに対応 ### 実装 [こちら](https://github.com/jyami/oreshell/commit/7c4904c00b66e7f891f503a21f7b91039fdbeac0)を参照。 要所だけ解説。 ```process.go if me.isLeader() { procAttr.Sys = &syscall.SysProcAttr{Setpgid: true} } else { log.Logger.Printf("leader pid: %v", me.leader().osProcess.Pid) procAttr.Sys = &syscall.SysProcAttr{Setpgid: true, Pgid: me.leader().osProcess.Pid} } ``` コマンドのパイプラインを実行するとき、コマンドが先頭であればプロセスグループリーダーになることを宣言し、先頭以外のコマンドは自身のプロセスグループIDのプロセスグループリーダーのプロセスIDを設定するように修正した。 ### 確認 [前回説明した通り](https://hackmd.io/@jyami/Hk0J0E7vn#%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A3%E3%81%A6%E3%81%AA%E3%81%AB)、プロセスグループとは > 同じプロセスグループ ID を共有するプロセスの集まりである。 シェルは、一つのコマンドもしくはパイプラインの実行に使われるプロセス群に 対して一つのプロセスグループを生成する である。それを確認する。 oreshellを起動し、 ``` (ore) > grep -s -r hoge / | sort > /dev/null 2>&1 & ``` を実行。 もう一つ端末を開いてそこから ``` $ ps -ejH ``` を実行。 ``` $ ps -ejH PID PGID SID TTY TIME CMD 1 0 0 ? 00:00:00 init(Ubuntu) 4 0 0 ? 00:00:00 init 8 8 8 ? 00:00:00 SessionLeader 9 8 8 ? 00:00:00 Relay(10) 10 10 10 pts/0 00:00:00 bash 170 170 10 pts/0 00:00:00 tmux: client 172 172 172 ? 00:00:00 tmux: server 173 173 173 pts/1 00:00:00 bash 10043 10043 10043 pts/5 00:00:00 bash 21966 21966 10043 pts/5 00:00:00 oreshell 22087 22087 10043 pts/5 00:00:01 grep ← ここ 22088 22087 10043 pts/5 00:00:00 sort ← ここ (略) ``` 「grep」プロセスのPIDが22087、PGIDが22087となっており、プロセスグループリーダーになったことがわかる。 「sort」プロセスのPGIDが22087となっており、grepのプロセスグループに属していることがわかる。 以上の通り、oreshellのプロセスグループ対応は完了。 で、次。ジョブとはなにか。 ## ジョブってなに? ジョブとは「人間がコンピュータに与える仕事の実行単位のこと」である。([参考](https://e-words.jp/w/%E3%82%B8%E3%83%A7%E3%83%96.html)) 人間が、「こういう処理をしたい」「ああいう処理をしたい」という要望をコンピュータに対して入力するときに、入力した要望のひとかたまりがジョブとなる。 Unix(とその眷属)の場合、シェルのプロンプトから入力したコマンド行1行がジョブである。 ジョブは1つ以上のプロセスで構成する。 先の例の場合 ``` $ ps -ejH ``` や ``` $ grep -s -r hoge / | sort > /dev/null 2>&1 & ``` はそれぞれ1つのジョブである。 1つ目のジョブは1つのプロセスで構成し、2つ目のジョブは2つのプロセスで構成している。 ### シェルをそれはジョブと呼び、OSはそれをプロセスグループと呼ぶ 前述したとおり、1つのコマンドもしくはパイプラインの実行に使われるプロセス群はプロセスグループである。 つまり、1つのコマンドもしくはパイプラインの実行に使われるプロセス群を、OSカーネルは「プロセスグループ」として管理し、シェルは「ジョブ」として管理する。 例としてシェル(bash)から以下を実行する。 ``` $ grep -s -r hoge / | sort > /dev/null /dev/null & $ ps -ejH ``` この時のシェルとOSカーネルから見た、1つのコマンドもしくはパイプラインの実行に使われるプロセス群の扱いを図で示す。 ![](https://hackmd.io/_uploads/SygOLC3j3.png) OSから見ると、シェルもプロセスグループの1つである。 ## フォアグラウンド、バックグラウンド 端末と結びついた複数のプロセスグループのうち、**端末の入力を受け付ける**プロセスグループは1つだけ。それをフォアグラウンドプロセスグループと呼ぶ。フォアグラウンドプロセスグループがシェルから起動したジョブの場合はフォアグラウンドジョブとも呼ぶ。 フォアグラウンドプロセスグループ/ジョブをシェルから起動する場合は、コマンド行の末尾に「&」をつけない。 それ以外の(端末の入力と切り離された)プロセスグループをバックグラウンドプロセスグループと呼ぶ。バックグラウンドプロセスグループがシェルから起動したジョブの場合はバックグラウンドジョブとも呼ぶ。 バックグラウンドプロセスグループ/ジョブをシェルから起動する場合は、コマンド行の末尾に「&」をつける。 例としてシェル(bash)から以下を実行したときの端末とフォアグラウンド/バックグラウンドのジョブ/プロセスグループの関係を図で示す。 ``` $ # 1 $ grep -s -r hoge / | sort > /dev/null 2>&1 & # 2 [1] 26227 $ cat # 3 こんにちは こんにちは (ctrl+dで入力終了) $ # 4 ``` #### 1行目 まだシェルからジョブを起動していない。端末から入力を受け取るプロセスグループはシェル(bash)だけ。よって、フォアグラウンドプロセスグループはシェル(bash)。 この時点のジョブの数は0。 ![](https://hackmd.io/_uploads/H16Gy16on.png) #### 2行目 シェルからコマンド行 ``` $ grep -s -r hoge / | sort > /dev/null 2>&1 & ``` を実行する。末尾に「&」がついていることに注意。コマンド行はバックグラウンドプロセスグループ/ジョブとなる。 端末からの入力は引き続きシェル(bash)が受け取る。 この時点のジョブの数は1つ。 ![](https://hackmd.io/_uploads/HkOQk1Toh.png) #### 3行目 シェルから「cat」コマンドを実行する。 引数無しで「cat」コマンドを実行すると入力待ちになる。続けて入力した文字をおうむ返しで出力する。 コマンドの末尾に「&」がないため、「cat」コマンドがフォアグラウンドプロセス/ジョブとなる。端末からの入力は「cat」コマンドが受け取る。 シェル(bash)は「cat」コマンドを起動した後、バックグラウンドプロセスグループに切り替わる。「cat」コマンドが終了するかバックグラウンドに変更するまでこの端末からシェルに入力することはできない。 この時点のジョブの数は2つ。 ![](https://hackmd.io/_uploads/rkwE1kTj2.png) #### 4行目 Ctrl+dを入力すると「cat」コマンドは入力受付を終了して実行を終了する。 「cat」コマンドが実行を終了すると、シェル(bash)は再びフォアグラウンドプロセスグループに切り替わる。 この時点のジョブの数は1つ。 ![](https://hackmd.io/_uploads/BkSB1kTi3.png) #### しばらく待っていると しばらく待っていると、「grep」「sort」のバックグラウンドプロセググループ/ジョブの実行が終了し、シェルが ``` [1]+ Done grep -s -r hoge . | sort > /dev/null 2>&1 ``` を表示する。([bashの場合、自動通知するか、次のプロンプト表示で通知するかは「set -bまたは+b」](https://www.chiark.greenend.org.uk/~sgtatham/bash-notify/)) ジョブの数は0。 ![](https://hackmd.io/_uploads/Hk25jG6in.png) ## 次回 ジョブの説明が終わったので次はジョブ制御とシグナルの話をする予定(たぶん)。