POSIX 環境で非同期的なタイマを使うには setitimer() を使いますが、これだとタイマが 1 個しか使えません。(正確には SIGALRM, SIGVALRM, SIGPROF という性質の違うタイマをそれぞれ 1 個ずつ)

さすがにこれでは厳しいということで、POSIX.1b には timer_create() という関数が定義されていて、これだと好きなだけタイマを作ることができます。さらに、setitimer() と違い、タイマごとに好きなシグナルを発生させることができますし、シグナルを発生させる代わりにスレッドを起動して通知関数を呼び出させることもできます。

#include <inttypes.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#if SIZEOF_PTHREAD_T == 4
#define PRI_PTHREAD_T "08" PRIx32
#define CASTPRI_PTHREAD_T uint32_t
#elif SIZEOF_PTHREAD_T == 8
#define PRI_PTHREAD_T "016" PRIx64
#define CASTPRI_PTHREAD_T uint64_t
#else
#error
#endif

const struct itimerspec its_1sec = { .it_value = { 1 } };
const struct itimerspec its_2sec = { .it_value = { 2 } };
const struct timespec ts_3sec = { 3 };

void notifyfunc(union sigval sv)
{
	printf("notifyfunc: timer has expired.\n");
	printf(" pthread_self() = %" PRI_PTHREAD_T "\n",
	     (CASTPRI_PTHREAD_T)pthread_self());
	printf(" sigval         = %d\n", sv.sival_int);
	return;
}

void sigalrm(int sig, siginfo_t *si, void *uc)
{
	printf("sigalrm: timer has expired.\n");
	printf(" pthread_self() = %" PRI_PTHREAD_T "\n",
	     (CASTPRI_PTHREAD_T)pthread_self());
	printf(" sigval         = %d\n", si->si_value.sival_int);
}

int main(void)
{
	struct sigevent se;
	struct sigaction sa;
	timer_t timerid_thread;
	timer_t timerid_signal;

	printf("main thread\n");
	printf(" pthread_self() = %" PRI_PTHREAD_T "\n",
	    (CASTPRI_PTHREAD_T)pthread_self());

	memset(&se, 0, sizeof(se));
	se.sigev_value.sival_int = 1;
	se.sigev_notify = SIGEV_THREAD;
	se.sigev_notify_function = notifyfunc;
	se.sigev_notify_attributes = NULL;
	timer_create(CLOCK_REALTIME, &se, &timerid_thread);
	timer_settime(timerid_thread, 0, &its_1sec, NULL);

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = sigalrm;
	sigfillset(&sa.sa_mask);
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGALRM, &sa, NULL);

	memset(&se, 0, sizeof(se));
	se.sigev_value.sival_int = 2;
	se.sigev_signo = SIGALRM;
	se.sigev_notify = SIGEV_SIGNAL;
	timer_create(CLOCK_REALTIME, &se, &timerid_signal);
	timer_settime(timerid_signal, 0, &its_2sec, NULL);

	nanosleep(&ts_3sec, NULL);

	timer_delete(timerid_thread);
	timer_delete(timerid_signal);

	return 0;
}

ごめん長かった。

で、これを Linux (CentOS 6, kernel 2.6.32-220.7.1.el6.x86_64) で実行すると、たとえば以下のようになります。

[umezawa@devlinux:pts/6 ~]$ ./timer_test
main thread
 pthread_self() = 00007f8258d8e700
notifyfunc: timer has expired.                 ← 1秒後
 pthread_self() = 00007f8253fff700
 sigval         = 1
sigalrm: timer has expired.                    ← 2秒後
 pthread_self() = 00007f8258d8e700
 sigval         = 2
[umezawa@devlinux:pts/6 ~]$

sigev_notify が SIGEV_SIGNAL な方は(プロセスにスレッドが 1 つしかないので)メインスレッドでシグナルハンドラが呼び出され、SIGEV_THREAD な方は新しいスレッドで通知関数が呼び出されていることが分かります。また、タイマを作成した時の sigev_value がハンドラに渡っており、ハンドラ側でタイマを識別できることが分かります。

でまあここまではいいんですが、他のプラットフォームではどうかというと、

(04/20追記) FreeBSD 9.0
Linux と同様に使えるぽい。
NetBSD 5
SIGEV_THREAD は使えない。timer_create() を呼ぶと成功するが、通知関数は呼ばれない。
ついでに言うと、なぜか sigev_notify_function の型が void (*)(union sigval) ではなく void (*)(union sigval *) になっていて、上のコードはそのままではコンパイルできない。
OpenBSD 5.0
そもそも timer_create() が無い。libc にはシンボルが含まれているが ENOSYS を返すだけであり、またヘッダファイルには宣言が無い。

というわけでちょっと使いづらいですね。

Trackback

3 comments untill now

  1. はじめまして。インターバルタイマを作成したく、こちらのブログに行き当たりました。
    私がやろうとしていたタイマの実装とほぼ同じで、大変参考になりました。ありがとうございます。
    ところで、こちらのコードをvalgrindを通して実行いたしますと、下記のようなエラーが出ております。私もこちらのサイトを拝見する前に作成したコードで同じようなエラーが出ており、エラーを消せないか調べていたところなのです。timer_createで呼ばれたスレッドが終わっていないのかと思うのですが、いかがでしょうか?
    大変お忙しいところ恐縮ですが、もし何か知見をお持ちでしたらぜひご教示賜りたくコメントいたしました。
    よろしくお願いいたします。
    =======================
    ==14568== HEAP SUMMARY:
    ==14568== in use at exit: 272 bytes in 1 blocks
    ==14568== total heap usage: 6 allocs, 5 frees, 1,680 bytes allocated
    ==14568==
    ==14568== 272 bytes in 1 blocks are possibly lost in loss record 1 of 1
    ==14568== at 0x4C31B25: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==14568== by 0x40134F6: allocate_dtv (dl-tls.c:286)
    ==14568== by 0x40134F6: _dl_allocate_tls (dl-tls.c:530)
    ==14568== by 0x543D227: allocate_stack (allocatestack.c:627)
    ==14568== by 0x543D227: pthread_create@@GLIBC_2.2.5 (pthread_create.c:644)
    ==14568== by 0x4E4151A: __start_helper_thread (timer_routines.c:176)
    ==14568== by 0x5444826: __pthread_once_slow (pthread_once.c:116)
    ==14568== by 0x4E403BA: timer_create@@GLIBC_2.3.3 (timer_create.c:101)
    ==14568== by 0x108A3D: main (in /home/koki/sample/sample_timer)
    ==14568==
    ==14568== LEAK SUMMARY:
    ==14568== definitely lost: 0 bytes in 0 blocks
    ==14568== indirectly lost: 0 bytes in 0 blocks
    ==14568== possibly lost: 272 bytes in 1 blocks
    ==14568== still reachable: 0 bytes in 0 blocks
    ==14568== suppressed: 0 bytes in 0 blocks

  2. 梅澤 威志 @ 2020-10-16 20:13

    glibc のソースコードを読んでいくと(あるいはスタックトレースに出てくる関数名を見ると)分かりますが、ここで起動したまま終了していないのは timer_create が内部的に作るヘルパースレッドであり、基本的にこれを終了させる方法は無いようです。(スレッドをキャンセルすると終了させられるようだが、キャンセルしようにもスレッドIDはglibc外に露出していない。atexit で始末してくれればいいのに…)

    valgrind で検出されてしまうことへの対策は…どうしたらいいんでしょうかね。

  3. お忙しいところこのような質問にご返信くださり、ありがとうございます。
    やはりそうなのですかね。。。私も元のソースコードを見てみていたのですが、どうにも終了させることができなさそうだと感じておりました。valgrindはとりあえず表示を抑制でもしておこうかと思います。
    ありがとうございます!

Add your comment now