Linuxのsignalfd、送ったシグナルの数がそのまま届くとは限らない

三本の桃色のリボンが薄紫色の折りたたまれた器へ集まり、中に緑色の小石が一つ置かれた抽象的な構図。

終了や設定の再読込みを知らせるシグナルを、ソケットと同じイベントループで扱いたい。Linuxのsignalfdは、そのためにシグナルをファイルディスクリプタから読み取れるようにする仕組みです。ただし、読み取り口ができても、シグナルが一件ずつ保存されるメッセージ列に変わるわけではありません。

特に同じ標準シグナルを何度も送る設計では、「三回送ったから三回処理できる」という期待が崩れます。何が読み取れ、何が失われるのかを、保留状態から見ていきます。

簡単にまとめると?

  • signalfdは、保留中の対象シグナルを構造体として読み取る入口です。通常は対象をブロックし、既定の動作へ渡らないようにします。
  • 同じ標準シグナルが保留中に繰り返し発生しても、保留は一つです。後からsignalfdで読んでも、発生回数は復元できません。
  • 「状態を確認してほしい」という合図と、「三件の仕事を保存する」という用途を分けます。件数や内容が必要なら、それを保持する別の仕組みが必要です。

保留とは、まだ受け取っていない合図

シグナルは、プロセスやスレッドに出来事を知らせる仕組みです。シグナルマスクは、そのスレッドがいま受け取らずにブロックする種類を指定します。発生してから受け取られるまでの状態が「保留(pending)」です。

signalfdには、読み取りたいシグナルの集合を指定します。しかし、集合を渡すだけでは、それらのシグナルをブロックしたことにはなりません。通常は単一スレッドならsigprocmask、複数スレッドならpthread_sigmaskなどでマスクを整え、シグナルハンドラや既定の動作に先に渡らないようにします。

プロセス全体へ送られたシグナルは、ブロックしていない別のスレッドに渡る場合があります。イベントループのスレッドだけを設定して「すべてsignalfdに来る」と考えるのは危険です。また、SIGKILLとSIGSTOPはsignalfdで受け取れません。

三回送っても、一つしか保留されない場合

標準シグナルのSIGUSR1を使う例を考えます。受信側はこれをブロックしており、途中でsignalfdから読み取ったり、他の経路で消費したりしないとします。

  1. 一回目のSIGUSR1が発生すると、「SIGUSR1が保留中」という状態になります。
  2. そのまま二回目、三回目が発生しても、標準シグナルは同じ種類の通知を列に積みません。保留中という状態が続きます。
  3. その後signalfdで読むと、この種類について取り出せる通知は一つです。
読み取らずに同じ標準シグナルが三回発生すると保留は一つ。毎回読み取りを挟む三回なら、三つの通知をそれぞれ消費できる。
同じSIGUSR1だけを扱い、他の受信経路がない場合の状態モデル。時刻やLinuxでの実測結果を表す図ではありません。

一方、一回発生するたびに読み取って保留を解消し、その後に次が発生するなら、合計三回取り出せます。違いは送信回数だけでなく、発生と消費がどう重なったかにあります。負荷が軽い試験では三回届いていても、受信側が忙しくなると一回にまとまることがあります。

すでに同じ標準シグナルが保留されている場合、付随するsiginfo_tの情報も後の発生で上書きされません。最初の情報が残ります。「最後の送信者の情報が届く」「回数だけ後で数え直せる」とは考えられません。

readの戻り値と、シグナルの件数も分ける

signalfdのreadは、signalfd_siginfoという構造体を一つ以上返します。読み取りバッファには少なくとも一つの構造体が入る大きさが必要で、保留中の通知のうちバッファに入る分を取り出します。戻り値はバイト数です。構造体の個数は、このバイト数を構造体のサイズで割って求めます。

ただし、それで分かるのは今回読み取れた通知の個数であって、過去の全発生回数ではありません。読み取りによって通知は消費されるため、同じ通知が後でハンドラやsigwaitinfoへもう一度渡ることもありません。

対象の通知が保留されていなければ、通常の読み取りは待ちます。SFD_NONBLOCKを指定した場合は、待つ代わりにEAGAINになります。pollやepollが示す読み取り可能という状態も、「何件の仕事が完了したか」ではなく、取り出せる通知があることを知らせます。

合図として使うか、仕事の記録に使うか

設定ファイルの再読込みを促す用途なら、複数の合図が一つにまとまっても、受信時点の設定を改めて読む設計が考えられます。ここで必要なのは、「何か変わったかもしれないので、実際の状態を確認する」というきっかけです。

対して、一回の通知を一件の注文や一回の課金処理と対応させる設計では、合流がそのまま取りこぼしになります。個々の仕事はキューなどに保存し、シグナルはそのキューを見に行く合図として使う、という役割分担が必要です。キューの排出と新しい合図の発生が競合する場面も含めて設計します。

リアルタイムシグナルは複数の発生をキューに保持できますが、無制限ではありません。標準シグナルの性質を変える設定としてsignalfdを使うことはできず、リアルタイムシグナルを選ぶ場合にも保留数の制約や送信失敗への対応が残ります。

また、signalfdの読み取りで受け取れるのは、呼出しスレッド自身へ向けられた通知と、プロセスへ向けられた通知です。別スレッドだけに向けたシグナルを、監視用スレッドから横取りする機能ではありません。種類、宛先、マスク、保持したい情報を分けると、signalfdに任せる範囲が明確になります。

出典