2006/03/20
時間処理の落とし穴 - 時間精度とtick
「旦那様、旦那様、起きて下さいませ。睡眠薬を飲む時間です」
マルチメディア関連のソフトウェアやデバイスドライバ、組み込み系のようなリアルタイム処理が必要なプログラム等を書いていると、タイミング制御や経過時間の取得等、時間に関する処理が必要になってきます。時間処理については、正しく理解していないと陥りがちな落とし穴がいくつかありますので、今回はそのあたりについて書いておこうと思います。
sleep系関数の精度と挙動
一定時間ごとの周期処理や、リソース競合時の遅延リトライ。そんな時に用いられるのがsleep系関数(sleep/usleep/nanosleep)です。一定時間呼び出しスレッドを停止させる関数(システムコール)であり、sleepなら秒、usleepならマイクロ秒(1/1000000秒)、nanosleepならナノ秒精度(1/1000000000秒)の指定が可能になっています。
しかし、実際この精度で、次に起床されるタイミングを制御できるわけではありません。実際、x86系のLinuxの実装では10ms以下の精度の指定は、10ms精度に丸めらてしまいます(※1)。これは、sleepによって、待ちキューに入ったスレッド(プロセス)を起床させるタイミングが、tick単位であるためです。tickは、上位アプリケーションの開発者にはあまり馴染みがないかもしれませんが、OSが周期的な処理間隔であり、x86 Linuxの場合、これが10msになのです。
大抵のOSでは、tick単位で、タイマ割り込みが発生し、このタイマ割り込みのタイミングで、時間指定でsleepしていたスレッド(プロセス)が起床されるという実装になっています。このため、tick以下のsleep時間を指定しても起こされるタイミングがないのです。
こう書くと、何でtickはそんなに大きな(※2)数字になっているんだという疑問もでそうなのですが、これには理由があります。このtickを小さくすればするほど、定期的なタイマ割り込みの頻度があがるため、システム全体の負荷に影響が出てしまいます。それをおしてでもということであれば、カーネルソース内のtick定義を変更すれば、sleep精度を改善することは可能です。但し、非常の根本にかかわるところなので、変更には十分な注意が必要です。
以上は、POSIXのsleep関数の問題ですが、ITRON系のwai_tsk()システムコールではもともとsleep時間をtick単位で指定するような仕様であるため、このような勘違いは起こりにくい仕様になっています。こういうところに、もともと組み込み等のリアルタイム処理向けの仕様として作られたか、汎用システムの仕様として作られたかという点に起因する思想の違いが見てとれます。
※1 Linuxのnanosleepでは、リアルタイムスレッドで2ms以下のsleep指定をするとbusy waitによるsleepを行います。よって、2ms以下の精度のsleepも可能にはなっていますが、busy waitのため、他のスレッドは動作できません。
※2 10msというと十分短いと感じる人もいるでしょうが、リアルタイム処理系にとっては、非常に長い時間です
時間情報取得関数の精度
sleep以外の時間処理としては、時刻や経過時間等の時間情報を取得するというケースがあります。リアルタイム系の処理では、定期的なポーリングで経過時間を取得しつつ処理を行ったりする時等に用いられます。リアルタイム処理以外でも、処理の実行時間等のプロファイル計測に用いられることが多いでしょう。
さて、この時間情報取得関数、POSIX準拠のC言語ライブラリだけでもいくつか種類があります。時間情報を正しく取得するためには、これらの違いとメリット・デメリットを正しく理解しておく必要があります。
◎clock()
clock()はANSI Cで定義されており、その返り値の型はclock_tです。clock_tの単位はCLOCKS_PER_SECtとして定義されており、POSIXではこれ(CLOCKS_PER_SEC)が1000000(1us)である亊を要求しています。しかし、この精度があるというわけではなく、実際大抵はtick精度しかでません。なお、clockは実行中のプロセスの処理経過時間を返すため、APIのプロファイル等には便利です。
◎gettimeofday()
時間情報取得の定番です。現在の時刻情報が取得できます。これも単位はusecだが、こちらはtick以下の精度もでる。但し、実行中以外のスレッドも含む全システムでの経過時間を返すため、プロファイリングで用いる時には注意が必要です。また、現在時刻を取得するというものであるため、バックグラウンドでntp等が動いていると、時刻情報に補正が入ったために経過時間計測が狂うことがあるのでこれも注意が必要です。
◎times()
clock()同様にスレッドの実行時間をtick精度で取得できます。tick精度の情報であれば十分です。
◎getrusage
POSIX系ならこれば一番無難でしょう。gettimeofday同様精度が高く、times、clockのようにプロセス単位の経過時間も取得できます。
【関連リンク】
・Manpage of sleep/usleep/nanosleep
・Manpage of gettimeofday/clock
・C言語: 実行時間測定の方法
・Linuxによる一定周期実行
【関連書籍】
・リアルタイム組込みOS基礎講座 Qing Li
・RTLinuxテキストブック―ハードリアルタイム機能を使いこなす Matt Sherer FSMLabs
・リアルタイムJavaプログラミング ピーター・C・ディブル
・詳解Linuxカーネル 第2版 ダニエル・P・ボベット 他
・Linuxデバイスドライバ 第2版 Alessandro Rubini 他
・組み込みLINUXシステム構築 カリム・ヤフマー
コメント
10msではなくHZ依存では?
nanosleep のx86における精度ですが、
これはKernel のバージョン依存で、現行の 2.6 では 10ms, 10/4ms, 1ms を選べるはずです。
2.6 初期は 1ms 固定でした
HZ という「1秒間にタイマー割り込みの入る回数」を設定する定数が、2.4の時は100でしたので、
そのときは精度も10msでしたが、現在は100, 250, 1000で選択できるようになっていると思います。
デフォルトの HZ は 250 だったと思います。
もちろん 1ms でもリアルタイム処理には全く足りませんので、記事の他の部分に特に異論はありません。
お時間取らせて失礼しました m(_ _)m
2006/03/20 23:07 by もわ URL 編集