Linuxによる一定周期実行 |
まず やってみましょう |
まずはソースリストです。時間計測にgettimeofdayを つかいます。Linuxではこの関数はマイクロ秒オーダの分解能が あるようですが、信じられない場合は -DUSE_RDTSC をつけて コンパイルしてください。その場合、Pentium 以降のCPUで装備された RDTSC という、リセット時からのCPUクロックの数を数える命令を つかって時間を計ります。TickPerMSec には CPUの動作周波数を 1000で割ったくらいの数字を書いておけば、だいたいの精度はでます。
#include <stdio.h> #include <sys/time.h> #include <unistd.h> #include <stdlib.h> #include <math.h> #define TestCount 1000 #ifdef USE_RDTSC /* Pentium 以降のRDTSC命令を使用 */ /* 1秒あたりの 1 tick */ #define TickPerMSec 200e3 /* CPU の周波数に依存: だいたい公称値 */ /* 現在時間取得: 単位 tick */ unsigned long long GetTick(void) { 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 /* 1秒あたりの 1 tick */ #define TickPerMSec 1e3 /* 現在時間取得: 単位 tick */ unsigned long long GetTick(void) { struct timeval tv; gettimeofday(&tv,NULL); return (unsigned long long)tv.tv_sec*1000000+tv.tv_usec; } #endif int main(int argc,char **argv) { int cycle=15; int i,j; double a=1; FILE *fp; unsigned long long ticks[TestCount]; if(argc>1) cycle=atoi(argv[1]); for(i=0;i<TestCount+3;i++) /* 一定周期ループ */ { /* 計測 */ if(i>2) /* 最初は絶対ずれるからパス */ ticks[i-3]=GetTick(); else GetTick(); /* 空読み */ /* ダミー処理 */ for(j=0;j<100;j++) a=sin(cos(a)); /* 休止 */ usleep(cycle*1000); /* msec->usec */ } fp=fopen("cycle.xy","w"); for(i=0;i<TestCount-1;i++) { /* number, interval, time */ fprintf(fp,"%d\t%lf\t%lf\n",i, (ticks[i+1]-ticks[i])/TickPerMSec, (ticks[i]-ticks[0])/TickPerMSec); } fclose(fp); return 0; } |
このプログラムは 引数を指定するとその時間([ms])だけ休むように なっています(usleep)。デフォルトでは15[ms]です。 この数値を変化させて結果を取ってみると以下のようなことがわかります。
|
より間隔を短く&細かく設定したい場合には、Linuxのソースを 数文字(0をひとつ足すなど)すれば希望通りになります。
その話のまえに、Linuxカーネルの動作の解析をしておきます。 いわば、本手法の原理にあたる部分です。
なんでもいいから早く試してみたいという方は Linux kernel:/usr/src/linux/include/asm/param.h の HZ という数値の定義を 100 から1000 にして再構築し、その カーネルで起動してみてください(モジュールの再構築も)。 2[ms]以上, 1[ms]単位で周期が決められるようになっているはずです
動作の解析 |
Linux とプロセス |
|
スケジューリングの選定条件 |
さて、その選定条件ですが、基本的には
|
この持ち時間は次に述べるタイマ割り込みのときにCPUを使用していた プロセスから減らしていきます。つまりCPUを使い続けるような プログラムはどんどん優先度が下がっていきます。それに対して、 タイマ割り込みになる前に sleep などでCPUをOSに返してしまえば、 CPUは使っていても、持ち時間そのままなので、実行可能状態に もどりさえすれば、CPUをもらいやすいことになります。
ちなみに、実行可能状態にあるプロセスの持ち時間がすべて0に
なると持ち時間の再計算が行われます。それは
という式で行われます。ここで c' は新しい数値、c は現在の持ち時間、
p はプロセスの優先順位を表す数値で、nice値(niceコマンド、
nice()システムコールで設定)によります。nice値1あたり、
再計算間で 200[ms]だけ余計にCPUがもらえるようになっています。
また、寝てばかりのプロセスのcは2pに漸近します。
これらのことから、
|
Linux とタイマ割り込み |
このタイマ割り込みで行う処理は
|
Linuxによる一定周期実行の原理 |
Linux の場合、
|
|
少し処理をして、sleep で休眠するようなプロセスは、スケジューリングの ところで述べたように、基本的に持ち時間は高くなっています。その結果、 ほぼ確実に、CPUを得られるわけです。その結果が上のグラフになります。 多少乱れが生じているのは、システムコール中だったりするためです。
さて、タイマ割り込みで同時に複数のプロセスが実行可能状態になったら、
どうなるでしょうか。そのときはもちろん、持ち時間の多い方が優先です。
UNIXにおいては、多くのプロセスが走っていますが、そのほとんどが休眠
状態です。つまり、目的の制御用に周期的に実行しているプロセスの
他のプロセスが起きてしまったらそっちにCPUをとられます。その場合、
単純な処理ならいいのですが、最悪の場合は次のタイマ割り込みまで
CPUが得られないということがあり得ます(それどころか、そのさき
2〜3回ということも)。この状態を回避するには、最初から
持ち時間を多くしておくに限ります。具体的にはnice --20 a.out
とします。これは nice値を -20 に設定することを意味します。
(数字が多いほどniceである、ということは-20というのはある意味極悪)
この nice値は root でなければ負の値は設定できませんが、
I/Oポートなどを操作しようと思ったら、やはり root でなければならないので、
それはそれでよしとしましょう。いっそのことプログラムに
nice(-20);などと書いてしまうのもありです。
まとめ |
|
このような仕掛けによって、一見いいかげんなプログラムで、 タイマ割り込みで量子化された周期で、周期的に処理を 実行することが可能なのです。
この方法が、いい加減なように見えてちゃんと動くのは、このように OSの動作が都合良く設計されていたためですが、逆にOSの動作が 分かってしまったので、いい加減な方法ではなく、理論に基づいた 手法となったのです。