Edited at

signalfdの使い方

More than 1 year has passed since last update.

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

ということで、メインのループ処理をこんな感じにします。


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の初期化

/**

* 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しかないので…


signalfd_read

int signalfd_read (int sfd, struct signalfd_siginfo *info) {

return read (sfd, info, sizeof(*info));
}

こんな感じのWrapperがあっても(eventfd_readとかはあるから)いいのかも、と思いつつ、これだけならなぁ…


関連

timerfdの使い方

eventfdの使い方