マルチスレッドのプログラミング

シグナルハンドラと「非同期シグナル安全」

スレッドに対する安全性と似た概念に、「非同期シグナル安全」があります。「非同期シグナル安全」操作は、割り込まれている操作を妨げないことが保証されています。

「非同期シグナル安全」に関する問題が生じるのは、現在の操作がシグナルハンドラによる割り込みで動作を妨げる可能性があるときです。

たとえば、プログラムが printf(3S) を呼び出している最中にシグナルが発生し、そのシグナルを処理するハンドラ自体も printf() を呼び出すとします。その場合は、2 つの printf() 文の出力が混ざり合ってしまいます。これを避けるには、printf() がシグナルに割り込まれたときにシグナルハンドラが printf() を呼び出さないようにします。

この問題は、同期プリミティブでは解決できません。シグナルハンドラと同期対象操作の間で同期をとろうとすると、たちまちデッドロックが発生するからです。

たとえば、printf() が自分自身を相互排他ロックで保護していると仮定します。あるスレッドが printf() を呼び出している最中に、つまり相互排他ロックを保持した状態にある時に、シグナルにより割り込まれたとします。

(printf() 内部にいるスレッドから呼び出されている) ハンドラ自身が printf() を呼び出すと、すでに相互排他ロックを保持しているスレッドが、もう一度相互排他ロックを獲得しようとします。その結果、即座にデッドロックとなります。

ハンドラと操作の干渉を回避するには、そうした状況が決して発生しないようにするか (通常は危険領域でシグナルをマスクする)、シグナルハンドラ内部では「非同期シグナル安全」操作以外は使用しないようにします。

スレッドのマスクを設定することは、負荷の小さなユーザレベルの操作であるため、関数やプログラムの一部分を「非同期シグナル安全」のために修正しても負荷は大きくなりません。

POSIX が「非同期シグナル安全」を保証しているルーチンだけを表 5-2 に示します。どのようなシグナルハンドラも、これらの関数を安全に呼び出すことができます。

表 5-2 「非同期シグナル安全」関数

_exit()

fstat()

read()

sysconf()

access()

getegid()

rename()

tcdrain()

alarm()

geteuid()

rmdir()

tcflow()

cfgetispeed()

getgid()

setgid()

tcflush()

cfgetospeed()

getgroups()

setpgid()

tcgetattr()

cfsetispeed()

getpgrp()

setsid()

tcgetpgrp()

cfsetospeed()

getpid()

setuid()

tcsendbreak()

chdir()

getppid()

sigaction()

tcsetattr()

chmod()

getuid()

sigaddset()

tcsetpgrp()

chown()

kill()

sigdelset()

time()

close()

link()

sigemptyset()

times()

creat()

lseek()

sigfillset()

umask()

dup2()

mkdir()

sigismember()

uname()

dup()

mkfifo()

sigpending()

unlink()

execle()

open()

sigprocmask()

utime()

execve()

pathconf()

sigsuspend()

wait()

fcntl()

pause()

sleep()

waitpid()

fork()

pipe()

stat()

write()