process group and killpg

C で書かれた昔のプログラムをデバッグ中に、 killpg というシステムコールに出くわす。killpg(2) は programmer を kill するわけではなく、プロセスグループ( process group) に対してシグナルを送るシステムコール。
忘れないようにメモ。

プロセスグループ
プロセスグループはプロセスの集まりに対して振られているID。例としては以下のものがある。

  • パイプでつなげたコマンド同士
  • フォーク直後の親プロセスと子プロセス

プロセスグループの確認方法

ps コマンドの PGID 列で確認できる

パイプの例

$ python pipe.py | grep foobar &
[1] 30802
$ ps -j
  PID  PGID   SID TTY          TIME CMD
24840 24840 24840 pts/7    00:00:01 bash
30801 30801 24840 pts/7    00:00:00 python
30802 30801 24840 pts/7    00:00:00 grep
30803 30803 24840 pts/7    00:00:00 ps

フォークの例

$ cat demo_fork.py
# 例解UNIXプログラミング P. 312
# 09-15
import os
import sys
import time

pid = os.fork()

c = '.' if pid == 0 else '@'

while True:
    sys.stderr.write(c)
    time.sleep(1)

起動すると次のようになる

$ ps xo pid,pgid,sid,tty,time,args
 PID PGID SID TT TIME COMMAND
 1467 1467 1467 ? 00:00:35 SCREEN
... snip ...
30767 30767 24840 pts/7 00:00:00 python demo_fork.py
30768 30767 24840 pts/7 00:00:00 python demo_fork.py
30779 30779 21795 pts/5 00:00:00 ps xo pid,pgid,sid,tty,time,args

同じプロセスグループ 30767 が振られている。
プロセスIDごとにシグナル送信するのは少し手間

$ python demo_fork.py
@.@.@.@.@.@.@.@.@.@.@.@..@@..@Terminated # 別ターミナルから $ kill -SIGTERM 30767 して親プロセス終了
$ ........... # 別ターミナルから $ kill -SIGTERM 30768 して 子プロセス終了
$

プロセスグループでシグナル送信すると楽になる。これがプロセスグループの狙い。

$ python demo_fork.py
@.@.@.@..@@..@@..@@..@@..@@..@Terminated # 別ターミナルから $ pkill -SIGTERM -g 30767
$

プロセスグループの変更

例えば、フォークした子プロセスのプロセスグループを変更したい場合は setpgid(2) を使う

$ cat demo_setpgid.py
# 例解UNIXプログラミング  P. 314
# 09-16
import os
import sys
import time

pid = os.fork()

c = '.' if pid == 0 else '@'
if pid == 0:
    # child process
    os.setpgid(os.getpid(), os.getpid())

while True:
    sys.stderr.write(c)
    time.sleep(1)

起動すると次のようになる

$ ps xo pid,pgid,sid,tty,time,args
 PID PGID SID TT TIME COMMAND
... snip ...
30863 30863 24840 pts/7 00:00:00 python demo_setpgid.py
30864 30864 24840 pts/7 00:00:00 python demo_setpgid.py
30865 30865 21795 pts/5 00:00:00 ps xo pid,pgid,sid,tty,time,args

$ python demo_setpgid.py
@.@.@.^CTraceback (most recent call last):  # 親プロセスに SIGINT を送る
 File "demo_setpgid.py", line 15, in
 time.sleep(1)
KeyboardInterrupt
$ .....^C # 子プロセスは SIGINT では終了しない
$ .................... # 別ターミナルから $ kill -SIGTERM 30864
$

プロセスグループへのシグナル送信

プロセスグループにシグナル送信する場合、システムコールなら killpg(2)/kill(2)、コマンドラインなら pkill(1)/kill(1) を使う。kill 系を利用する場合、負の pid を与えると、絶対値のプロセスグループID(pgid)とみなされる。

system call

C なら killpg(pgid, sig)/kill(-pgid, sig)
Python なら os.killpg(pgid, sig)/os.kill(-pgid, sig) で OK

例)

import os, signal
os.killpg(30000, signal.SIGTERM)
os.kill(-30000, signal.SIGTERM)

program

$ pkill -sig -g pgid
$ kill -sig -pgid

例)

$ pkill -SIGTERM -g 30000
$ kill -SIGTERM -30000

SIGINT を受け取るとプロセスIDとプロセスグループIDを出力するプログラムを用意して動作を確認

$ cat demo_signal.py
import os
import time
import signal
import sys

pid = os.fork()
if pid == 0:
    # child process
    # do something
    pass
elif  pid > 0:
    # parent process
    sys.stderr.write('[%d]forked\n' % pid)
    pass
else:
    # fork failed
    sys.exit(-1)

while True:
    try:
        time.sleep(1)
    except KeyboardInterrupt:
        sys.stderr.write('%d:%d\n'%(os.getpgrp(), os.getpid()))

起動して、プロセスグループにシグナルを送信する。

$ ps xo pid,pgid,sid,tty,time,args
  PID  PGID   SID TT           TIME COMMAND
... snip ...
31014 31014 24840 pts/7    00:00:00 python demo_signal.py
31015 31014 24840 pts/7    00:00:00 python demo_signal.py

$ python demo_signal.py
[31015]forked

^C31014:31014 # CTRL-C
31014:31015

31014:31015  # $ pkill -SIGINT -g 31014
31014:31014

31014:31015  # $ pkill -SIGINT -g 31014
31014:31014

Terminated   # $ kill -SIGTERM  -31014
$

MEMO1

ながらく積ん読状態だった 冨永 和人権藤 克彦 著の「例解UNIXプログラミング教室」(2007, ピアソンエデュケーション)の  §9.13「プロセスグループ、ジョブ制御、セッション、制御端末」が非常に参考になった。たくさんのミニマルなプログラムを書いて、動かして、動作を確認して理解していくスタイル。Unix システムプログラミングではいろいろと力になってくれそう。。

著:冨永 和人、権藤 克彦

目次
第1 章 C の復習(1):マニュアルの読み方,エラー処理,構造体,共用体
第2 章 C の復習(2):ポインタ,バイトオーダ,複雑な型
第3 章 低水準入出力
第4 章 標準入出力ライブラリ
第5 章 プロセス
第6 章 ファイルシステム
第7 章 ファイル記述子のコピーとパイプ:dup,dup2,pipe
第8 章 ソケット通信入門
第9 章 シグナルと競合状態
第10 章 端末(1):端末,端末ラインディシプリン,termios 構造体
第11 章 端末(2):エスケープシーケンス,curses ライブラリ,擬似端末
第12 章 非局所脱出:setjmp, longjmp

MEMO2

fork した子プロセスを終了させると、ゾンビプロセス(zombie process/defunct process)として残る。
ゾンビプロセスを ps コマンドで確認すると、ステート(STAT) 列は「Z」で始まり、コマンド(COMMAND)列に defunct が表示される。

$ ps xo pid,pgid,stat,command
  PID  PGID STAT COMMAND
31831 31831 S+   python defunct.py
31832 31831 Z+   [python]

親プロセスが wait すればzombieを回収(reap)できる。「例解UNIXプログラミング教室」の 「§9.12 wait せずにゾンビを避けたい」でwait せずにゾンビを避ける方法が2つ説明されていた。

1つめは、fork() した子が再び fork して孫を作り、子はすぐに終了し、孫プロセスを orphan process にするというもの。init orphant の親になる(養子/adopt)ので、wait の面倒を見なくてすむ。

プログラムとしては次のようになる。

import os
import sys
import time

pid = os.fork()
if pid == 0:
    # child process
    pid = os.fork()
    if pid == 0:
        # grand child process
        time.sleep(60) # do slave processing
        os._exit(0)
    elif pid > 0:
        # child process
        # child suicides after spawning
        time.sleep(15)
        os._exit(0)
    else:
        # fork failed
        os._exit(-1)
elif pid > 0:
    # parent process
    time.sleep(30) # do something
    os.waitpid(pid, 0) # explicitly reap the immediate suicide of child
    time.sleep(90) # do something
else:
    # fork failed
    sys.exit(-1)

起動直後は次のようになり

$ python orphand.py &
[1] 19316

$ ps xo pid,ppid,pgid,sid,tty,time,args
  PID  PPID  PGID   SID TT           TIME COMMAND
 9016  1620  9016  9016 pts/2    00:00:01 /bin/bash
 ...
19316  9016 19316  9016 pts/2    00:00:00 python orphand.py
19317 19316 19316  9016 pts/2    00:00:00 python orphand.py
19318 19317 19316  9016 pts/2    00:00:00 python orphand.py

親(pid=19317)はすぐに自殺するのでゾンビとして残る。親が死んだ子供(pid=19318)は root(pid=1) が新しい親になる。

$ ps xo pid,ppid,pgid,sid,tty,time,args
  PID  PPID  PGID   SID TT           TIME COMMAND
 9016  1620  9016  9016 pts/2    00:00:01 /bin/bash
 ...
19316  9016 19316  9016 pts/2    00:00:00 python orphand.py
19317 19316 19316  9016 pts/2    00:00:00 [python] <defunct>
19318     1 19316  9016 pts/2    00:00:00 python orphand.py

親のゾンビプロセス(pid=19317)はすぐさま回収される。孤児プロセス(pid=19318)が終了しても root がすぐに回収してくれるので、ゾンビとして残らない。

$ ps xo pid,ppid,pgid,sid,tty,time,args
  PID  PPID  PGID   SID TT           TIME COMMAND
 9016  1620  9016  9016 pts/2    00:00:01 /bin/bash
 ...
19316  9016 19316  9016 pts/2    00:00:00 python orphand.py
19318     1 19316  9016 pts/2    00:00:00 python orphand.py

orphaned process と親プロセスの関係は同書の「§5.13 プロセスが作る木構造」に説明がある。孤児になる前後の親プロセスIDを出力するプログラム5.12(pp.176-177)を Python に移植すると、次の様になる。

import os
import sys
import time
if os.fork() == 0:
    # child process
    time.sleep(3)
    print 'my parent is now pid %d' % os.getppid() # pid:1 = init process
    sys.exit(0)
else:
    # parent process
    print 'parent process, pid %d' % os.getppid()
    sys.exit(0)

関連リンク

MEMO3
件のプログラムは、fork で子供をたくさん生成し、その時に子プロセスには 親プロセスとは別のシグナルハンドラーを設定。ある条件がそろうとシグナル送信 killpg(getpgrp(), SIGTERM) するようになっていた。

Advertisements
Tagged with: , , , , ,
Posted in linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
  • RT @__apf__: How to write a research paper: a guide for software engineers & practitioners. docs.google.com/presentation/d… /cc @inwyrd 4 months ago
  • RT @HayatoChiba: 昔、自然と対話しながら数学に打ち込んだら何かを悟れるのではと思いたち、専門書1つだけ持ってパワースポットで名高い奈良の山奥に1週間籠ったことがある。しかし泊まった民宿にドカベンが全巻揃っていたため、水島新司と対話しただけで1週間過ぎた。 それ… 4 months ago
  • RT @googlecloud: Ever wonder what underwater fiber optic internet cables look like? Look no further than this deep dive w/ @NatAndLo: https… 4 months ago
  • @ijin UTC+01:00 な時間帯で生活しています、、、 10 months ago
  • RT @mattcutts: Google's world-class Site Reliability Engineering team wrote a new book: amazon.com/Site-Reliabili… It's about managing produc… 1 year ago
%d bloggers like this: