English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

LinuxでタイマーTimerを実現する方法のまとめ

プログラム開発の過程で、時々タイマを使用する必要があります。時間精度が高くない場合、sleep、usleep関数を使用してプロセスを一定時間スリープさせることでタイマを実現できます。

前者は秒(s)単位、後者はマイクロ秒(us)単位ですが、時にはプロセスがスリープ状態になるのを避けたい場合があり、プロセスが正常に実行し、指定された時間に対応する操作を実行する必要があります。

Linuxでは、通常alarm関数とsetitimer関数を使用してタイマ機能を実現します;

以下にこれらの関数を詳細に分析します:

(1)alarm関数

alarmはアラーム関数とも呼ばれ、プロセスにタイマーを設定し、指定された時間が経過するとプロセスにSIGALRMシグナルを送信します。

alarm関数のプロトタイプは以下の通りです:

unsigned int alarm(unsigned int seconds);
//seconds:指定された秒数

必要なヘッダーファイル
  #include<unistd.h>

関数のプロトタイプ
  unsigned int alarm(unsigned int seconds)

関数の引数
  seconds:指定された秒数

関数の返値
  成功:alarm()を呼び出す前にプロセスがアラーム時間を設定している場合、前のアラーム時間の残り時間が返されます。そうでない場合、0が返されます。
  エラー:-1

以下はalarm()関数の簡単な例です:

void sigalrm_fn(int sig)  
{ 
  printf("alarm!\n"); 
  alarm(2); 
  return; 
} 
int main(void) 
{ 
  signal(SIGALRM, sigalrm_fn); //後の関数はint引数を持つ必要があります
  alarm(1); 
  while(1)  
  pause(); 
}

(2)setitimer()関数

Linuxでは、タイマーに対する精度が低い場合、alarm()とsignal()を使用できますが、精度の高いタイマー機能を実現するには、setitimer関数を使用する必要があります。

setitimer()はLinuxのAPIであり、C言語のStandard Libraryではありません。setitimer()には2つの機能があります。一つは、特定の時間が経過した後に特定のfunctionを実行する指定、もう一つは特定の間隔を空けるごとに特定のfunctionを実行する指定です。

Linuxは各タスクに3内部タイマー:

ITIMER_REAL:リアルタイムタイマーです。プロセスがどのモードで動作しているかに関わらず(プロセスが停止されている場合でも)、常にカウントされます。定期的に到達すると、プロセスにSIGALRMシグナルが送信されます。

ITIMER_VIRTUAL:これはリアルタイムタイマーではありません。プロセスがユーザーモード(プログラムの実行時)でプロセスの実行時間を計算します。定期的に到達すると、そのプロセスにSIGVTALRMシグナルが送信されます。

ITIMER_PROF:プロセスがユーザーモード(プログラムの実行時)およびコアモード(プロセススケジューリング時)でカウントされます。定期的に到達するとSIGPROFシグナルが生成されます。ITIMER_PROFが記録する時間はITIMER_VIRTUALよりもプロセススケジューリングに費やされる時間が多いです。

タイマーは初期化時に初期値が設定され、時間とともに減少し、0に達するとシグナルが発信され、同時に初期値が復元されます。タスク中には、1つまたは3つのタイマーをいずれか一つ使用できますが、同じタイマータイプは同時に1つしか使用できません。

setitimer関数のプロトタイプは以下の通りです:

#include <sys/time.h>
    int setitimer(int which, const struct itimerval *new_value,
           struct itimerval *old_value);
 タイマーの値は以下の構造体で定義されます:
      struct itimerval {
        struct timeval it_interval; /* next value */
        struct timeval it_value;  /* current value */
      };
      struct timeval {
        time_t   tv_sec;     /* seconds */
        suseconds_t tv_usec;    /* microseconds */
      };

it_intervalはタスクの実行間隔を指定し、it_valueは現在の時間からタスク実行までの残り時間を保存します。例えば、it_intervalを2秒(マイクロ秒は0)、最初にit_valueの時間も2秒(マイクロ秒は0)が経過すると、it_valueが1つ減少します1、さらに1秒、则it_value又减少1、0に戻ります。この時点でシグナルが発信され(ユーザーに時間が経過したことを通知し、タスクを実行できるようにします)、システムが自動的にit_valueの時間をit_intervalの値にリセットします。2秒、再びカウントをリセットします

以下はsetitimerの簡単な例です:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
void test_func()
{
  static count = 0;
  printf("count is %d\n", count++);
}
void init_sigaction()
{
  struct sigaction act;
  act.sa_handler = test_func; //シグナルを処理する関数を設定します
  act.sa_flags = 0;
  sigemptyset(&act.sa_mask);
  sigaction(SIGPROF, &act, NULL);//時間が来たらSIGROFシグナルを送信します
}
void init_time()
{
  struct itimerval val;
  val.it_value.tv_sec = 1; //1秒後にタイマーを有効にします
  val.it_value.tv_usec = 0;
  val.it_interval = val.it_value; //タイマー間隔は1s
  setitimer(ITIMER_PROF, &val, NULL);
}
int main(int argc, char **argv)
{
  init_sigaction();
  init_time();
  while(1);
  return 0;
}

1秒ごとにcountの値が出力されることがわかります:

以下は実行結果です:

[root@localhost 5th]# ./test
countは0
countは 1
countは 2
countは 3
countは 4
countは 5
countは 6
countは 7
countは 8
countは 9

付録:

signal

1. ヘッダーファイル
#include <signal.h>

2. 設定するシグナルの対応動作
. 機能

3. 関数プロトタイプ
void (*signal(int signum,void(* handler)(int)))(int);

分解してみます:

typedef void (*sig_t) (int);
sig_t signal(int sig, sig_t func);

最初のパラメータは目標シグナルです。funcパラメータは、シグナルを処理する関数へのポインタです。このシグナル処理関数はint型のパラメータを持ち、voidを返します。
funcパラメータは以下の値の一部でも設定できます:
SIG_IGN: funcパラメータがSIG_IGNに設定されている場合、シグナルは無視されます。
SIG_DFL: funcパラメータがSIG_DFLに設定されている場合、シグナルは既定の動作で処理されます。

4. sigシグナルの可能な種類

1) #define SIGHUP 1 /* hangup */

SIGHUPはUnixシステム管理者がよく使うシグナルです。多くのバックグラウンドサービスプロセスは、このシグナルを受け取ると設定ファイルを再読み込みします。しかし、このシグナルの実際の機能は、プロセスの制御端末が切断されたことをプロセスに通知することです。デフォルトの動作はプロセスの終了です。

2) #define SIGINT 2 /* interrupt */

Unixユーザーにとって、SIGINTはもう一つの常用のシグナルです。多くのシェルのCTRL-C 组合使得这个信号被大家所熟知。该信号的正式名字是中断信号。默认行为是终止进程。

3) #define SIGQUIT 3 /* quit */

SIGQUIT 信号被用于接收 shell 的 CTRL-/组合。另外,它还用于告知进程退出。这是一个常用信号,用来通知应用程序从容的(译注:即在结束前执行一些退出动作)关闭。默认行为是终止进程,并且创建一个核心转储。

4) #define SIGILL 4 /* illegal instr. (not reset when caught) */

如果正在执行的进程中包含非法指令,操作系统将向该进程发送 SIGILL 信号。如果你的程序使用了线程,或者 pointer functions,那么可能的话可以尝试捕获该信号来协助调试。([color=Red]注意:原文这句为:“If your program makes use of use of threads, or pointer functions, try to catch this signal if possible for aid in debugging.”。中间的两个 use of use of,不知是原书排版的瑕疵还是我确实没有明白其意义;另外,偶经常听说 functions pointer,对于 pointer functions,google 了一下,应该是 fortran 里面的东西,不管怎样,还真不知道,确切含义还请知道的兄弟斧正。[/color])默认行为是终止进程,并且创建一个核心转储。

5) #define SIGTRAP 5 /* trace trap (not reset when caught) */

SIGTRAP 这个信号是由 POSIX 标准定义的,用于调试目的。当被调试进程接收到该信号时,就意味着它到达了某一个调试断点。一旦这个信号被交付,被调试的进程就会停止,并且它的父进程将接到通知。默认行为是终止进程,并且创建一个核心转储。

6) #define SIGABRT 6 /* abort() */

SIGABRT 提供了一种在异常终止(abort)一个进程的同时创建一个核心转储的方法。然而如果该信号被捕获,并且信号处理句柄没有返回,那么进程不会终止。默认行为是终止进程,并且创建一个核心转储。

7) #define SIGFPE 8 /* 浮点异常 */

プロセスが浮動小数点エラーが発生した場合、SIGFPEシグナルがそのプロセスに送信されます。複雑な数学演算を処理するプログラムでは、このシグナルをキャッチすることをお勧めします。デフォルトの動作はプロセスを終了し、コアダンプを作成することです。

8) #define SIGKILL 9 /* kill(キャッチしたり無視したりすることができません) */

SIGKILLはこれらのシグナルの中で最も対処が難しいものです。その横のコメントを見ると、このシグナルはキャッチしたり無視したりすることができません。シグナルがプロセスに渡されると、そのプロセスは終了します。しかし、SIGKILLがプロセスを終了しない非常に稀な状況もあります。これらの珍しい状況は、「非中断操作」(例えば、ディスクI/O)が発生したときに。このような状況は非常に稀ですが、発生するとプロセスが死锁します。プロセスを終了する唯一の方法は再起動だけです。デフォルトの動作はプロセスを終了することです。

9) #define SIGBUS 10 /* バスエラー */

その名の通り、CPUがデータバス上のエラーを検出したときにSIGBUSシグナルが発生します。プログラムが正しくアライメントされていないメモリアドレスにアクセスを試みたときにこのシグナルが発生します。デフォルトの動作はプロセスを終了し、コアダンプを作成することです。

10) #define SIGSEGV 11 /* セグメンテーション違反 */

SIGSEGVは別のC/Mybatisの欠点および問題の詳細な分析++プログラマーにとって馴染み深いシグナルです。プログラムが保護されたメモリアドレスにアクセスする権限がない場合、または無効な仮想メモリアドレス(ダーティポイント、ダーティポイント、訳注:バックアップメモリと同期していないために発生。野ポイントについての詳細は、http:で参照できます)//en.wikipedia.org/wiki/Wild_pointerの説明)のときにこのシグナルが発生します。デフォルトの動作はプロセスを終了し、コアダンプを作成することです。

11) #define SIGSYS 12 /* non-存在するシステムコールが呼び出された */

SIGSYSシグナルは、プロセスが存在しないシステムコールを実行したときに渡されます。オペレーティングシステムはこのシグナルを渡し、プロセスを終了します。デフォルトの動作はプロセスを終了し、コアダンプを作成することです。

12) #define SIGPIPE 13 /* 誰も読み込んでいないパイプに対しての書き込み */

パイプの役割は電話のように、プロセス間の通信を許可しています。プロセスがパイプに対して書き込み操作を試みた場合、しかしパイプの反対側に応答者がいないと、オペレーティングシステムはこの嫌なプロセス(ここでは書き込みを試みたプロセス)にSIGPIPEシグナルを渡します。デフォルトの動作はプロセスを終了することです。

13) #define SIGALRM 14 /* アラームクロック */

プロセスのタイマーが切れたとき、SIGALRMシグナルがプロセスに渡されます(delivered)。これらのタイマーは本章の後で言及する
のsetitimerとalarmコールで設定されます。デフォルトの動作はプロセスを終了することです。

14) #define SIGTERM 15 /* ソフトウェア終了シグナル(killからの) */

SIGTERMシグナルがプロセスに送信され、プロセスが終了する時刻が来たことを通知し、終了前にクリーンアップ活動を行います。SIGTERMシグナルはUnixのkillコマンドで送信されるデフォルトのシグナルであり、同時にオペレーティングシステムがプロセスに送信するデフォルトのシグナルです。デフォルトの動作はプロセスを終了することです。

15) #define SIGURG 16 /* IOチャンネルの緊急状態 */

プロセスがオープンしているソケットで何かが起こった場合、SIGURGがそのプロセスに送信されます。プロセスがこのシグナルをキャッチしない場合、捨てられます。デフォルトの動作はシグナルを捨てることです。

16) #define SIGSTOP 17 /* ttyからの送信可能な停止シグナルでない */

このシグナルはキャッチや無視もできません。プロセスがSIGSTOPシグナルを受け取ると、すぐに停止します(stop)。他のSIGCONT
シグナルを受信するまで停止します。デフォルトの動作はプロセスを停止し、SIGCONTシグナルを受信するまで停止します。

17) #define SIGTSTP 18 /* ttyからの停止シグナル */

SIGSTPはSIGSTOPと似ていますが、違いはSIGSTPシグナルがキャッチや無視が可能であることです。シェルがキーボードからCTRL-Zの時点でプロセスにシグナルを渡します(deliver)。デフォルトの動作はプロセスを停止し、SIGCONTシグナルを受信するまで停止します。

18) #define SIGCONT 19 /* 停止されたプロセスを再開する */

SIGCONTは非常に興味深いシグナルです。前述の通り、プロセスが停止した場合、このシグナルはプロセスが再開されることを伝えるために使用されます。このシグナルの面白い点は、無視もブロックもできないが、キャッチできることです。これは非常に意味のあることです:プロセスはSIGCONTシグナルを無視したりブロックしたりしたくないでしょう。そうしないと、プロセスがSIGSTOPやSIGSTPを收到した場合どうしますか?デフォルトの動作はシグナルを捨てることです。

19SIGCHLD 20 /* 子プロセスの停止または終了時、親プロセスへ */

SIGCHLDはBerkeley Unixで導入され、SRV 4 Unixにおける実装はより良いインターフェースを持っています。(シグナルが追跡不可能なプロセス(not a retroactive process)である場合、BSDのSIGCHIDシグナルの実装が良いです。)system V Unixの実装では、プロセスがこのシグナルをキャッチする要求がある場合、オペレーティングシステムは未完了な子プロセス(これらの子プロセスは既にexitしていない)が存在するかどうかを確認し、waitを呼び出す親プロセスがそれらの状態を収集を待っているかどうかを確認します。子プロセスが終了したときに終了情報(terminating information)が付属している場合、シグナルハンドラが呼び出されます。したがって、シグナルをキャッチする要求をすることで、シグナルハンドラが呼び出されることになります(訳注:これは上で言った「シグナルの追跡能力」です)が、これは非常に混乱した状況です。)一旦プロセスの子プロセスの状態が変更されたら、SIGCHLDシグナルがそのプロセスに送信されます。前の章で述べたように、親プロセスはforkで子プロセスを生成できますが、子プロセスが終了するのを待つ必要はありません。一般的にはこれはよくありません。なぜなら、プロセスが終了すると、ゾンビプロセスになる可能性があるからです。しかし、親プロセスがSIGCHLDシグナルをキャッチする場合、wait系列の呼び出しのいずれかを使って子プロセスの状態を収集したり、何が起こったかを判断することができます。SIGSTOP、SIGSTP、またはSIGCONFシグナルを子プロセスに送信した場合、SIGCHLDシグナルも親プロセスに送信されます。デフォルトの動作は、シグナルを無視することです。

2SIGTTIN 21 /* バックグラウンドtty読み取りにおけるreaders pgrpへ */

バックグラウンドプロセスが読み操作を試みたとき、SIGTTINシグナルがそのプロセスに送信されます。プロセスはSIGCONTシグナルを受信するまでブロックされます。デフォルトの動作は、SIGCONTシグナルを受信するまでプロセスを停止することです。

21SIGTTOU 22 /* TTINのように if (tp-t_local<OSTOP */

SIGTTOUシグナルはSIGTTINシグナルと非常に似ていますが、違いはSIGTTOUシグナルがバックグラウンドプロセスがTOSTOP属性を設定されたttyに対して書き操作を試みたときにのみ発生します。ただし、ttyにこの属性が設定されていない場合、SIGTTOUは送信されません。デフォルトの動作は、SIGCONTシグナルを受信するまでプロセスを停止することです。

22SIGIO 23 /* 入力/出力可能シグナル */

プロセスがファイルデスクリプタに対してI/Oオペレーションの場合、SIGIOシグナルがこのプロセスに送信されます。プロセスはfcntlコールを使って設定できます。デフォルトの動作はシグナルの破棄です。

23) #define SIGXCPU 24 /* CPU時間の制限を超えた */

プロセスが使用できるCPUの制限(CPU limit)を超えた場合、SIGXCPUシグナルがそのプロセスに送信されます。この制限は、後で説明するsetrlimitで設定できます。デフォルトの動作はプロセスの終了です。

24) #define SIGXFSZ 25 /* ファイルサイズの制限を超えた */

プロセスが使用できるファイルサイズの制限を超えた場合、SIGXFSZシグナルがそのプロセスに送信されます。このシグナルについて後で詳しく説明します。デフォルトの動作はプロセスの終了です。

25) #define SIGVTALRM 26 /* 仮想時間アラーム */

プロセスが設定した仮想タイマーのカウントを超えた場合、SIGVTALRMシグナルがそのプロセスに送信されます。デフォルトの動作はプロセスの終了です。

26) #define SIGPROF 27 /* プロファイリング時間アラーム */

タイマーが設定された場合、SIGPROFはプロセスに送信されるもう一つのシグナルです。デフォルトの動作はプロセスの終了です。

27) #define SIGWINCH 28 /* ウィンドウサイズの変更 */

プロセスがテキストの行や列を調整した場合(例えば、xtermのサイズを大きくする場合)、SIGWINCHシグナルがそのプロセスに送信されます。デフォルトの動作はシグナルの破棄です。

28) #define SIGUSR1 29 /* ユーザー定義シグナル 1 */

29) #define SIGUSR2 30 /* ユーザー定義シグナル 2 */

SIGUSR1およびSIGUSR2これらのシグナルはユーザーが指定するために設計されています。それらは、必要などんな動作にも設定することができます。つまり、オペレーティングシステムには、これらのシグナルに関連するどんな行動もありません。デフォルトの動作はプロセスの終了です。(訳注:原文の意味では、この二つの文が矛盾しているように見えます。)

5. 例

5Android1. LinuxにおけるCtrl+CのWindowsにおける実装

Linuxにおける一般的な方法:

signal(SIGINT, sigfunc); // シグナルの設定
void sigfunc(int signo)
  {
   ... //シグナル関連の操作を処理
  }

以下はLinuxにおけるCtrl+CのWindowsにおける実装

#include <stdio.h>
  #include <windows.h>
  static is_loop = 1;
  // キャプチャー コントロール コンソール Ctrl+C 事件的関数
  BOOL CtrlHandler( DWORD fdwCtrlType )
  {
   switch (fdwCtrlType)
   {
   /* Handle the CTRL-C signal. */
   case CTRL_C_EVENT:
     printf("CTRL_C_EVENT \n");
     break;
   case CTRL_CLOSE_EVENT:
     printf("CTRL_CLOSE_EVENT \n");
     break;
   case CTRL_BREAK_EVENT:
     printf("CTRL_BREAK_EVENT \n");
     break;
   case CTRL_LOGOFF_EVENT:
     printf("CTRL_LOGOFF_EVENT \n");
     break;
   case CTRL_SHUTDOWN_EVENT:
     printf("CTRL_SHUTDOWN_EVENT \n");
     break;
   default:
     return FALSE;
   }
   is_loop = 0;
   return (TRUE);
  }
  int main(int argc, char *argv[])
  {
   printf("Set Console Ctrl Handler\n");
   SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
   while (is_loop);
   return 0;
  }

5Android2.LinuxにおけるCtrl+CのWindowsにおける実装二

#include <stdio.h>
  #include <windows.h>
  #define CONTRL_C_HANDLE() signal(3, exit)
  int main(int argc, char *argv[])
  {
   printf("Set Console Ctrl Handler\n");
   CONTRL_C_HANDLE();
   while (1);
   system("PAUSE");
   return 0;
  }

これで、編集者が皆さんに提供したLinuxのいくつかの定時関数の使用に関する簡単な説明がすべて終わりました。皆さん、呐喊チュートリアルを多くのサポートをお願いします~

基礎教程
Elasticsearch チュートリアル