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

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を使って共有メモリを作成しているが、Pythonmmapモジュールでは/dev/zeroを指定するよりも、

area = mmap.mmap(-1, SIZE)

とした方がよい。このように指定すれば、システムVでは/dev/zeroBSD系ならばアノニマスメモリマップによる共有メモリを作成する。