このBlogは移転しました。今後は aish.dev を御覧ください。

Pythonで学ぶ「詳解 UNIXプログラミング」(その8) 第8章 プロセス制御

8.1 fork関数の例

import sys, os, time

glob = 6
buf = "a write to stdout\n"
STDOUT_FILENO = sys.stdout.fileno()

def main():
    global glob
    var = 88    # automatic variable on the stack
    os.write(STDOUT_FILENO, buf) # we don't flush stdout
    print "before fork"
    
    pid = os.fork()
    if pid == 0:     # child
        glob += 1    # modify variables
        var += 1
    else:                # parent
        time.sleep(2)
    
    print "pid={0}, glob={1}, var={2}".format(os.getpid(), glob, var)
    
main()

8.2 vfork関数の例

Pythonでは、vfork()は提供されていない。

8.3 プロセスの終了状態を出力する

import os

def pr_exit(status):
    if os.WIFEXITED(status):
        print "normal termination, exit status = {0}".format(os.WEXITSTATUS(status))
    elif os.WIFSIGNALED(sttus):
        print "abnormal termination, signal number = {0}{1}".format(
            os.WTERMSIG(status), " (core file generated)" if os.WCOREDUMP(status) or "")
    elif os.WIFSTOPPED(status):
        print "child stopped, signal number = {0}".format(os.WSTOPSIG(status))

8.4 様々な終了状態を出力する

import sys, os

pid = os.fork()
if pid == 0: # child
    sys.exit(7) 

childpid, status = os.wait()   # wait for child
pr_exit(status)

pid = os.fork()
if pid == 0: #child 
    os.abort()    # generate SIGABORT

childpid, status = os.wait()   # wait for child
pr_exit(status)

pid = os.fork()
if pid == 0: # child
    1/0

childpid, status = os.wait()   # wait for child
pr_exit(status)

pr_exit()関数は、サンプル8.3を参照。C言語版のサンプルとは異なり、Pythonでは1/0としてもSIGFPEは発生しないので注意。

8.5 forkを2度呼んでゾンビプロセスを避ける

import os, time, sys

pid = os.fork()
if pid == 0: # 一つ目の子プロセス
    pid = os.fork()
    if pid > 0: # 2回目のfork()の親プロセス (=一つ目の子プロセス)
        print "exit 0"
        sys.exit(0)
    
    # 二つ目の子プロセス
    # このプロセスの本来の親プロセスは起動後すぐに sys.exit() を呼び出すので、
    # 親プロセスが init に変更される。
    # このプロセスが終了すると、終了状態は init で解放される
    
    time.sleep(2)
    print "second child, parent pid = {0}".format(os.getppid())
    sys.exit(0)

os.waitpid(pid, 0)

8.6 レースコンディションを有するプログラム

import sys, os, time

def charatatime(s):
    for c in s:
        sys.stdout.write(c)
        sys.stdout.flush()
        time.sleep(0.1)
            
pid = os.fork()
if pid == 0:
    charatatime("output from child\n")
else:
    charatatime("output from parent\n")

あっという間に終わってしまって競合してくれないので、time.sleep()を追加している。

8.7 プログラム8.6をレースコンディションを避けるように変更

import sys, os, time, signal, select, fcntl

def charatatime(s):
    for c in s:
        sys.stdout.write(c)
        sys.stdout.flush()
        time.sleep(0.1)
            
def TELL_WAIT():
    global rd, wr
    rd, wr = os.pipe()
    flags = fcntl.fcntl(wr, fcntl.F_GETFL, 0) | os.O_NONBLOCK
    fcntl.fcntl(wr, fcntl.F_SETFL, flags)
    signal.set_wakeup_fd(wr)
    signal.signal(signal.SIGUSR1, lambda signum, frame:None)

def WAIT_PARENT():
    # wait for parent to terminate
    while True:
        try:
            select.select([rd], [], [])
        except select.error, e:
            if os.read(rd, 1):
                break
            else:
                raise

        while True:
            # consume pipe
            if not os.read(rd, 64):
                break

def TELL_CHILD(pid):
    os.kill(pid, signal.SIGUSR1)

TELL_WAIT()

pid = os.fork()
if pid == 0:
    WAIT_PARENT()
    charatatime("output from child\n")
else:
    charatatime("output from parent\n")
    TELL_CHILD(pid)

ここでは、Python 2.6で追加されたsignal.set_wakeup_fd()を使って同期を取ってみた。もう少し綺麗に書く方法はないのだろうか?

8.8 exec関数の例

import os

pid = os.fork()
if pid == 0:
    os.execle(
        "./echoall", "echoall", "myarg1", "MY ARG2", 
            {"USER":"unknown"})
os.waitpid(pid, 0)

pid = os.fork()
if pid == 0:
    os.execlp("./echoall", "echoall", "only one arg")

8.9 全てのコマンド行引数と全ての環境変数を出力する

#! /usr/bin/env python

import sys, os

# echo all args
for n, arg in enumerate(sys.argv):  
    print "argv[{0}]: {1}".format(n, arg)

# echo all env strings
for name, value in os.environ.iteritems():
    print "{0}:{1}".format(name, value)

8.10 解釈実行ファイルをexecするプログラム

import os

pid = os.fork()
if pid == 0:
    os.execl(
        "./testinterp", "testinterp", "arg1", "MY ARG2")
os.waitpid(pid, 0)

8.11 解釈実行ファイルに入れたPythonプログラム

#! /usr/bin/python 
import sys
for n, arg in enumerate(sys.argv):
    print "ARG[{0}] = {1}".format(n, arg)

元のサンプルはawkプログラムだが、ここは当然Pythonスクリプトとした。Pythonでは-fを使わないので、ちょっとサンプルの意味が変わってきてしまうが…

Pythonでは、一般に/usr/bin/pythonなどと絶対パスを指定するのではなく、

#! /usr/bin/env python 
import sys
for n, arg in enumerate(sys.argv):
    print "ARG[{0}] = {1}".format(n, arg)

として、環境変数PATHに従って起動するように指定することが多い。

8.12 (シグナルを処理しない)システム関数

import os, sys, errno

pid = os.fork()
if pid == 0:
    try:
        os.execl("/bin/sh", "sh", "-c", sys.argv[1])
    except Exception:
        sys.exit(127)
else:
    while True:
        try:
            pid, status = os.waitpid(pid, 0)
            break
        except OSError, e:
            if e.errno == errno.EINTR:
                status = -1
                break

sys.exit(status)

8.13 system関数を呼ぶ

import os

status = os.system("date")
pr_exit(status)

status = os.system("nosuchcommand")
pr_exit(status)

status = os.system("who; exit 44")
pr_exit(status)

pr_exit()関数は、サンプル8.3を参照。

8.14 systemを用いてコマンド行引数を実行する

import os, sys
status = os.system(sys.argv[1])
pr_exit(status)

8.15 実ユーザIDおよび実効ユーザIDを出力する

import os
print "real uid = {0}, effective uid = {1}".format(os.getuid(), os.geteuid())

8.16 実効記録データを生成するためのプログラム

import os, time, sys, signal

pid = os.fork()
if pid != 0:            # parent
    time.sleep(2)
    sys.exit(2)

pid = os.fork()
if pid != 0:            # first child
    time.sleep(4)
    os.abort()
    
pid = os.fork()
if pid != 0:            # second child
    os.execl("/usr/bin/dd", "dd", "if=/boot", "of=/dev/null")
    sys.exit(7)

pid = os.fork()
if pid != 0:            # third child
    time.sleep(8)
    sys.exit(0)

# fourth child
time.sleep(8)
os.kill(os.getpid(), signal.SIGKILL)
sys.exit(6)

8.17 システムの実効記録ファイルの特定フィールドを出力する

from ctypes import *

ACCT_COMM = 16

class acct_v3(Structure):
    AFORK = 0x01  # Has executed fork, but no exec.
    ASU = 0x02    # Used super-user privileges.
    ACORE = 0x08  #Dumped core.
    AXSIG = 0x10  # Killed by a signal.

    _fields_ = [
        ("ac_flag", c_byte),          # Flags 
        ("ac_version", c_char),       # Always set to ACCT_VERSION 
        ("ac_tty", c_uint16),         # Control Terminal 
        ("ac_exitcode", c_uint32),    # Exitcode 
        ("ac_uid", c_uint32),         # Real User ID 
        ("ac_gid", c_uint32),         # Real Group ID 
        ("ac_pid", c_uint32),         # Process ID 
        ("ac_ppid", c_uint32),        # Parent Process ID 
        ("ac_btime", c_uint32),       # Process Creation Time 
        ("ac_etime", c_float),        # Elapsed Time 
        ("_ac_utime", c_uint16),       # User Time 
        ("_ac_stime", c_uint16),       # System Time 
        ("_ac_mem", c_uint16),         # Average Memory Usage 
        ("_ac_io", c_uint16),          # Chars Transferred 
        ("_ac_rw", c_uint16),          # Blocks Read or Written 
        ("_ac_minflt", c_uint16),      # Minor Pagefaults 
        ("_ac_majflt", c_uint16),      # Major Pagefaults 
        ("_ac_swaps", c_uint16),       # Number of Swaps 
        ("ac_comm", c_char*ACCT_COMM) # Command Name 
    ]


    @property
    def afork(self):
        return self.ac_flag & self.AFORK

    @property
    def asu(self):
        return self.ac_flag & self.ASU

    @property
    def acore(self):
        return self.ac_flag & self.ACORE

    @property
    def axsig(self):
        return self.ac_flag & self.AXSIG

    def _compt2ulong(self, comptime):
        val = comptime & 017777       # 13-bit fraction
        exp = (comptime >> 13) & 7    # 3-bit exponent
        while exp:
            exp -= 1
            val *= 8
        return val

    @property
    def ac_utime(self):
        return self._compt2ulong(self._ac_utime)
        
    @property
    def ac_stime(self):
        return self._compt2ulong(self._ac_stime)
        
    @property
    def ac_mem(self):
        return self._compt2ulong(self._ac_mem)
        
    @property
    def ac_io(self):
        return self._compt2ulong(self._ac_io)
        
    @property
    def ac_rw(self):
        return self._compt2ulong(self._ac_rw)
        
    @property
    def ac_minflt(self):
        return self._compt2ulong(self._ac_minflt)
        
    @property
    def ac_majflt(self):
        return self._compt2ulong(self._ac_majflt)
        
    @property
    def ac_swaps(self):
        return self._compt2ulong(self._ac_swaps)


acct_ptr = POINTER(acct_v3)
PACCT = "/var/log/account/pacct"
ACCTSIZE = sizeof(acct_v3)

f = open(PACCT, "r")
while True:
    buf = f.read(ACCTSIZE)
    if not buf:
        break
    if len(buf) != ACCTSIZE:
        print "invalid buf size", len(buf)
    acct = cast(buf, acct_ptr).contents
    
    print "{0:<16} e = {1:<8} chars = {2:<8} {3}{4}{5}{6}".format(
        acct.ac_comm, acct.ac_etime, acct.ac_io,
        "D" if acct.acore else " ", "X" if acct.axsig else " ",
        "F" if acct.afork else " ", "S" if acct.asu else " ")

標準ライブラリには実行記録ファイルを扱うモジュールがないので、ここではctypesモジュールを使って構造体を定義した。オリジナルのサンプルではacctという構造体を使用しているが、このバージョンのLinuxではacct_v3となっているようだ。

8.18 全てのコマンド行引数を実行し時間を計る

import sys, os

for arg in sys.argv[1:]:
    start = os.times()
    os.system(arg)
    end = os.times()
    
    real, utime, stime, cutime, cstime = (t-f for f, t in zip(start, end))
    print arg
    print "  real:", real
    print "  user:", utime
    print "  sys:", stime
    print "  child user:", cutime
    print "  child sys:", cstime