Pythonで学ぶ「詳解 UNIXプログラミング」(その12) 第12章 高度な入出力
12.1 ブロックしない大量のwrite
import os, sys, fcntl buf = sys.stdin.read() print >>sys.stderr, "read {0} bytes".format(len(buf)) wrote = 0 fno = sys.stdout.fileno() val = fcntl.fcntl(fno, fcntl.F_GETFL, 0) fcntl.fcntl(fno, fcntl.F_SETFL, val | os.O_NONBLOCK) # set nonblocking while True: try: n = os.write(fno, buf) except OSError, e: print >>sys.stderr, "errno: {0}".format(e.errno) continue else: print >>sys.stderr, "wrote {0} bytes".format(n) buf = buf[n:] if not buf: break fcntl.fcntl(fno, fcntl.F_SETFL, val ) # clear nonblocking
12.2 ファイル領域のロックを設定・解除する関数
import fcntl def read_lock(fd, offset, whence, length): return fcntl.lockf(fd, fcntl.LOCK_SH|fcntl.LOCK_NB, length, offset, whence) def readw_lock(fd, offset, whence, length): return fcntl.lockf(fd, fcntl.LOCK_SH, length, offset, whence) def write_lock(fd, offset, whence, length): return fcntl.lockf(fd, fcntl.LOCK_EX|fcntl.LOCK_NB, length, offset, whence) def writew_lock(fd, offset, whence, length): return fcntl.lockf(fd, fcntl.LOCK_EX, length, offset, whence) def un_lock(fd, offset, whence, length): return fcntl.lockf(fd, fcntl.LOCK_UN, length, offset, whence)
領域を指定したファイルロックはfcntl.lockf()
で実装されている。fcntl.lockf()
ではF_RDLCK
はfcntl.LOCK_SH
、F_WDLCK
はfcntl.LOCK_EX
となるので注意。また、fcntl()
へのコマンドは、fcntl.LOCK_NB
が指定されていればF_SETLK
、指定されていなければF_SETLKW
となる。
12.3 ロックの条件をテストする関数
import fcntl, struct def lock_test(fd, type, offset, whence, length): lockdata = struct.pack('hhqqI', type, whence, offset, length, 0) ret = fcntl.fcntl(fd, fcntl.F_GETLK, lockdata) type, whence, offset, length, pid = struct.unpack('hhqqI', ret) if type == fcntl.F_UNLCK: return None # false, region is not locked by another proc else: return pid # true, return pid of lock owner def is_readlock(fd, offset, whence, lenght): return lock_test(fd, fcntl.F_RDLCK, offset, whence, length) def is_writelock(fd, offset, whence, lenght): return lock_test(fd, fcntl.F_WRLCK, offset, whence, length)
PythonではF_GETLK
のインターフェースは用意されていないため、fcntl.fcntl()
を用いて呼び出している。flock構造体の定義は環境によって異なるので注意が必要だ。Linuxであれば、今時LFSが入っていないということもないだろうが…
12.4 プログラム例 - デッドロック
# create a file and write two bytes to it f = open("templock", "w", 0) f.write("ab") fd = f.fileno() TELL_WAIT() pid = os.fork() if pid == 0: #child lockabyte("child", fd, 0) TELL_PARENT(os.getppid()) WAIT_PARENT() lockabyte("child", fd, 1) else: # parent lockabyte("parent", fd, 1) TELL_CHILD(pid) WAIT_CHILD() lockabyte("parent", fd, 0)
12.5 複数の同じデーモンが実行することを防ぐデーモン起動時のコード
import os, errno, sys, fcntl PIDFILE = "daemon.pid" pidfile = open(PIDFILE, "w", 0) fd = pidfile.fileno() try: # try and set a write lock on the entire file write_lock(fd, 0, os.SEEK_SET, 0) except IOError, e: if e.errno in (errno.EACCES, errno.EAGAIN): sys.exit(0) # gracefully exit, daemon is already running # truncate to zero length, now that we have the lock pidfile.truncate(0) # and write our process ID pidfile.write("{0}\n".format(os.getpid())) # set close-on-exit flag for descriptor val = fcntl.fcntl(pidfile, fcntl.F_GETFD, 0) fcntl.fcntl(pidfile, fcntl.F_GETFD, val | fcntl.FD_CLOEXEC) # leave file open until we terminate: lock will be held # do whatever
12.6 ファイルの終わりに対応してロックを解除する際の問題を示すプログラム
import os out = open("temp.lock", "w+", 0) fd = out.fileno() for i in range(1000000): # lock from current EOF to EOF write_lock(fd, 0, os.SEEK_END, 0) os.write(fd, "1") un_lock(fd, 0, os.SEEK_END, 0) os.write(fd, "1")
かなり長時間このスクリプトを動かしても、エラーは発生しなかった。最近のカーネルではロックの最大数等はどう管理されているんだろう?
12.7 必須ロックを使えるかどうかを調べる
import os, fcntl, stat out = open("temp.lock", "w+", 0) fd = out.fileno() out.write("abcdef") # turn on set-group-id and turn off group-execute st = os.fstat(fd) os.fchmod(fd, (st.st_mode & ~stat.S_IXGRP) | stat.S_ISGID) TELL_WAIT() pid = os.fork() if pid: # parent # write lock entire file write_lock(fd, 0, os.SEEK_SET, 0) TELL_CHILD(pid) os.waitpid(pid, 0) else: WAIT_PARENT() # wait for parent to set lock val = fcntl.fcntl(fd, fcntl.F_GETFL, 0) fcntl.fcntl(fd, fcntl.F_SETFL, val | os.O_NONBLOCK) try: read_lock(fd, 0, os.SEEK_SET, 0) except IOError, e: print "read_lock of already-locked region returns {0}".format(e.errno) else: sys.exit("child: read lock succeeded") # now try to read the mandatory locked file os.lseek(fd, 0, os.SEEK_SET) try: buf = os.read(fd, 2) except IOError, e: sys.exit("read failed (mandatory locking works") else: print "read OK (no mandatory locking), buf =", buf
普通に実行すると
read_lock of already-locked region returns 11 read OK (no mandatory locking), buf = ab
と必須ロック不可となる。
# mount -o bind,mand testdir /testdir
とディレクトリを必須ロック可にして再マウントし、もう一度やってみたが、同じ結果だった。ディレクトリを再マウントするだけではmand
が有効にならないのだろうか?わざわざ試すのも面倒なので放置。
12.12 writen関数
import os def writen(fd, buf): while buf: try: n = os.write(fd, buf) except IOError: return len(buf) buf = buf[n:]
12.13 readn関数
import os def readn(fd, n): ret = [] while n > 0: try: s = os.read(fd, n) except IOError, e: break ret.append(s) nread = len(s) if nread == 0: break n -= nread return "".join(ret)
12.14 メモリマップ入出力を用いたファイルのコピー
import sys, os, mmap, stat if len(sys.argv) != 3: sys.exit("usage: mmapcpy <fromfile> <tofile>") fdin = os.open(sys.argv[1], os.O_RDONLY) fdout = os.open(sys.argv[2], os.O_RDWR | os.O_CREAT | os.O_TRUNC, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP) st = os.fstat(fdin) # need size of input file # set size of output size os.lseek(fdout, st.st_size-1, os.SEEK_SET) os.write(fdout, " ") src = mmap.mmap(fdin, st.st_size, prot=mmap.PROT_READ) dst = mmap.mmap(fdout, st.st_size, prot=mmap.PROT_READ|mmap.PROT_WRITE) dst[:] = src[:]