epoll
と合わせて使うと嬉しい系FDの一つsignalfd
の使い方をちょっと纏めてみる
基本的な考え方は、これまでマルチスレッドでシグナル受信の為だけにsigwait
で受信待ちだけしていたスレッドで、IO多重化するなどの使い方がやりやすいのではないかと思います
従来の処理
従来の、というよりはepoll
/signalfd
が無い非Linux環境だとこんな感じで処理するスレッドを設けます。
/**
* SignalLoop - シグナルの受信待ち/受信後の処理を行う
*/
void SignalLoop (sigset_t *mask)
{
while (!done) {
int signum = 0;
sigwait (mask, &signum);
switch (signum) {
case SIGINT:
case SIGTERM:
done = true;
break;
case SIGCHLD:
WaitChild ();
break;
default:
break;
}
}
return ;
}
メインのスレッド関数からSignalLoopを起動してループして、sigwait
で取得したシグナルを判断→処理を繰り返す様な感じです。
これくらいの数ならシンプルでわかりやすいとは思うのですがw
signalfd + epoll
ということで、メインのループ処理をこんな感じにします。
/**
* MyOperation_t : コマンドID
*/
typedef enum {
MYOP_ERROR = -1,
MYOP_NONE = 0,
MYOP_QUIT,
MYOP_WAITCHILD,
/* snip */
} MyOperation_t;
/**
* AnalyseEvent - epoll_eventから任意のコマンドIDを導く
*/
MyOperation_t AnalyseEvent (struct epoll_event *event)
{
if (event->data.fd == sigFD) {
struct signalfd_siginfo info = {0};
read (sigFD, &info, sizeof(info));
switch (info.ssi_signo) {
case SIGINT:
case SIGTERM:
return MYOP_QUIT;
case SIGCHLD:
return MYOP_WAITCHILD;
default:
break;
}
}
else if (event->data.fd == cliFD) {
/* snip */
}
/* else if (event->data.fd == any_other_fd) { ... } */
return MYOP_NONE;
}
/**
* DispatchEvent - 指定されたコマンドIDによる処理分岐
*/
void DispatchEvent (MyOperation_t mycmd)
{
switch (mycmd) {
case MYOP_QUIT:
done = true;
break;
case MYOP_WAITCHILD:
WaitChild ();
break;
default:
break;
}
return ;
}
/**
* EpollLoop - イベント待ちおよびイベント発生時の処理起動
*/
void EpollLoop (void)
{
while (!done) {
struct epoll_event ev = {0};
int evnum = epoll_wait (epollFD, &ev, 1, -1);
for (int i=0; i<evnum; ++i) {
MyOperation_t mycmd = AnalyseEvent (&ev);
DispatchEvent (mycmd);
}
}
return ;
}
EpollLoopではepoll_wait
を行った結果をAnalyseEventに判断させているのですが、ここではまだ他のFDによる処理分岐の可能性を残すことが出来ています。
cliFDとして、コマンドラインインタフェースを(標準入力とかローカルドメインソケットとか) AnalyseEvent内の分岐可能性として一つの関数内で整理してみたり…
その後、DispatchEventで実際の処理を行うわけですが、これはSIGINT/TERMで終了シグナル処理を行ったり/CLIで"quit"と入力されたり、など幾つかの方法から同じ処理を行うことが出来ます。
コード量は増えますが、後々でメンテナンスなどをして行くにはこれくらいの方が楽な場合が多いと思ってます。
signalfdの初期化
基本的にはsigprocmaskと合わせて生成するのが一番わかりやすい気がしています(maskの使い回しもできるw)
/**
* SignalFD_Init - プロセスマスクを設定してsignalfdを生成
*/
int SignalFD_Init (void)
{
sigset_t mask;
sigemptyset (&mask);
sigaddset (&mask, SIGINT);
sigaddset (&mask, SIGTERM);
sigaddset (&mask, SIGCHLD);
sigprocmask (SIG_BLOCK, &mask, NULL);
return signalfd (-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
}
この後、signalfd
のマスクを設定したい場合なども、必ずプロセスマスクとの関係は気にする必要があるのですが…
その辺りはsignalfd
を使うかどうかに限らず、毎度悩まされるシグナル関連のアレコレになるかなと。
その他
signalfd
に対する操作としては、固定サイズでのread
しかないので…
int signalfd_read (int sfd, struct signalfd_siginfo *info) {
return read (sfd, info, sizeof(*info));
}
こんな感じのWrapperがあっても(eventfd_read
とかはあるから)いいのかも、と思いつつ、これだけならなぁ…