Apache HTTP Server や Nginx など多くの HTTPサーバでは UNIXシグナル を送信して設定ファイルや各種ログファイルを開き直したりできます。例えば Nginx のマスタープロセスは以下のシグナルをサポートしています。
シグナル | 動作 |
---|---|
SIGINT, SIGTERM | シャットダウン |
SIGHUP | 設定ファイルの再読み込み |
SIGQUIT | Gracefully shutdown |
SIGUSR1 | ログファイルの再読み込み |
SIGUSR2 | 実行ファイルの更新 |
SIGWINCH | 全ての worker processes を Gracefully shutdown |
本題ですが, あるHTTPサーバのプラグイン開発でRTSPサーバとの連携実装しようと思っていたのですが、 fd の遅延解放がどうしても必要なタイミングで適切な hook-entry が見つからなかったので、この非同期処理をシグナルを使って実装しました。
sigactionの実装は以下の通り。注意点として singal(2) ではなく sigaction(2) です。
struct sigaction {
union {
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int, struct siginfo *, void *);
} _u;
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
};
timer.it_interval.tv_secで設定した値の間隔でSIGALRMを発生させます。
中ではalarm(2)システムコールを使っていて, timerが切れた時にシグナルがカーネルからプロセスに対して通知されSignalHandlerで操作を行えます。
int main(void)
{
struct sigaction action;
struct itimerval timer;
memset(&action, 0, sizeof(action));
/* set signal handler */
action.sa_handler = SignalHandler;
action.sa_flags = SA_RESTART;
sigemptyset(&action.sa_mask);
if(sigaction(SIGALRM, &action, NULL) < 0){
perror("sigaction error");
exit(1);
}
/* set timer */
timer.it_value.tv_sec = 1;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1;
timer.it_interval.tv_usec =0;
if(setitimer(ITIMER_REAL, &timer, NULL) < 0){
perror("setitimer error");
exit(1);
}
printf("waiting timer for fire\n");
for(;;){};
return 0;
}
SignalHandler では任意の処理を行う事ができますが制約は多いです。安全なシグナルハンドラを実装するには ――C/C++セキュアコーディング入門(4)から抜粋すると以下があるそうです。
- 非同期シグナル安全 (async-signal-safe) な関数の呼び出し
- volatile sig_atomic_t型変数の読み書き
- シグナルハンドラ内の自動変数の読み書き
malloc(), free() などヒープ領域を使う関数は使用できません。アトミックでない操作はできないということです。
分かり易い例では、 main() で free() 解放中に、偶然シグナルハンドラが発生すると二重解放となる可能性があります。
また, sig_atomic_t型変数の読み書きとありますが, 実際は typedef int なのでなんとも。
typedef int sig_atomic_t;
SignalHandlerでは signum は 14(SIGALRM) が入ってきます。close(), kill(), recvfrom() などは非同期安全関数なので使用できます。
void SignalHandler(int signum)
{
close(fd);
return;
}
Shell Script からシグナルを投げる方法としては trap(1) や kill(1) があります。
kill -l でUNIXシグナルの一覧が確認できます。強制終了の SIGKILL や通常の終了シグナルの SIGTERM は馴染み深いですね。SIGUSR1, SIGUSR2 はアプリケーションで独自に定義できるシグナルです。他にも Signal がこんなにあるとは知らなかったです。
# centos 6.5
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
ちなみに SIGCHLD は fork() 等で生成した子プロセスが終了したタイミングで親プロセスに通知されるシグナルです。
[1] Linuxカーネルでは/arch/arm/include/uapi/asm/signal.hや/arch/arm/include/asm/signal.hにARM実装があります。(github)
[2] ちなみに signal の仕組みは Linux Kernel 0.01 からあります。
[3] shellのtrapについて覚え書き