Control robots with non-RT Linux
非RT Linux による制御法の実際
Linux による周期実行
一般にロボットなどの制御を行う場合には、数ミリ秒などの単位で、
特定の制御ルーチンを周期的に実行することになります。
Linux においては、周期的に実行する機能、というものはないため、
一定時間の休眠(休憩)をはさみつつ、処理を繰返し行うことになります。
すなわち、
while(1)
{
usleep(5000); // 5msec 休み
DoControl();
}
というような感じです。
さて、この方法がそのまま思い通り動くかというと No です。
その理由は通りです。
- Linux はマルチユーザ・マルチプロセスのOSです。一般に
こういったOSでは「いかに平等に各プロセスにCPU時間を割り振るか」を
CPU割り当ての原則にしているため、場合によってはうまく動くものの、
場合によっては他のプロセスに先にCPUを割り当てられてしまって、
待たされることがあります。
- Linux において、休眠解除はいつでも起きるわけではありません。
つまり、「5msec後」と指定しても、ピッタリその時間に起きるわけではなく、
5msec 後のあるタイミングで解除されることになります。いわば、
「10分単位でしか設定できない目覚まし時計」みたいなものです。
これでは5分後に起こしてほしくとも、うまくはいきません。
ふつうに構築したLinuxのばあい、この間隔が10msec で、しかも、
最低10msec より長い時間たたなければなりません。
つまり、いくら頑張っても、この方法では20msec 単位でしか処理
できません。
しかし、ちょっとした工夫をするだけで、これらの問題はかなりの部分が
解決します。
Linux の動作
ここで、簡単に上のようなプログラムをつくった場合の動作について
触れておきます。
まず、usleep(sleep) を実行すると、Linux によって、そのプロセスは
休眠状態に入ります。休眠状態はそのプロセスがLinuxに対して
CPUの割り当てを要求しない状態です。この状態にすることで、
時間を見ながら無限ループで無駄に時間を過ごして周期的な実行をする、
というMS-DOSでやることがあるような方法に比べて、ほかのプロセスに
迷惑をかけず、CPUを有効利用することができます。
Linuxでは、内部で一定周期でタイマ割り込みが発生しています。
これは一般的なLinuxの場合は 10msec 単位で起きます。
このとき、OS内部の時間に関する処理をいくつか行いますが、そのとき、
休眠しているプロセスを調べて、指定された時間が経過していた場合には、
休眠を解除して、実行可能状態にします。
Linux は現在実行可能状態のプロセスの中から、優先順位の高いものを
実行します。この優先順位は基本的には nice という数値で決定され
ますが、CPUを使いっ放しのプロセスの優先順位は時間とともに下がって
いきます。
以上のような仕掛けの元で、上のようなプログラムを実行すると、
休眠→(タイマ割り込み×n)→休眠解除→CPU割り当て
ということになります。先ほどの2点の問題点は、起きる時間が
タイマ割り込みのときのみ、かつ休眠解除しても優先順位の関係で
CPUが割り当てられないことがある、ということに起因します。
優先順位の問題解決
まず、優先順位の問題を解決します。これは簡単で、
# nice --20 program
と、プログラムの前に nice --20 をつけます。これはプログラムの
優先順位をあげてやるもので、標準で0、最低で20、最高で -20 です
(--20 の一つ目の '-'はオプションの印でそのあとの'-20'が数値)。
こうすることで、休眠解除後に実行される確率が非常に高くなります。
なお、この nice は標準より優先度をあげるには root でなければ、なりません。
特定のプログラムを常に優先度を高く実行したい、という場合には、
別の方法があります。
とはいえ、休眠はほんの少しで、おおむねCPUを使いっ放しにするような
プログラムをつくると、いくら起動時に優先度をあげても、優先順位は
時間とともに低下、思った通り実行されなくなりますので、要注意。
さらに、強制的に優先度をあげて、他のプロセスより必ず先に実行
させるという方法もありますが、詳細は他にゆずります。
休眠周期の分解能向上
次に、時間分解能を向上させます。先に述べたように、標準状態では
20msec 以上、10msec 単位でしか周期実行できず、非常に不便です。
しかも、他のプロセスにCPUをとられてしまった場合、最悪、この
10msec の間CPUを占有されてしまい、大幅な遅れが発生します。
解決案は非常に単純です。タイマ割り込みをもっと短くします。
10分単位の目覚まし時計を1分単位に改造するようなものです。
具体的には、カーネルのソースの /usr/src/linux/include/asm/param.h に
#ifndef HZ
#define HZ 100
#endif
という部分があります。この HZ を 1000 にすれば、1msec 単位で
タイマ割り込みが、すなわち、休眠解除ができるようになります。
当然、タイマ割り込みの頻度が10倍になるので、その処理の分だけ
全体の処理速度は落ちますが、微々たるものです。現在のところ、
これによって生じている弊害は 'top' の時間表示がおかしくなった
ことのみで、安定性には変化はみられていません。
なお、当然、ソースを変えただけではだめで、カーネルの再コンパイルが
必要です。
実験・考察
さて、どのくらいきれいに周期実行ができるかどうかを、ここで
検証しておきましょう。上で「確率が非常に高い」などと曖昧な
表現だったのは、この方法はあくまで「簡単にそこそこの性能」を
目的にしたものであって「完璧」な性能は出ない、ということにあります。
それでも、私としてはロボット制御には十分だとおもっています。
実際にデータをご覧の上、御評価下さい。
実験は周期測定用のダミー処理入りプログラムを
つかっています。パソコンは Celeron 462MHzで、X を立ち上げて、
kterm や mule がいくつか起動された状態、Linux は 2.2.10 を
上記 HZ を 1000 にしたものです。
処理は適当に三角関数を計算し、ランダムに処理量が変動します。
周期性を保つ部分には、処理にかかった時間から適切な休眠時間を
割り出すような処理をいれてあります。目標周期は5msecで1万周期の
データを示します。
このように、nice の設定やコンピュータで稼働中のプログラムにも
大きく依存しますが、1万回に数回レベルで1タイマ割り込み周期くらい
遅延が発生します(10msec周期の標準的なLinuxの場合、20msecを指定
しても、30になることがあります)。これがどう影響するかは処理内容次第と言えると
おもいます。PID制御で時間間隔が変わると困る、ということもあると
思いますが、現在時間を細かく知る方法もあるので、それをもとに
サンプリング間隔に依存するパラメータ(D,Iゲイン)を修正すれば、
この程度ならそれほど影響はないとおもいます。また、よほどきれいな
環境で理想的な制御をするのでなければ、むしろ、外乱の方が大きな
影響になると、私は考えています。
補足:cyclic.c
- sin,cos をつかっていますので、コンパイルには -lm をお付け下さい。
- Pentium 以降のCPUの場合、
CPU内蔵のカウンタを利用して時間を高精度に測定できます。
この場合、#define USE_RDTSC を有効にしてください。また、
変換定数 TickPerMSEC をクロック測定
プログラムなどで指定してください。簡易的には、CPUの
クロック数を1000で割った値が使用できます。
- 目標周期は cyclic 20 などと実行時の引数で指定できます。
- プログラムを Control-C でとめると、周期変動が cprof.xy に
最近1万回分出力されます。
- ダミーの計算は Dummy() で行います。引数に比例して計算量が増えます。
- 休眠時間の設定は3種類の方法を例示しておきます。適当にコメント
アウトして、どのように変化するかをお試し下さい。
なお、方法3は方法2に比較して、一回余分に usleep するため、
(処理時間+4×タイマ周期)以下の周期で実行できません。そのかわり、
処理時間がタイマ周期とほぼ変わらなくなったときに、周期が安定
しやすいはずです。
非RT Linux によるロボット制御
熊谷正朗/くまがいまさあき/Masaaki Kumagai
kumagai@emura.mech.tohoku.ac.jp