特定のイベントのタイミングは、開発者にとって一般的なタスクです。タイマーの一般的なシナリオは、ウォッチドッグ、タスクの周期的な実行、または特定の時間のイベントのスケジューリングです。この記事では、timer_create(...)を使用してPOSIX準拠のインターバルタイマーを作成する方法を示します。
次の例のソースコードはGitHubからダウンロードできます。
QtCreatorを準備する
この例では、IDEとしてQtCreatorを使用しました。 Qt Creatorでサンプルコードを実行してデバッグするには、GitHubリポジトリのクローンを作成し、Qt Creatorを開いて、ファイル->ファイルまたはプロジェクトを開く...に移動します。 CMakeLists.txtを選択します :
ツールチェーンを選択したら、プロジェクトの構成をクリックします 。プロジェクトには3つの独立した例が含まれています(この記事ではそのうちの2つのみを取り上げます)。緑色のマークが付いたメニューで、各例の構成を切り替えて、ターミナルで実行をアクティブにします。 それらのそれぞれについて(下の黄色いマークを参照)。ビルドとデバッグの現在アクティブな例は、デバッグから選択できます。 左下隅のボタン(下のオレンジ色のマークを参照):
スレッドタイマー
simple_threading_timer.cを見てみましょう。 例。これは最も単純なものです。これは、関数期限切れを呼び出すインターバルタイマーがどのように作成されるかを示しています。 有効期限が切れたとき。有効期限が切れるたびに、関数有効期限が含まれる新しいスレッドが作成されます。 と呼ばれます。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
void expired(union sigval timer_data);
pid_t gettid(void);
struct t_eventData{
int myData;
};
int main()
{
int res = 0;
timer_t timerId = 0;
struct t_eventData eventData = { .myData = 0 };
/* sigevent specifies behaviour on expiration */
struct sigevent sev = { 0 };
/* specify start delay and interval
* it_value and it_interval must not be zero */
struct itimerspec its = { .it_value.tv_sec = 1,
.it_value.tv_nsec = 0,
.it_interval.tv_sec = 1,
.it_interval.tv_nsec = 0
};
printf("Simple Threading Timer - thread-id: %d\n", gettid());
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = &expired;
sev.sigev_value.sival_ptr = &eventData;
/* create timer */
res = timer_create(CLOCK_REALTIME, &sev, &timerId);
if (res != 0){
fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
exit(-1);
}
/* start timer */
res = timer_settime(timerId, 0, &its, NULL);
if (res != 0){
fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
exit(-1);
}
printf("Press ETNER Key to Exit\n");
while(getchar()!='\n'){}
return 0;
}
void expired(union sigval timer_data){
struct t_eventData *data = timer_data.sival_ptr;
printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}
このアプローチの利点は、コードと単純なデバッグの観点から、フットプリントが小さいことです。欠点は、有効期限が切れたときに新しいスレッドが作成されるためにオーバーヘッドが増えるため、決定論的な動作が少なくなることです。
その他のLinuxリソース
- Linuxコマンドのチートシート
- 高度なLinuxコマンドのチートシート
- 無料のオンラインコース:RHELの技術概要
- Linuxネットワーキングのチートシート
- SELinuxチートシート
- Linuxの一般的なコマンドのチートシート
- Linuxコンテナとは何ですか?
- 最新のLinux記事
割り込み信号タイマー
期限切れのタイマーによって通知されるもう1つの可能性は、カーネル信号に基づいています。タイマーが切れるたびに新しいスレッドを作成する代わりに、カーネルはプロセスにシグナルを送信し、プロセスは中断され、対応するシグナルハンドラーが呼び出されます。
シグナルを受信したときのデフォルトのアクションはプロセスを終了することであるため(シグナルのマニュアルページを参照)、適切なデバッグが可能になるように、事前にQtCreatorを準備する必要があります。
デバッグ対象がシグナルを受信したときのQtCreatorのデフォルトの動作は次のとおりです。
- 実行を中断し、デバッガコンテキストに切り替えます。
- 信号の受信についてユーザーに通知するポップアップウィンドウを表示します。
信号の受信はアプリケーションの一部であるため、両方のアクションは必要ありません。
QtCreatorはバックグラウンドでGDBを使用します。プロセスがシグナルを受信したときにGDBが実行を停止しないようにするには、ツールに移動します。 ->オプション 、デバッガを選択します 、ローカルと式に移動します 。次の式をDebuggingHelper Customizationに追加します :
handle SIG34 nostop pass
GDBシグナル処理の詳細については、GDBのドキュメントを参照してください。
次に、シグナルハンドラーで停止したときにシグナルを受信するたびに通知するポップアップウィンドウを抑制します。
これを行うには、[ GDB]タブに移動します マークされたチェックボックスのチェックを外します:
これで、 signal_interrupt_timerを適切にデバッグできます。 。シグナルタイマーの実際の実装はもう少し複雑です:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define UNUSED(x) (void)(x)
static void handler(int sig, siginfo_t *si, void *uc);
pid_t gettid(void);
struct t_eventData{
int myData;
};
int main()
{
int res = 0;
timer_t timerId = 0;
struct sigevent sev = { 0 };
struct t_eventData eventData = { .myData = 0 };
/* specifies the action when receiving a signal */
struct sigaction sa = { 0 };
/* specify start delay and interval */
struct itimerspec its = { .it_value.tv_sec = 1,
.it_value.tv_nsec = 0,
.it_interval.tv_sec = 1,
.it_interval.tv_nsec = 0
};
printf("Signal Interrupt Timer - thread-id: %d\n", gettid());
sev.sigev_notify = SIGEV_SIGNAL; // Linux-specific
sev.sigev_signo = SIGRTMIN;
sev.sigev_value.sival_ptr = &eventData;
/* create timer */
res = timer_create(CLOCK_REALTIME, &sev, &timerId);
if ( res != 0){
fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
exit(-1);
}
/* specifz signal and handler */
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
/* Initialize signal */
sigemptyset(&sa.sa_mask);
printf("Establishing handler for signal %d\n", SIGRTMIN);
/* Register signal handler */
if (sigaction(SIGRTMIN, &sa, NULL) == -1){
fprintf(stderr, "Error sigaction: %s\n", strerror(errno));
exit(-1);
}
/* start timer */
res = timer_settime(timerId, 0, &its, NULL);
if ( res != 0){
fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
exit(-1);
}
printf("Press ENTER to Exit\n");
while(getchar()!='\n'){}
return 0;
}
static void
handler(int sig, siginfo_t *si, void *uc)
{
UNUSED(sig);
UNUSED(uc);
struct t_eventData *data = (struct t_eventData *) si->_sifields._rt.si_sigval.sival_ptr;
printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}
スレッドタイマーとは対照的に、シグナルを初期化し、シグナルハンドラーを登録する必要があります。このアプローチでは、追加のスレッドが作成されないため、パフォーマンスが向上します。このため、シグナルハンドラーの実行もより決定論的です。欠点は明らかに、これを適切にデバッグするための余分な構成作業です。
概要
この記事で説明する両方の方法は、タイマーのカーネルに近い実装です。 timer_create(...)関数がPOSIX仕様の一部であっても、データ構造のわずかな違いのため、FreeBSDシステムでサンプルコードをコンパイルすることはできません。この欠点に加えて、このような実装により、汎用タイミングアプリケーションをきめ細かく制御できます。