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