プロセス

RubyプログラムはUnixシステム上の1プロセスとして動いている。 プロセスは新たに生成したり,生成したものに信号を送ったりなどの 操作ができる。

プロセスの属性

Unixプロセスは1つのプログラムが活動している状態で, 状態を示す値がいくつかある,プログラミングをする上で 知っておくべき代表的なものが以下のものである。

プロセスID

各プロセスに振られる固有の整数

親プロセスID

そのプロセスを生成したプロセスのプロセスID

ユーザID

プロセスの動作権限となるユーザID

グループID

プロセスの動作権限となるグループID

カレントディレクトリ

プロセスのその時点の作業ディレクトリ

TTY

入出力を結びつけられた端末

Rubyプログラムのシステムプロセスとしての情報は Process モジュールを介して得たり設定したりできる。以下のプログラムは 起動したプログラムのPID, UID, GIDを出力する。

#!/usr/bin/env ruby
# coding: euc-jp
printf("pid=%d, UID=%d, GID=%d\n",
       Process.pid, Process.uid, Process.gid)

fork&exec

Unixシステムは起動時に /sbin/init プロセスが起動され, 残りのプロセスは全て init の子として起動される。 プロセスの生成は以下の2つの機構を組み合わせて行なわれる。

  1. fork

    現在の自己プロセスのコピーを作り,プログラムの同じ場所から 続けて動作する

  2. exec

    現在の自己プロセスの情報を全て引き継ぐ外部プログラムに 実行を移す

一般的に,1つのプログラムの制御下で別のプログラムを起動するには forkとexecを組み合わせて行なう。forkはシステムコールで, 呼出しと同時にプロセスの複製が作られる。呼出し元となったプロセスには 子プロセスのPIDが返されるが,子プロセスには0(Rubyプログラムの場合 nil)が返る。

f+e.rb

#!/usr/bin/env ruby
# coding: euc-jp
cmd = "kterm"

if (pid = fork()) then
  # 親プロセスのみ必要な処理はここ
else
  # 子プロセスのみの処理はここ
  exec(cmd)
end

STDERR.printf("%s について: 終了を待つ=w killする=k 放置=その他: ", cmd)
case gets
when /^k/i
  STDERR.puts "ボシュっ"
  Process.kill(:KILL, pid)
when /^w/i
  STDERR.puts "じゃ,待ちます。"
  Process.wait
else
  STDERR.puts "じゃ,わしゃ勝手に終わります。さいなら。"
end

exec は,プログラムの起動に失敗すると 例外を発生させる。実際のプログラムでは外部プログラムの実行が 失敗する可能性も考えなければならない。そのときの処理は スレッドで解説する。

シグナル

上記の例題プログラムにある Process.kill は, あるプロセスにシグナルを送るためのメソッドである。 シグナルとはUnixシステムで走行中のプロセスに対し,「何か」が起こったことを 非同期に通知するための仕組みで, どんなプロセスも走行中,自分自身に送られるシグナルを即時に受け取り それに応じた処理に移ることも,無視することもできる。 「それに応じた処理」は,シグナルを受け取ったときに自動的に呼ばれる ルーチンを登録することで行なわせる。自動的に呼ばれる部分を シグナルハンドラという。

シグナルはシステムによって若干異なるが数十種類のものが 存在する。日常的プログラミングで利用する主なものには以下のものがある。

シグナル名意味
HUPHangup 端末の回線が切断されたとき。 ktermなど親となる仮想端末が終了したときも該当。
INTInterrupt 割り込みキー(C-c)を タイプしたとき。
QUIT終了キー(C-\)をタイプしたとき。
KILL強制終了(捕捉できない)。
SEGVSegmantation violation セグメンテーション 違反(書き込み禁止メモリへの書き込みなど)。
ALRMAlarm 設定した制限時間が経過したときに 自動的に送られる。
TERMTerminate killコマンドによる 強制終了(捕捉できる)。
STOP端末以外からの実行中断(捕捉できない)
TSTP端末からの実行中断(C-z)
CONT中断からの復帰

Rubyプログラムから別のプロセスにシグナルを送るには Process.kill に シグナル名の前にコロン(:)を付けたシンボルと, シグナル送信先のプロセスIDを指定する。

シグナル捕捉

プログラムにシグナルが送られた場合のデフォルトの処理は シグナルごとに決まっている。たとえば,SIGINTが送られた場合は プログラムが中断させられる。これを別のものに変える場合は シグナルハンドラの登録を行なう。このためには Signal.trap() を用いる。

Signal.trap(:シグナル, ハンドラ)

ハンドラ として nil を指定すると シグナルを無視する。"DEFAULT" を指定すると 独自のハンドラ登録を解除する。次のプログラムは C-c を 押されて SIGINT が送られたときの処理を変えるものである。

intbye.rb

#!/usr/bin/env ruby
# coding: euc-jp
def bye()
  Signal.trap(:INT, nil)        # ここでのSIGINTは無視
  STDERR.puts "そんなー。"
  sleep 1
  STDERR.puts "でもサヨナラ"
  exit 1
end

Signal.trap(:INT, "bye")
10.downto(1) do |i|
  STDERR.printf("%d..", i)
  sleep 1
end
STDERR.puts "0 おしまい!"

プログラムの実行時に一時ファイルを作成しているようなものは C-c で止められたときに,一時ファイルを消去するなどの 必要な後始末を行なってからexitするようにする。


本日の目次

yuuji@e.koeki-u.ac.jp