株式会社アイズ・ソフトウェア

/~eyes-software-co-jp/A1A2
お問い合わせ |  情報セキュリティへの取組について情報セキュリティ方針個人情報保護方針) |  ブログ

BLOG 社員ブログ

Linux

timerfdによる周期処理のサンプル

Linuxでtimerfdを使用したウェイト処理の手順について説明します。

一定時間ウェイトする関数には以下のものがあります。

  1. sleep
  2. usleep
  3. nanosleep
  4. selectのタイムアウト指定で待つ
  5. settimer
  6. timerfd
それぞれメリット、デメリットがありますが、 ここに挙げたほとんどの関数はSIGNALが発生すると中断してしまい、正しく指定時間待つには残り時間を再度待つ為にウェイトのリトライを行う必要があります。
リトライでウェイトを継続できるとはいえ、指定時間の分解能や中断中の処理のオーバーヘッドにより当初指定した時間を正しく待てる保障はありません。

上の一覧のうち、timerfdを使用すればリトライ処理やオーバーヘッドを気にする事無く正確に指定時間を待つ事が出来ます。

ではtimerfdとはどの様なものでしょう?
timerfd_createのmanページには「満了通知をファイルディスクリプター経由で配送する タイマーの生成と操作を行う。……このファイルディスクリプターを select(2), poll(2), epoll(7) で監視できるという利点がある。」とあります。
つまり、タイマーが満了したらそのファイルディスクリプターにデータが入るというものです。

timerfdのその他のメリットについて
  1. selectを使用する事で、ファイルI/Oやソケット通信などのディスクリプタを使用する他のI/Oとの同時待ちができる。
  2. 複数のタイマーを同時に使える。
  3. 周期的なタイマーを設定できる。
以下にtimerfdを使用してreadで待つ例、selectで待つ例、ポーリングする例を示します。
timerfdに関しては本ブログ等よりオンラインマニュアルの方が詳しく書かれており、使用例も有ります。
Linuxマシンの端末でman timerfd_create と打つか、man timerfd_create でググって見ることをお勧めします。


========== readで待つ場合のサンプル ==========

#include <sys/timerfd.h>
#include <signal.h>
#include <signal.h>
#include <stdint.h>

void timerfd_read()
{
    int tfd;
    struct itimerspec stime;
    ssize_t     rtn;
    uint64_t   exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, 0 );

    stime.it_value.tv_sec  = 1;        // ①
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    do {
        rtn = read( tfd, &exp, sizeof(uint64_t));        // ②
    } while ( rtn < 0 && errno == EINTR);

    printf("timerfd timeout.  exp=%lu\n", exp);
    close( tfd );
}
① itimerspec構造体で初回のタイマー及び2回目以後の周期タイマーを設定する。
  it_valueが初回タイマーで、it_intervalが周期タイマーである。
② readでtimerfdのタイマー満了を待つ。
  この例ではタイマー待ち中にシグナルの発生でreadがエラー終了する場合、リトライする様に対処している。

========== selectで待つ場合のサンプル ==========

#include <sys/select.h>
#include <sys/timerfd.h>

void timerfd_select()
{
    int tfd;
    struct itimerspec stime;
    fd_set fds;
    ssize_t    rtn;
    uint64_t exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, 0 );

    stime.it_value.tv_sec  = 1;
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    while( 1 ){
        FD_ZERO( &fds );
        FD_SET( tfd, &fds );

        rtn = select( tfd+1, &fds, NULL, NULL, NULL );     // ①
        if ( rtn > 0 ){
            if ( FD_ISSET( tfd, &fds )){
                read( tfd, &exp, sizeof(uint64_t));        // ②
                printf("timerfd timeout.  exp=%lu\n", exp);
                break;
            }
        }
    }
    close( tfd );
}
① selectでタイマー満了を待つ
② selectで待った時でもreadでtimerfdのデータを読み込む必要がある。

========== readでポーリングする場合のサンプル ==========

#include <sys/timerfd.h>
#include <signal.h>
#include <signal.h>
#include <stdint.h>
void timerfd_read()
{
    int tfd;
    struct itimerspec stime;
    ssize_t    rtn;
    uint64_t   exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK );        // ①
    stime.it_value.tv_sec  = 1;
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    while (1) {
        rtn = read( tfd, &exp, sizeof(uint64_t));        // ②
        if ( rtn < 0 && errno == EINTR  ) continue;
        if ( rtn < 0 && errno == EAGAIN ) continue;        // ③
        break;
    }
    printf("timerfd timeout.  exp=%lu, rtn = %ld, errno=%d\n", exp, rtn, errno);

    close( tfd );
}
① TFD_NONBLOCKを指定する事でtimerfdはノンブロッキングになる。
② ノンブロッキンッグなので、このreadはタイマー満了していなくても終了する。
③ タイマー満了していない場合、readの戻り値は-1でerrnoにEAGAINがセットされる。
  readが成功完了するまで繰り返してタイマー満了を待つ。

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。

Linux®課題

当社社員がLinux®を使用したプロジェクトに参加する際に事前学習に使用したテキストを公開します。
解答は記載しませんが、課題を解いてみてください。

※ Linux® は、Linus Torvalds 氏の日本およびその他の国における登録商標または商標です。

==================== ここから ====================
■■総合課題■■
  「課題」は、課題をこなすことが目的ではなく、
  「課題」を通じて、「知識」と「気づき」を得てもらうことが目的です。
  
  従って、総合課題として以下を記載します。
  
  1.各「個別課題」から得られた「知識」をノートにまとめてください。
      各「個別課題」毎に自分のノートにまとめ、コピーを提出してください。
      
  2.各「個別課題」終了後に「どんな学び」「どんな気づき」があったかを
      報告してください。
      これは日報に入れてください。

■■個別課題■■

●課題1
  内容:Unix系コマンドを使ってフォルダ移動、ファイルのコピー等を行う。
  前提:Linuxまたは互換環境があること。
  達成レベル:良く使う基本的なコマンドが使えること。各オプション確認。
        ・cd
        ・pwd
        ・ls
        ・cp
        ・mv
        ・mkdir
        ・rmdir
        ・clear
        ・diff
        ・cut
        ・du
        ・df
        ・su
        ・tar
        ・telnet
        ・netstat
        ・ping
        ・head
        ・history
        ・rm
        ・ps
        ・find
        ・grep
        ・cat
        ・tail
        ・more
        ・less
        ・echo
        ・chown
        ・chmod
        ・chgrp
        ・whereis
        ・man
        ・printenv
        ・shutdown
        ・reboot
        ・kill
        ・popd
        ・pushd
        ・set
        ・ln
        ・シェル
        ・ファイル属性等の意味
        ・環境変数
        ・実行ファイルの実行方法
        ・パス変数の意味
        ・大文字小文字
        ・漢字の入力
        ・特殊フォルダ
        ・デバイススペシャルファイル
        ・先頭が . で始まるファイル
        ・ログファイルの位置
        ・(|)の意味
        ・(>)の意味
        ・(&)の意味
        ・(.)の意味
        ・(..)の意味
        ・(/)の意味
        ・(~)の意味
        
        ※ 上記にあげたコマンド等は、環境やディストリビューションによって
           使えないものやインストールされていないものがあるので、留意すること。
  課題:
    ① man ページを使って上記に挙げてある各コマンドの概要を確認してみよ。
      (例) man cp
        <manページ上での操作の確認>
       ・表示されている画面で↑を押下するとどうなるか?
       ・表示されている画面で↓を押下するとどうなるか?
       ・表示されている画面でスペースキーを押下するとどうなるか?
       ・表示されている画面でwを押下するとどうなるか?
       ・表示されている画面でエンターを押下するとどうなるか?
       ・表示されている画面でqを押すとどうなるか?
        <cpコマンドの説明の確認>
       ・cpコマンドは何をするコマンドか?
       ・どういう文法か?
       ・オプションはどのようなものがあるか?
        -----上記は例なので全てのコマンドについて確認すること-----
    ② 環境変数の一覧から使用しているシェルを特定せよ。
    ③ man ページを使ってシェルについて調査せよ。
      (例) man bash      ※シェルにbashを使っている場合
        <manページ上での操作の確認>
       ・表示されている画面でhを押すとどうなるか?
       ・表示されている画面でqを押すとどうなるか?
        <シェル文法の確認>
       ・リダイレクトについて確認せよ( REDIRECTION で検索 )
       ・パイプラインについて確認せよ( Pipe または Pipeline で検索)
    ④ 現在のディレクトリはどこか確認せよ。
    ⑤ サブディレクトリ「test」を作成せよ。
    ⑥ サブディレクトリ「test」に移動せよ。
    ⑦ 「test」フォルダにて、echoコマンドを使ってファイルを1つ作成せよ。
       ファイル名は、echo_test.txt とすること。
       ヒント:リダイレクト(>)
    ⑧ ファイルが作成されていることを確認せよ。
    ⑨ echo_test.txtの内容を表示せよ。
    ⑩ echo_test.txtを コピーして echo_test2.txt を作成せよ。
    ⑪ catコマンドを使って、echo_test2.txt ファイルと同じ内容の
       echo_test3.txt ファイルを作成せよ。
    ⑫ ④のディレクトリへ移動し、
       ⑪で作成した echo_test3.txt ファイルを現在のディレクトリにコピーせよ。
    ⑬ 現在のフォルダに「/cygdrive/c/windows/system32/notepad.exe」の、
       リンク(シンボリックリンク)を作成せよ。
    ⑭ ファイル一覧を表示させ、属性、オーナー、グループ、
       リンクについて確認せよ。
    ⑮ ファイル名にlogがつくものを、「/」フォルダから検索せよ。
    ⑯ /var/log 以下にあるファイルで一番大きいサイズのファイルを表示させよ。
    ⑰ ⑯のファイルの先頭から10行を表示させよ。また、末尾10行の表示もさせよ。
    ⑱ ⑯のファイルの中から「/etc」を含む行だけを表示させよ。
       また先頭10行の表示、末尾10行の表示をそれぞれ行うこと。
    ⑲ 「./notepad.exe」と「./notepad.exe &」の差を確認せよ。
       また、psコマンドで起動プロセスの確認をせよ。
    ⑳ フォルダ「test」を削除せよ。(「test」フォルダ内のファイルも削除。)

●課題2
  内容:viエディタを使ってC言語のソースを入力する。
  前提:viエディタがインストールされているLinuxまたは互換環境があること。
  達成レベル:viエディタの最低限の操作ができること。
        ・入力モード/編集モード切替
        ・検索
        ・置換
        ・保存終了、破棄
        ・カーソル移動
        ・行コピー
        ・1文字削除
        ・行削除
        (等)
        
  課題:
    ① 以下の内容を hoge.c として作成し保存せよ。
      -------
      #include <stdio.h>
      
      main(){
          int cnt=0;
          cnt=cnt+5;
          printf("%d\n",cnt);
      }
      --------
    ② 作成された hoge.c を編集して以下の内容にして保存せよ。
      -------
      #include <stdio.h>
      
      main(){
          int cnt=0;
          int num[10]={0,};
          int hsize=sizeof(num)/sizeof(num[0]);
          
          for(cnt=0;cnt<hsize;cnt++){
              num[cnt]=cnt*cnt;
          }
          for(cnt=0;cnt<hsize;cnt++){
              printf("%d\n",num[cnt]);
          }
      }
      --------

●課題3
  内容:単一C言語ファイルをビルドして実行、デバッグする
  前提:gcc、gdbがインストールされているLinuxまたは互換環境があること。
  達成レベル:単一ファイルのビルドと実行、デバッグができる。各オプションの確認。
        ・gcc
        ・gdb
        
  課題:
    ① manページを使って、gccのオプション等を確認せよ。
    ② 課題2で作成したhoge.cを、以下のコマンドライン入力をしてビルドせよ。
       gcc hoge.c
    ③ 上記②で作成されたファイルは何か?サイズは?属性は?
       を確認し、実行してみよ。
    ④ 課題2で作成したhoge.cを、以下のコマンドライン入力をしてビルドせよ。
       gcc -g -O0 -ohoge hoge.c
    ⑤ 上記④で作成されたファイルは何か?サイズは?属性は?
       を確認し、実行してみよ。
    ⑥ manページを使って、gdbのオプション等を確認せよ。
    ⑦ 上記②で作成されたファイルをgdbで指定し、
       gdb内でソースコードを閲覧できるか確認せよ。→確認後quit
    ⑧ 上記④で作成されたファイルをgdbで指定し、
       gdb内でソースコードを閲覧できるか確認せよ。
    ⑨ 上記⑧の状態で、5行目(int num[10]={0,};)に
       ブレークポイントを設定し、実行してみよ。
    ⑩ さらに1行実行してみよ。
    ⑫ 変数 hsizeの値を表示せよ。
    ⑬ 最後までいっきに実行せよ。

●課題4
  内容:複数C言語ファイルをビルドして実行、デバッグする
  前提:gcc、gdbがインストールされているLinuxまたは互換環境があること。
  達成レベル:複数ファイルのビルドと実行、デバッグができる。
  課題:
    ① 課題2の hoge.c をviエディタで編集して以下の内容にして保存せよ。
      -------
      #define _HOGE_C_
      #include "hoge.h"
      
      int main(){
          int cnt=0;
          int num[10]={0,};
          int hsize=sizeof(num)/sizeof(num[0]);
          
          for(cnt=0;cnt<hsize;cnt++){
              //change
              num[cnt]=extFunc(cnt);
          }
          for(cnt=0;cnt<hsize;cnt++){
              printf("%d\n",num[cnt]);
          }
          return 0;
      }
      --------
    ② 以下の内容を extfunc.c として保存せよ。
      -------
      #include "hoge.h"
      
      int extFunc(int number) {
          return number*number;
      }
      --------
    ③ 以下の内容を inc/hoge.h として保存せよ。
      -------
      #include <stdio.h>
      
      #ifdef _HOGE_C_
      extern int extFunc(int);
      #endif
      --------
    ④ 以下の内容を makefile として保存せよ。
       尚、下記の行の★印以降は課題用のコメントなので入力不要である。
      -------
      #Makefile for hoge
      
      PROGRAM = hoge
      OBJS = hoge.o extfunc.o
      CC = gcc
      INCDIR = inc
      CFLAGS = -Wall -g -O0 -I$(INCDIR)
      
      .SUFFIXES: .c .o
      
      $(PROGRAM): $(OBJS)
              $(CC) -o $(PROGRAM) $^   ★先頭の空白はタブ
      
      .c.o:
              $(CC) $(CFLAGS) -c $<   ★先頭の空白はタブ
      
      .PHONY: all
      all: hoge
      
      .PHONY: clean
      clean:
              $(RM) $(PROGRAM) $(OBJS)   ★先頭の空白はタブ
      --------
    ⑤ make all でビルドせよ。
    ⑥ gdb で hoge.exe を指定して開き、extfunc.cのソースを表示せよ。
    ⑦ extfunc.c の4行目( return number*number; )にブレークを設定し実行せよ。
    ⑧ 変数 number を表示せよ。

●課題5
  内容:ファイルを読書きする複数C言語ファイルを作成、ビルドして実行、デバッグする
  前提:gcc、gdbがインストールされているLinuxまたは互換環境があること。
  達成レベル:自分でファイルを読書きするプログラムを作成できる。
              複数ファイルのビルドと実行、デバッグができる。
  課題:
    ① 入力した値をファイルに書き出すプログラムを作成せよ。
    ② ファイル内に書かれたカンマ区切りの数値を読込み、
       入力した値に最も近い値を表示するプログラムを作成せよ。


  • Category:
  • タグ:Linux
  • Author:eyes-software-co-jp