Try   HackMD

ジョブってなに? - シェルもどきをgoで自作する#17

おさらい

これまで

シェルもどき「oreshell」を自作している。
前回は、oreshellにジョブ制御を実装しようとしたが、ジョブ制御を実現する前にプロセスグループを理解しておいたほうが良いらしいので、プロセスグループ/セッションについて調査した。

今回はoreshellの実装をプロセスグループに対応する。
また、oreshellをジョブ制御に対応するために、まずはジョブについて調べる。

oreshellをプロセスグループに対応

実装

こちらを参照。

要所だけ解説。

  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を設定するように修正した。

確認

前回説明した通り、プロセスグループとは

同じプロセスグループ 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のプロセスグループ対応は完了。

で、次。ジョブとはなにか。

ジョブってなに?

ジョブとは「人間がコンピュータに与える仕事の実行単位のこと」である。(参考)

人間が、「こういう処理をしたい」「ああいう処理をしたい」という要望をコンピュータに対して入力するときに、入力した要望のひとかたまりがジョブとなる。

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つのコマンドもしくはパイプラインの実行に使われるプロセス群の扱いを図で示す。

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。

2行目

シェルからコマンド行

$ grep -s -r hoge / | sort > /dev/null 2>&1 &

を実行する。末尾に「&」がついていることに注意。コマンド行はバックグラウンドプロセスグループ/ジョブとなる。

端末からの入力は引き続きシェル(bash)が受け取る。

この時点のジョブの数は1つ。

3行目

シェルから「cat」コマンドを実行する。
引数無しで「cat」コマンドを実行すると入力待ちになる。続けて入力した文字をおうむ返しで出力する。
コマンドの末尾に「&」がないため、「cat」コマンドがフォアグラウンドプロセス/ジョブとなる。端末からの入力は「cat」コマンドが受け取る。

シェル(bash)は「cat」コマンドを起動した後、バックグラウンドプロセスグループに切り替わる。「cat」コマンドが終了するかバックグラウンドに変更するまでこの端末からシェルに入力することはできない。

この時点のジョブの数は2つ。

4行目

Ctrl+dを入力すると「cat」コマンドは入力受付を終了して実行を終了する。

「cat」コマンドが実行を終了すると、シェル(bash)は再びフォアグラウンドプロセスグループに切り替わる。

この時点のジョブの数は1つ。

しばらく待っていると

しばらく待っていると、「grep」「sort」のバックグラウンドプロセググループ/ジョブの実行が終了し、シェルが

[1]+  Done                    grep -s -r hoge . | sort > /dev/null 2>&1

を表示する。(bashの場合、自動通知するか、次のプロンプト表示で通知するかは「set -bまたは+b」)

ジョブの数は0。

次回

ジョブの説明が終わったので次はジョブ制御とシグナルの話をする予定(たぶん)。