シグナルハンドラ内では、malloc()やprintf()系が使えず、thread safe な関数しか呼べないので、シグナルハンドラではキューに記録して、メインルーチン内でキューを取り出して処理する形式を考えてみた。
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <ucontext.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define TM_ONESHOT 1
#define TM_INTERVAL 0
#define QUEMAX 256
timer_t timerid;
volatile static int head = 0,tail=0;
volatile static int event_queue[QUEMAX];
static int sigint_sigevent(int signum, siginfo_t *info, void *ctx){
int ret;
ret = 0;
if(signum>0){ // シグナルによる呼び出し
event_queue[tail++] = signum;
if(tail>=QUEMAX){
tail = 0;
}
if(head==tail){
head++;
if(head>=QUEMAX){
head = 0;
}
}
}else{
if(head!=tail){
ret = event_queue[head++];
if(head>=QUEMAX){
head = 0;
}
}
}
return ret;
}
int set_signal_handler(void){
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_SIGINFO|SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask,SIGINT);
sigaddset(&sa.sa_mask,SIGRTMIN+1);
sa.sa_sigaction = (void *)sigint_sigevent;
if(sigaction(SIGINT,&sa,(struct sigaction *)NULL)) {
perror("sigaction error");
exit(1);
}
sa.sa_sigaction = (void *)sigint_sigevent;
if(sigaction(SIGRTMIN+1,&sa,(struct sigaction *)NULL)) {
perror("sigaction error");
exit(1);
}
return 0;
}
int set_timer_count(int oneshot,int sec,int nsec){
struct itimerspec ispec;
if(oneshot!=TM_INTERVAL){
ispec.it_interval.tv_sec = 0;
ispec.it_interval.tv_nsec = 0;
}else{
ispec.it_interval.tv_sec = sec;
ispec.it_interval.tv_nsec = nsec;
}
ispec.it_value.tv_sec = sec;
ispec.it_value.tv_nsec = nsec;
if(timer_settime(timerid, 0, &ispec, NULL) < 0){
perror("timer_settime error");
exit(1);
}
return 0;
}
int init_timer(void){
struct sigevent se;
memset(&se, 0, sizeof(se));
se.sigev_notify = SIGEV_SIGNAL;
se.sigev_signo = SIGRTMIN+1;
if(timer_create(CLOCK_REALTIME, &se, &timerid) < 0){
perror("timer_create error");
exit(1);
}
return 0;
}
int main(int argc, char** argv)
{
init_timer();
set_timer_count(TM_INTERVAL,0,500000000);
set_signal_handler();
while(1){
printf("%d:%d sigevent:%d\n",head,tail,sigint_sigevent(0,NULL,NULL));
while(usleep(50000)!=0);
}
return 0;
}
実際に動作させてみると、0.5秒毎にタイマーが起動してキューにシグナル番号が積まれるが、メインルーチンで、0.05秒毎にキューをチェックするので、キューに積まれるシグナル番号は殆ど増える事は無く処理される。
30:30 sigevent:0
30:30 sigevent:0
30:30 sigevent:0
30:30 sigevent:0
30:30 sigevent:0
30:30 sigevent:0
31:31 sigevent:35
31:31 sigevent:0
31:31 sigevent:0
31:31 sigevent:0
31:31 sigevent:0
31:31 sigevent:0
31:31 sigevent:0
実行すると、こんな感じ。
表示しているのは、
キュー先頭(head):キュー末尾(tail) sigevent:シグナル番号
というフォーマットになっている。
しかし、Ctrl-Cを押しっぱなしにすると、メインルーチンでは、usleep()から抜けないので、キューにSIGINTが積まれまくる。
130:130 sigevent:0
131:131 sigevent:35
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C132:42 sigevent:2
133:42 sigevent:2
134:42 sigevent:2
135:43 sigevent:2
136:43 sigevent:2
137:43 sigevent:2
138:43 sigevent:2
139:43 sigevent:2
140:43 sigevent:2
141:43 sigevent:2
142:43 sigevent:2
143:43 sigevent:2
144:44 sigevent:2
145:44 sigevent:35
ちなみにこのキューはオーバーフローすると、古いデータを捨てるようになっている。オーバフローを確認する方法は作ってない(笑)。
Ctrl-Cを離すと、メインルーチンでキューに溜まっているデータをせっせと処理し、キューの先頭のheadの数字と末尾のtailの数字がみるみる接近していくのが見て取れる。
これで、メインルーチンでシグナル割り込みを検知し、malloc()、printf()使いたい放題。
難点は、ちょっとリアルタイム性が悪くなるという点だな。
このプログラムはリアルタイムライブラリを使うので、コンパイル時には -lrt オプションがいる。

