sleep/wakeup関数のその後

先日、UNIX V6におけるsleep/wakeup関数のことを書いた。Plan 9もsleep/wakeup関数を持っていると書いたが、いまどきのOSではどのような実装になっているのか、フォローアップしていきたいと思う。
Linuxにはsleep_on/wake_upって関数がある。実際にはinterruptible(シグナルを受け付ける)*1かどうか、タイムアウトを指定するかどうかなどのバリエーションがある。ただし、あとで触れるけど、sleep_onは使ってはいけない。
FreeBSDだとsched_sleep/sched_wakeupってのがあるなぁ。

Linuxのsleep_onはwake_upイベントを失う可能性があるので推奨されない。つまり二つのプロセスがいて、たまたまsleep_onの前にwake_upが実行されてしまうと、sleep_onしたプロセスは永遠の眠りに就いてしまう。interruptibleじゃなければ、シグナルも受け付けないのでkillすることもできない。タイムアウト時間を指定しなかった場合は、LONG_MAX jiffies待てばタイムアウトするが、そんな悠長なことは言ってられない。

このようなレースコンディションを回避するために、sleep_onの代わりにwait_eventマクロを使うことが推奨されている。wait_eventもsleep_onと同様に内部的には、イベント待ち合わせ用のwait queueにつないで、schedule関数によってCPUを放棄しているが、レースコンディションが起きないように、wait queue操作とイベント成立判定の順番を工夫している。

sleepとwakeupのレースコンディションというのは、古典的な問題で、単純に考えればwakeupされたことを覚えておくビットがあればよい。競合するプロセスが増えた場合はビット数を増やせばいいのだが、あまり本質的な解にはならない。そういう経緯でDijkstraが提案したのがセマフォだったはずである。

*1:UNIX V6ではスリープ優先度ってのがあって、それによってプロセスの状態がSSLEEPとSWAITに区別されていた。基本的な考えは一緒。