ジムカーナ用タイマーを作成する為にリアルタイムパッチが当たったカーネルを導入してみました。各種ドキュメントによるとpreempt_rtパッチが当たった場合、config_preemptよりもタイムスライスが短くなり、応答する割り込みが増えるようです。通常のリナックスで10ms、config_preemptで100us、preempt_rtで30usぐらいだそう。
ただ公式のサンプルは周期実行のみで、割り込みではどういった挙動を示すのかについては触れられていません。割り込みについては、preempt_rtパッチが性能を向上させるのか、させないのか、ここは自分の目で確認してみようと思いました。
その前に公式のサンプルの読解です。そうしないとリアルタイムタスクの作り方が分かりません。
https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO
何もしないプログラムの割には結構長いんです。 長くなっている理由は2つで、メモリフォルトの防止と高精度なnanosleepの準備でした。
mlockallはページがスワップエリアにスワップされるのを防止して、ramに残ることが保証します。stack_prefault はmemsetをダミーで呼び出して、予めスタックフォールトを発生させておくようです。いずれもリアルタイムタスクにコンテクストスイッチが発生する際にページフォルトが起こるのを防止して、応答性を上げるための配慮ですね
以下のコードclock_nanosleepとこれを使うための準備です。
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL); 絶対値としての時間が経過するまでスレッドを停止 clock_gettime(CLOCK_MONOTONIC ,&t); ある単調時間の増加により表現されるクロックの取得 while (t.tv_nsec >= NSEC_PER_SEC) { t.tv_nsec -= NSEC_PER_SEC; t.tv_sec++; } t.tv_nsecが1000000を超えている場合にtv_secを増やしてt.tv_nsecを1000000以下にする
実装してみたコードがこちら。
#include <stdlib.h> #include <stdio.h> #include <time.h> #include <sched.h> #include <sys/mman.h> #include <string.h> #include <wiringPi.h> #define MY_PRIORITY (49) /* we use 49 as the PRREMPT_RT use 50 as the priority of kernel tasklets and interrupt handler by default */ #define MAX_SAFE_STACK (8*1024) /* The maximum stack size which is guaranteed safe to access without faulting */ #define NSEC_PER_SEC (1000000000) /* The number of nsecs per sec. */ void stack_prefault(void) { unsigned char dummy[MAX_SAFE_STACK]; memset(dummy, 0, MAX_SAFE_STACK); return; } void myInterrupt0 (void) { if(digitalRead(0)){ digitalWrite(7,HIGH); } else { digitalWrite(7,LOW); }; } int main(int argc, char* argv[]) { struct timespec t; struct sched_param param; // int interval = 500000000; /* 500ms*/ int interval = 1000000000; /* 1000ms*/ // int interval = 50000; /* 50us*/ /* Declare ourself as a real time task */ /* param.sched_priority = MY_PRIORITY; if(sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) { perror("sched_setscheduler failed"); exit(-1); }*/ /* Lock memory */ if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { perror("mlockall failed"); exit(-2); } /* Pre-fault our stack */ stack_prefault(); clock_gettime(CLOCK_MONOTONIC ,&t); /* start after one second */ t.tv_sec++; wiringPiSetup () ; pinMode (7, OUTPUT) ; wiringPiISR (0, INT_EDGE_BOTH, &myInterrupt0) ; while(1) { /* wait until next shot */ clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL); /* do the stuff */ // printf("hello\n"); /* calculate next shot */ t.tv_nsec += interval; while (t.tv_nsec >= NSEC_PER_SEC) { t.tv_nsec -= NSEC_PER_SEC; t.tv_sec++; } } }
0番ピンの入力で割り込みを発生させて、7番ピンの出力をそれに合わせます。それだけ。外部で作った波形を入力してオシロで観測し、波形の位相差を観測するというもくろみです。
配線はこんな感じ。ごちゃごちゃですね。
優先度の確認方法 pi@navio-rpi ~ $ sudo ps ax -l スレッドを含めた優先度の確認方法 pi@navio-rpi ~ $ ps -em -o pid,tid,policy,pri,ni,rtprio,comm
実際に優先度が設定されているかはこれらのコマンドで確かめます。wiringPiの割り込みはスレッドで動いていて、スレッドの方のコマンドだと、設定数値を同じ数値を確認できます。grepでソートできないのが辛いですが。
で、以下が優先度を設定した波形。緑色がPiに入力されているマイコン側の波形です。黄色がPiから出力されている波形。パルス幅が200usなので遅延はおよそ100usぐらいでしょうか。
で、優先度を外ます。以下のコードの部分をコメントアウトします。
struct sched_param param; param.sched_priority = 48; if(sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) { perror("sched_setscheduler failed"); exit(-1); }
で、これが波形。
変わらん、、、。負荷をかけているのか以下のコマンドです。
stress --cpu 99 --vm 10--timeout 10m
周期実行の時にはこのコマンドの影響がもろに出て、非リアルタイムタスクは実行周期がボロボロになりました。が、今回の実験では結果がほぼ同じです。ということは、wiringPiの割り込み、内部的にはpoll()で実装されているのですが、これは実行主体であるタスクの優先度が高くなくても、優先度が高くなるということでしょうか?謎です。
で、ここで次に疑問となるのは、リアルタイムパッチが当たっていないカーネルで実行したらどうなるのか。使っているカーネルがパッチが当たっているのでこれだけの応答性があるのかもしれません。そこで普通のRaspbianを用意しました(そのために3時間もかかってしまった。泣ける。)。
で同じことをやった結果がこちら。優先度の設定部分は外しています。
やっぱり変わらない。負荷をかけてもかけなくても同じです。更にいえばpreempt_rtパッチ有りよりも、割り込みへの反応性がちょっとだけ高い感じがします。
これから作ろうとしているシステムにおいて必要なリアルタイム性は割り込み応答性のみなので、こうなるとpreempt_rtパッチを使う必要がありません。本当はもっと定量的に評価をすべきなんだと思いますが、これをpiの反応を更にマイコンで捕捉して時間計測し統計を取るところまでやっていると時間がかかりそうです。普通のカーネルでもオシロの波形がぶれることがほとんど無いので、今回はやっぱり公式のJESSIEをつかって見ようかと思います。





納得したらすぐにシェア!