1プロセスで複数周期

[| ]  最終更新: 2023/02/14 18:27:15

1プロセスで複数周期を利用

基本的に休眠をつかって周期実行をするこの手法は、1プロセス1周期が原則です。 もちろん、休眠時間をうまく設定してやれば複数周期のループを導入することは可能ですが、少しばかり面倒です。 そこで、1つのプロセスで、複数の周期を利用するのに便利なモジュールをつくってみました。

複数周期を利用するために開発したものですが、もちろん単周期でも使用可能です。 そのときは、最低タイマ割り込み2周期分という制限がなくなって、1周期単位から設定できるようになり、起きるべき時間をカーネル内部でチェックしてくれるようになるので、制御処理に要する時間を測定する必要もなくなります。


原理と予備知識

原理・動作

原理的には、カーネルにデバイスドライバとして、周期の面倒をみてくれる機能を追加します。

制御などを実行すべき時刻が来たかどうかをプロセスに通知する機能として、select() を使用しました(poll()も可)。 これは、複数のデバイスの読み書きの準備情況を待機し、準備が出来次第もどってくる関数です。 たとえば、キー入力待ちや、ネットワークからの情報到着待ちをすることが可能です。 これをつかって、次の動作を開始すべき時刻をプロセスに通知するようにしました。 select()をつかって周期実行まで待機させる利点は、その他デバイスとの共存にあります(Enterキーを押すと終了とか)。

実際にこの方法をつかうにはモジュールをカーネルに登録し、アクセスのための特別ファイル(/dev/なんとか、の類い)をつくらなければなりません(詳細は後述)。
登録したら、実行するプログラムでは open()でこのファイルを開き、ioctl()で周期設定をします。 あとは、ループの中で select()を実行し、戻ってきたら、その返り値を判定して、各周期に対応した処理を実行します。

予備知識


実践

ソースと実行例

以下に、実際に用いるドライバのソースと、それを利用する周期実行プログラムのソースを示します。 Linux 2.0.30/2.2.10で動作を確認しています。

// ドライバ
// gcc -c cyclesel.c -Wall -Wstrict-prototypes -O -pipe -m486
//    -DUSE_RDTSC をつけると RDTSC利用:プログラムとそろえること
// mknod /dev/csel c 60 0;  chmod a+rw /dev/csel

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/time.h>

#if LINUX_VERSION_CODE >= 0x020100
#include <asm/uaccess.h>
#include <linux/poll.h>            
#else
static inline unsigned long
copy_to_user(void *to, const void *from, unsigned long n)
{
        memcpy_tofs(to,from,n);
        return 0;
}

static inline unsigned long
copy_from_user(void *to, const void *from, unsigned long n)
{
        memcpy_fromfs(to,from,n);
        return 0;
}
#endif

static int devmajor=60;
static char *devname="cyclesel";
static int basecycle=1;
#if LINUX_VERSION_CODE > 0x20115
MODULE_PARM(basecycle, "i");
MODULE_PARM(devmajor, "i");
MODULE_PARM(devname, "s");
#endif

// ファイルごとの管理領域
typedef struct
{
  unsigned long f_version;   // 識別用 f_version
  struct wait_queue *wait;   // 休眠待機用領域
  int sleep_mode;            // 0: 起きてる 1: 待ち
  unsigned long long wtime;  // 最後に起こした時刻
  unsigned long wakeup_jiffies;  // 起きるべき時間
  unsigned int period;       // 周期
} FileInfo;
#define MAXFILE 30
static FileInfo fi[MAXFILE];


// 時間取得関数: 
unsigned long long get_time(void)
{
#ifdef USE_RDTSC             // RDTSC 直読み
  unsigned int h,l;
  /* read Pentium cycle counter */
  __asm__(".byte 0x0f,0x31" : "=a" (l),"=d" (h));
  return ((unsigned long long int)h<<32)|l;
#else                        // gettimeofday 使用
  struct timeval tv;
  do_gettimeofday(&tv);
  return (unsigned long long)(tv.tv_sec)*1000000+tv.tv_usec;
#endif
}

// Linux 2.0/2.2 共通
static int cycsel_open(struct inode * inode, struct file * file)
{
  int i;
  printk("cycsel_open: (%ld)\n",file->f_version);
  for(i=0;i<MAXFILE;i++)
    {
      if(fi[i].f_version==0) break;
    }
  if(i==MAXFILE)
    {
      printk("cycsel_open: busy\n");
      return -EBUSY;
    }
  fi[i].f_version=file->f_version;
  fi[i].sleep_mode=0;
  fi[i].wait=NULL;
  fi[i].wakeup_jiffies=jiffies;
  fi[i].period=MINOR(inode->i_rdev)*basecycle;  // マイナで周期設定
  if(fi[i].period==0) fi[i].period=HZ;          // デフォルト秒1
  file->private_data=fi+i;      // プライベートデータに構造体ポインタ設定

  MOD_INC_USE_COUNT;
  file->f_pos=0;                // 読み出し 書き込み 位置を 0
  return 0;
}


// Linux 2.1 以降帰り値 int (事実上共通でも可: カーネル内で返り値使わず)
#if LINUX_VERSION_CODE >= 0x020100
static int cycsel_close(struct inode * inode, struct file * file)
#else
static void cycsel_close(struct inode * inode, struct file * file)
#endif
{
  printk("cycsel_close: (%ld)\n",file->f_version);
  MOD_DEC_USE_COUNT;
  ((FileInfo *)(file->private_data))->f_version=0;
  ((FileInfo *)(file->private_data))->sleep_mode=0;
  ((FileInfo *)(file->private_data))->wtime=0;
#if LINUX_VERSION_CODE >= 0x020100
  return 0;
#endif
}

// poll / select : 2.2 では poll. 2.0 では select
#if LINUX_VERSION_CODE >= 0x020100
static unsigned int cycsel_poll(struct file *file, 
				 struct poll_table_struct *ptab)
{
  FileInfo *fip=(FileInfo *)(file->private_data);
  poll_wait(file,&(fip->wait),ptab);
  fip->sleep_mode=1;
  if(fip->wakeup_jiffies <= jiffies)
    {
      fip->sleep_mode=0;
      fip->wakeup_jiffies=    // 次に起きる時間
	jiffies+(fip->period)-(jiffies-fip->wakeup_jiffies)%(fip->period);
      return POLLIN|POLLRDNORM;                        // 読み込みOK
    }
  else 
    return 0;
}
#else     // 2.0 系用
static int cycsel_select(struct inode *inode, struct file *file, 
			  int sel_type, select_table *stab)
{
  FileInfo *fip=(FileInfo *)(file->private_data);
  if(sel_type==SEL_IN)
    {
      select_wait(&(fip->wait),stab);
      fip->sleep_mode=1;
      if(fip->wakeup_jiffies <= jiffies)
	{
	  fip->sleep_mode=0;
	  fip->wakeup_jiffies=    // 次に起きる時間
	    jiffies+(fip->period)-(jiffies-fip->wakeup_jiffies)%(fip->period);
	  return 1;                        // 読み込みOK
	}
      else 
	return 0;
    }
  return 0;
}
#endif    

// (少なくとも現時点で)他のとだぶらないタイミングを見つける
static unsigned long next_spare(void)
{
  int i,j;
  for(j=0;j<20;j++) // とりあえず、この先20を想定
    {
      for(i=0;i<MAXFILE;i++)
	if((fi[i].f_version) && (fi[i].wakeup_jiffies == jiffies+j))
	  break;
      if(i==MAXFILE) break;  // あたらなかった
    }
  return jiffies+j;
}

// ioctl: Linux 2.0/2.2共通
static int cycsel_ioctl(struct inode *inode, struct file *file, 
		 unsigned int cmd, unsigned long arg)
{
  switch(cmd)
    {
    case 1:  //  起こされ時間の記録
      copy_to_user((void *)arg,
		     &(((FileInfo *)(file->private_data))->wtime),8);
      return 0;
    case 2:  //  周期の設定
      if(arg==0) return -EINVAL;
      ((FileInfo *)(file->private_data))->period=arg;
      return 0;
    case 3:  //  周期を意図的にずらす
      ((FileInfo *)(file->private_data))->wakeup_jiffies=next_spare();
    default:
      return 0;
    }
}


#if LINUX_VERSION_CODE >= 0x020100
static struct file_operations cycsel_fops = {    // Linux 2.2.10 より
  NULL,         // loff_t  llseek(struct file *, loff_t, int)
  NULL,         // ssize_t read(struct file *, char *, size_t, loff_t *)
  NULL,         // ssize_t write(struct file *, const char *, size_t, loff_t *)
  NULL,         // int     readdir(struct file *, void *, filldir_t)
  cycsel_poll,  // u. int  poll(struct file *, struct poll_table_struct *)
  cycsel_ioctl, // int     ioctl(struct inode *, struct file *, u.int, u.long)
  NULL,         // int     mmap(struct file *, struct vm_area_struct *)
  cycsel_open,  // int     open(struct inode *, struct file *)
  NULL,         // int     flush(struct file *)
  cycsel_close, // int     release(struct inode *, struct file *)
  NULL,         // int     fsync(struct file *, struct dentry *)
  NULL,         // int     fasync(int, struct file *, int)
  NULL,         // int     check_media_change(kdev_t dev)
  NULL,         // int     revalidate(kdev_t dev)
  NULL,         // int     lock(struct file *, int, struct file_lock *)
};
#else
static struct file_operations cycsel_fops = {    // Linux 2.0.36 より
  NULL,         // int  lseek(struct inode *, struct file *, off_t, int)
  NULL,         // int  read(struct inode *, struct file *, char *, int)
  NULL,         // int  write(struct inode *, struct file *, const char *, int)
  NULL,         // int  readdir(struct inode *, struct file *, void *, filldir_t)
  cycsel_select,// int  select(struct inode *, struct file *, int, select_table *)
  cycsel_ioctl, // int  ioctl(struct inode *, struct file *, u.int, unsigned long)
  NULL,         // int  mmap(struct inode *, struct file *, struct vm_area_struct *)
  cycsel_open,  // int  open(struct inode *, struct file *)
  cycsel_close, // void release(struct inode *, struct file *)
  NULL,         // int  fsync(struct inode *, struct file *)
  NULL,         // int  fasync(struct inode *, struct file *, int)
  NULL,         // int  check_media_change(kdev_t dev)
  NULL,         // int  revalidate(kdev_t dev)
};
#endif


// 周期
static void cyclefunc(unsigned long /*dummy*/);

static struct timer_list cyclefunc_list =
{NULL, NULL, 0, 0, cyclefunc};
/* { NULL, NULL, 終了時間, 関数呼出引数, 呼出関数 } */


static void cyclefunc(unsigned long c)
{
  int i;
  unsigned long long wtime=get_time();

  for(i=0;i<MAXFILE;i++)
    if((fi[i].f_version)&&(fi[i].sleep_mode))        // 待ちを
      {
	if(jiffies>=fi[i].wakeup_jiffies)
	  {
	    fi[i].wtime=wtime;
	    fi[i].sleep_mode=0;
	    wake_up_interruptible(&(fi[i].wait));        // 起こす
	  }
      }
  /* タイマ再登録 */
  cyclefunc_list.expires = jiffies + basecycle;
  cyclefunc_list.data = c+1;
  add_timer (&cyclefunc_list);
}

int init_module(void)
{
  int i;
  for(i=0;i<MAXFILE;i++) fi[i].f_version=0;            // 未使用マーク

  printk("install '%s' into major %d\n",devname,devmajor);
  if(register_chrdev(devmajor,devname,&cycsel_fops))  // 登録
    {
      printk("device registration error\n");
      return -EBUSY;
    }

  cyclefunc_list.expires = jiffies + basecycle;
  cyclefunc_list.data = 0;
  add_timer(&cyclefunc_list);
  return 0;
}

void cleanup_module(void)
{
  printk("remove '%s' from major %d\n",devname,devmajor);
  if(unregister_chrdev(devmajor,devname)) 
    printk ("unregister_chrdev failed\n");
  del_timer(&cyclefunc_list); 
};

// gcc -o cyclesel_user cyclesel_user.c
//   -DPRINT をつけてコンパイルすると状況を表示するようになる
//   -DUSE_RDTSC をつけると RDTSC利用:ドライバとそろえること
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sched.h>
#include <sys/resource.h>

// 時間取得関数: 
unsigned long long get_time(void)
{
#ifdef USE_RDTSC             // RDTSC 直読み
  unsigned int h,l;
  /* read Pentium cycle counter */
  __asm__(".byte 0x0f,0x31" : "=a" (l),"=d" (h));
  return ((unsigned long long int)h<<32)|l;
#else                        // gettimeofday 使用
  struct timeval tv;
  gettimeofday(&tv,NULL);
  return (unsigned long long)(tv.tv_sec)*1000000+tv.tv_usec;
#endif
}

#ifdef USE_RDTSC             // 1ミリ秒あたりの数字
#define TickPerMSEC 366000.0 // CPU動作周波数
#else
#define TickPerMSEC 1000.0   // 1ミリ/1マイクロ秒
#endif

#define NC 10

struct cdata
{
  int ch;                    /* チャネル */
  unsigned long long utime;  /* ユーザ空間で測定時間 */
};

int main(int argc,char **argv)
{
  int fd[NC],nc,r,i,j,maxfd=0;
  struct cdata crec[10000];
  int ccrec=0;

  setpriority(PRIO_PROCESS,0,-20);
  //{ struct sched_param sp; sp.sched_priority=99; 
  //sched_setscheduler(0,SCHED_FIFO,&sp); }

  if(argc<2)
    {
      printf("cyclesel_user <cycle0> <cycle1> ...\n");
      return 1;
    }
  nc=argc-1;
  if(nc>NC) nc=NC;

  // 初期化
  for(i=0;i<nc;i++)
    {
      fd[i]=open("/dev/csel",O_RDWR);
      if(fd[i]<0)
	{ printf("cannnot open /dev/csel0\n"); return 1; }
    }
  for(i=0;i<nc;i++)
    {
      if(!ioctl(fd[i],2,atoi(argv[i+1])))
	printf("set period of cycle%d to %d\n",i,atoi(argv[i+1]));
      //ioctl(fd[i],3);                    // ずらし設定
      if(maxfd<fd[i]) maxfd=fd[i];
    }

  
  while(1)
    {
      unsigned long long utime,wtime;
      fd_set fds;
      struct timeval tv;
      FD_ZERO(&fds);                           // select の準備
      for(i=0;i<nc;i++)
	FD_SET(fd[i],&fds);                    // 各待機用fd
      FD_SET(0,&fds);                          // 標準入力(stdin)
      tv.tv_sec=1;   tv.tv_usec=0;             // タイムアウト1秒

      r=select(maxfd+1,&fds,NULL,NULL,&tv);    // 読みselect

      if(r<0)
	{ printf("select returned with signal or error\n"); break; }
      if(r==0)
	{ putchar('.'); fflush(stdout); continue; }

      if(FD_ISSET(0,&fds))
	{ printf("key-input detected\n"); break; }

      utime=get_time();
      for(i=0;i<nc;i++)
	if(FD_ISSET(fd[i],&fds))
	  {
	    crec[ccrec].ch=i;
	    crec[ccrec].utime=utime;
	    ccrec++;
#ifdef PRINT
	    ioctl(fd[i],1,&wtime);
	    printf("%d ",i);
	  }
	else
	  {
	    printf("  ");
#endif
	  }

      if(ccrec>10000-nc) break;
#ifdef PRINT
      printf("   at %lld    delay: %lf [ms] (%lld ticks)\n",wtime,
	     (utime-wtime)/TickPerMSEC, utime-wtime);
#endif
    }
  for(i=0;i<nc;i++)
    close(fd[i]);

  for(i=0;i<nc;i++)
    {
      FILE *fp;
      char file[20];
      unsigned long long prev=0;
      sprintf(file,"cycle%d.xy",i);
      if((fp=fopen(file,"w"))==NULL)
	{ printf("cannno open file %s\n",file); break; }
      printf("writing '%s'\n",file);
      for(j=100;j<ccrec;j++) // 最初の不安定期間をとばす
	{
	  if(crec[j].ch!=i) continue;
	  if(prev!=0)
	    {
	      fprintf(fp,"%lf %lf\n",
		      (crec[j].utime-crec[0].utime)/TickPerMSEC,
		      (crec[j].utime-prev)/TickPerMSEC);
	    }
	  prev=crec[j].utime;
	}
    }
  return 0;
}
これらのプログラムのコンパイルおよび実行は以下のようになります。
% gcc -c cyclesel.c -Wall -Wstrict-prototypes -O -pipe -m486
% gcc -o cyclesel_user cyclesel_user.c -DPRINT
% cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 ttyS
  5 cua
  7 vcs
 10 misc              60が空いていることを確認
128 ptm
136 pts
% su
# insmod cyclesel
# insmod cyclesel devmajor=60   (60が空いていないときは空いている数字を指定)
# mknod /dev/csel -m 666 c 60 0
% ./cyclesel_user 20 40 60 80  普通のLinuxなら 2 4 6 8
set period of cycle0 to 20
set period of cycle1 to 40
set period of cycle2 to 60
set period of cycle3 to 80
0 1 2 3    at 0    delay: 955605636100.333008 [ms] (955605636100333 ticks)
0          at 955605636111503    delay: 7.241000 [ms] (7241 ticks)
0 1        at 955605636131491    delay: 0.025000 [ms] (25 ticks)
0   2      at 955605636151490    delay: 0.035000 [ms] (35 ticks)
0 1   3    at 955605636171493    delay: 0.040000 [ms] (40 ticks)
0          at 955605636191492    delay: 0.041000 [ms] (41 ticks)
0 1 2      at 955605636211491    delay: 0.040000 [ms] (40 ticks)
0          at 955605636231492    delay: 0.040000 [ms] (40 ticks)
                     :
0 1   3    at 955605636891493    delay: 0.039000 [ms] (39 ticks)
0          at 955605636911492    delay: 0.040000 [ms] (40 ticks)
0 1 2      at 955605636931491    delay: 0.041000 [ms] (41 ticks)
              エンターキー
key-input detected
writing 'cycle0.xy'
writing 'cycle1.xy'
writing 'cycle2.xy'
writing 'cycle3.xy'
% 

説明

モジュールについてはデバイスドライバに関する知識が要されるので、ここでは解説を省きます(ブラックボックスとして使用してもかまわない でしょう)。

周期実行プログラムは、起動時に引数を10個まで指定できます。 それぞれを周期とした周期実行がなされます。周期の単位は「1タイマ割り込み周期」です(普通のLinuxは2で20ms, 1k-Linuxで20=20ms)。 このプログラムはエンターキーで停止します。 または、合計で10000周期しても止ります。 最後にデータファイルに実測した周期を出力します(cycle0.xy-cycle?.xy, 第1カラム:時刻, 第2カラム:周期)。

周期を20[ms]にしてしまうと、ほとんど直線しか見えませんので、ここでは2,4,6,8[ms]にした結果例を示します。
experimental result of multiple cycle
(生データファイル: cycle0.xy, cycle1.xy, cycle2.xy, cycle3.xy )
1つの時の例同様、ほぼ一定の周期が保たれつつ、ときどき大きく周期がはずれます。 これまでの例と異なるのは、遅れた分は次回に早くなることです。 これは、ドライバ内部での処理の実装によっています。

細かい説明

このドライバは1周期につき1つopen()して使います。 open()の返り値である、ファイル記述子 fd がこのあとこの周期の識別番号になります。

次に、ioctl()で設定します。

ioctl(fd,1,unsigned long long *wtime);
  最後に起こされた時刻(マイクロ秒単位orタイムスタンプ単位)
ioctl(fd,2,unsigned long cycle);
  周期をタイマ割り込みcycle分に設定
ioctl(fd,3);
  複数周期分散
最初の形式は最後に起こされた時刻を返します。 -DUSE_RDTSC をつけてモジュールをコンパイルした場合はPentium以降のCPUに搭載されたタイムスタンプカウンタ(リセット時に0で以後クロック毎に カウントアップ)の値で、つけてない場合は現在時刻をマイクロ秒単位で返します(gettimeofday()の秒、マイクロ秒成分を合成)。

2つ目の形式でこのファイル記述子を使用した場合の周期を設定します。 時間単位はタイマ割り込み周期です。

3つ目の形式は周期をちらす役目を持ちます。上の実行例では、公約数2を持つ周期群を設定しましたが、周期の基準点がopenした時刻であるため、同時にopenすると各実行タイミングがそろいます(0,1,2,3が1行に表示)。 それぞれの周期に制御動作などを割り当てた場合、その処理順によって周期にばらつきが出かねません。 それに対して、同時に複数の周期のタイミングが揃わないようにすれば、それぞれの処理をタイマ割り込みの直後に実行できるはずです(2,4,6,8だと(理論的に)むりですが、3,6,9では可能)。 この場合に、3つ目の形式を実行すると、適当にちらしてくれます(ちゃんとずれるように探すので乱数よりまともです:-)。が、均等にはなりません)。 なお、これは単一プロセスではなく複数プロセスからそれぞれ周期を設定した場合にも効果があります。 それ以前に、各周期設定は完全に独立なので、同じプロセスでも、異なるプロセスでも動作はかわりません。

設定したら、周期実行のループをつくります。基本的に、whileループなどの中で、selectで各周期用のfdをまとめて読み込み待機します。

unsigned long long utime,wtime;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);                           // select の準備
for(i=0;i<nc;i++)
  FD_SET(fd[i],&fds);                    // 各待機用fd
FD_SET(0,&fds);                          // 標準入力(stdin)
tv.tv_sec=1;   tv.tv_usec=0;             // タイムアウト1秒

r=select(maxfd+1,&fds,NULL,NULL,&tv);    // 読みselect
この例はselect()の忠実な使い方です。 fd_set 型の変数に FD_ZERO(), FD_SET()で待機するファイル記述子を設定します。 最後に0をつけくわえているのは、標準入力(stdin)もまとめて待機するためです。 ネットワークにも応答する必要があるなら、ここにはソケットも同じようにならべます。 tv はselectの待機の上限時間設定です。 今回のような使い方の場合、タイムアウトは発生しないはずです。 発生したら、ドライバがおかしいなどの異常事態です。

ここまで準備した上でselect()を実行します。 書き込み・特別用途の待機はしませんので、NULLを設定しました。 maxfd はselectで待機しているファイル記述子群のなかで、一番数字の大きなものです(selectがそういう数字を要求する仕様)。 いずれかの周期が終了して、ドライバが実行タイミングを通知した場合や、標準入力から入力がくると、select()は、「準備ができた数」を返してもどってきます。

返り値が負の場合、普通はエラーですが、シグナルのこともあります。 返り値が0の場合はタイムアウトしています。返り値が正の場合は渡したファイル記述子のいずれかの準備が完了しています。 どの記述子の準備が出来たかを知るには、select()に渡した fd_set 型の変数を調べます(selectが書き換えています)。 具体的には FD_ISSET(fd,fd_set *)を使います。 この例では0ならループ終了、それ以外なら、各周期設定ごとの処理として、時刻の記録と画面表示(-DPRINT指定時)を行っています。 実際には、ここで制御ルーチンなどを呼ぶことになるでしょう。 このように、ループの中が若干複雑になりますが、usleepのかわりにドライバとselectを使うようにして、複数周期に対応するとともに、実行間隔の平均値の確保が可能になります。

もっと細かい説明:ドライバの設定

標準では、1タイマ割り込み毎にselectをしているプロセスのチェックをしますが、もっとチェックを荒らくしてもいい、という場合、insmod するときに オプションで

# insmod cyclesel basecycle=10
とbasecycleを指定すれば、タイマ割り込みその数ごとに、チェックします。 周期自体は ioctl などでタイマ割り込み1つ単位で設定できますが、チェックがあらくなるので、妙な周期になります(長時間の平均値はかわりませんが、基本的にbasecycle単位になります。設定によっては2種類の周期がでます)。

ioctl()によって周期は設定できますが、その他にデバイスのマイナー番号をつかうと、ioctl使わずに周期を最初から設定できます。 特殊ファイルをつくるときに

# mknod /dev/csel  -m 666 c 60 0
# mknod /dev/csel1 -m 666 c 60 1
# mknod /dev/csel2 -m 666 c 60 2
# mknod /dev/csel3 -m 666 c 60 3
とすると、 /dev/csel3 open()すると 初期状態で タイマ割り込み basecycle*3 の周期になります。 マイナー番号は 255 まであるので、一応、そのくらい設定できます。なお、0の場合は周期1秒です。

コンパイル時のオプションに -DUSE_RDTSC があります。 これをつけない場合、ドライバもプロセス側も時刻情報の取得にはgettimeofday()を使います(応答速度をみるためだけのものですので、周期実行には関係ありません)。 Linuxのソースのコメントを信じるならマイクロ秒単位の精度があるそうです。それに対して、-DUSE_RDTSCをつけてコンパイルした場合、時刻の基準をCPUのクロックに求めます。 500MHzのCPUなら、分解能が2nsあることにはなります(そんなに要らないですが)。 ただし、Pentium以降のCPUでしかつかえませんし(いまどきPentiumより前が珍しいですが:-))、 CPUの速度に応じて実時間への変換係数を設定する必要があります。
前者は実時間の数値がそのまま得られ、汎用性に優れるのですが、プロセス側からはシステムコールを呼ばなければならず、時間が必要です(わずかですが)。 それに対して、後者は1クロックの命令一つで済むので非常に高速ですが、変換係数を設定しなければならず、汎用性が低下します。 固定のシステムでは後者をつかうといいとはおもいます。
いずれにせよ、今回のように複数の部分で時刻情報を使いたい場合は統一しなければならないでしょう。

うらわざ?

このドライバは、本来Linuxにある制限の「最低2タイマ割り込み周期でしか周期実行できない」を回避できます。 この制限は、そもそも、usleepなどするとき、最低1周期分は休眠することによります。 そのため、1周期の9割くらいの時間で処理して短時間sleepしてタイマ割り込みを回避して優先順位を保ちつつ、また処理をする、という ps などでみえるCPUの利用率が0%なのに、ほとんどCPUを使いっぱなし、という悪どいことができません。 が、このドライバをつかうと、それが原理的には回避できてしまうはずです。やって得するかどうかはわかりませんが(^^;。


まとめ

以上のように、ちょっとした小細工を加えることで、キー入力やネットワーク、その他デバイスを待ちながら、あわせて複数の周期実行をこなす、といったプログラムをつくることが可能です。 主たる制御プログラムに、やたらと機能を追加することは素のLinuxのスケジューリングにたよる本手法では、できれば避けたいところですが、こういうことも可能、という例として示しておきます。


熊谷正朗 [→連絡]
東北学院大学 工学部 機械知能工学科 RDE
[| ]