Pythonで学ぶ「詳解 UNIXプログラミング」(その14) 第14章 プロセス間通信
14.1 パイプを介して親から子へデータを転送する
import sys, os r, w = os.pipe() pid = os.fork() if pid: # parent os.close(r) os.write(w, "hello world\n") else: # child os.close(w) line = os.read(r, 80) print line
14.2 ページ表示プログラムへファイルをコピーする
import sys, os if len(sys.argv) != 2: sys.exit("usage: 14.2.py <pathname>") fp = open(sys.argv[1]) r, w = os.pipe() pid = os.fork() if pid: # parent os.close(r) # close read end for line in fp: os.write(w, line) os.close(w) # close write end of pipe for reader os.waitpid(pid, 0) else: # child os.close(w) # close write end if r != sys.stdin.fileno(): os.dup2(r, sys.stdin.fileno()) os.close(r) # don't need this after dup2() pager = os.environ.get('PAGER', '/bin/more') argv0 = os.path.split(pager)[1] # strip past rightmost slash os.execl(pager, argv0)
14.3 親と子を同期するルーティン
import os, sys pfd1 = pfd2 = None def TELL_WAIT(): global pfd1, pfd2 pfd1 = os.pipe() pfd2 = os.pipe() def TELL_PARENT(pid): os.write(pfd2[1], "c") def WAIT_PARENT(): c = os.read(pfd1[0], 1) if c != "p": sys.exit("WAIT_PARENT: incorrect data") def TELL_CHILD(pid): os.write(pfd1[1], "p") def WAIT_CHILD(): c = os.read(pfd2[0], 1) if c != "c": sys.exit("WAIT_CHILD: incorrect data")
14.4 popenを用いたページ表示プログラムへのファイルのコピー
import sys, os if len(sys.argv) != 2: sys.exit("usage: 14.4.py <pathname>") PAGER = "${PAGER:-more}" fpin = open(sys.argv[1]) fpout = os.popen(PAGER, "w") for line in fpin: fpout.write(line) fpout.close()
os.popen()
はPython2.6からdeprecateとなっており、代わりにsubprocess
モジュールを使うことになっている。
import sys, subprocess if len(sys.argv) != 2: sys.exit("usage: 14.4.py <pathname>") PAGER = "${PAGER:-more}" fpin = open(sys.argv[1]) process = subprocess.Popen(PAGER, shell=True, stdin=subprocess.PIPE) for line in fpin: process.stdin.write(line) process.stdin.close() process.wait()
14.5 popenとpclose関数
import sys, os, errno STDIN_FILENO = 0 STDOUT_FILENO = 1 SHELL = "/bin/sh" childpid = {} def popen(cmdstring, type): if type not in "rw": raise ValueError() pfd = os.pipe() pid = os.fork() if pid == 0: # child if type == 'r': os.close(pfd[0]) if pfd[1] != STDOUT_FILENO: os.dup2(pfd[1], STDOUT_FILENO) os.close(pfd[1]) else: os.close(pfd[1]) if pfd[1] != STDOUT_FILENO: os.dup2(pfd[0], STDIN_FILENO) os.close(pfd[0]) # close all descriptors in childpid for fd in childpid.keys(): os.close(fd) os.execl(SHELL, "sh", "-c", cmdstring) sys.exit(127) # parent if type == 'r': os.close(pfd[1]) fp = os.fdopen(pfd[0], type) else: os.close(pfd[0]) fp = os.fdopen(pfd[1], type) # remember child pid for this fd childpid[fp.fileno()] = pid return fp def pclose(fp): if not childpid: raise RuntimeError("popen() has never been called") fd = fp.fileno() if fd not in childpid: raise RuntimeError("fp wasn't opened by popen()") pid = childpid[fd] del childpid[fd] fp.close() while True: try: stat = os.waitpid(pid, 0) break except OSError, e: if e.errno != errno.EINTR: raise return stat
14.6 大文字を小文字に変換するフィルタ
while True: c = sys.stdin.read(1) if not c: break sys.stdout.write(c.lower()) if c == '\n': sys.stdout.flush()
14.7 コマンドを読み取るための大文字/小文字フィルタの起動
import sys fpin = popen("./14.6.py", "r") while True: sys.stdout.write("prompt> ") sys.stdout.flush() line = fpin.readline() if not line: break sys.stdout.write(line) pclose(fpin) sys.stdout.write("\n")
14.8 2つの数値を加算する単純なフィルタ
import sys def _read(): fd = sys.stdin.fileno() line = [] while True: c = os.read(fd, 1) if not c: if line: yield "".join(line) break line.append(c) if c == '\n': yield "".join(line) line = [] for line in _read(): os.write(1, "{0}\n".format(int(line[0]) + int(line[1])))
14.9 add2フィルタを駆動するプログラム
import signal, sys, os def sig_pipe(signo, frame): print "SIGPIPE caught" signal.signal(signal.SIGPIPE, sig_pipe) fd1 = os.pipe() fd2 = os.pipe() pid = os.fork() if pid: # parent os.close(fd1[0]) os.close(fd2[1]) while True: line = sys.stdin.readline() if not line: break os.write(fd1[1], line) ret = [] while True: c = os.read(fd2[0], 1) if not c or c == "\n": break ret.append(c) if c == '\n': break print "".join(ret) else: # child os.close(fd1[1]) os.close(fd2[0]) if fd1[0] != 0: os.dup2(fd1[0], 0) os.close(fd1[0]) if fd2[1] != 1: os.dup2(fd2[1], 1) os.close(fd2[1]) os.execl("./14.8.py", "14.8.py")
14.10 標準入出力を用いる2つの数値を加算する単純なフィルタ
import sys for line in sys.stdin: sys.stdout.write("{0}\n".format(int(line[0]) + int(line[1])))
14.11 異なるデータ型を割り付けた場所を表示する
SytemVの共有メモリは、Pythonの標準モジュールではサポートされていないのでパス。
14.12 /dev/zeroのメモリマップ入出力を用いた親と子のIPC
import os, mmap, struct, sys SIZE = 4 NLOOPS = 1000 fd = os.open("/dev/zero", os.O_RDWR) area = mmap.mmap(fd, SIZE) os.close(fd) TELL_WAIT() def update(area): cur, = struct.unpack("L", area) area[:] = struct.pack("L", cur+1) return cur pid = os.fork() if pid: # parent for i in range(0, NLOOPS, 2): cur = update(area) if cur != i: sys.exit("parent: expected {0}, got {1}".format(i, cur)) TELL_CHILD(pid) WAIT_CHILD() else: # child for i in range(1, NLOOPS, 2): WAIT_PARENT() cur = update(area) if cur != i: sys.exit("child: expected {0}, got {1}".format(i, cur)) TELL_PARENT(os.getppid())
システムVの流儀で/dev/zero
を使って共有メモリを作成しているが、Pythonのmmap
モジュールでは/dev/zero
を指定するよりも、
area = mmap.mmap(-1, SIZE)
とした方がよい。このように指定すれば、システムVでは/dev/zero
、BSD系ならばアノニマスメモリマップによる共有メモリを作成する。