第 3 章 初歩の RTLinux プログラミング 27
3.5 組み込み中にも実行されるプログラムの解説
それでは、先程のプログラムの動きを解説します。
まず、このサンプルの大まかな動きを以下の図に示します。
init_module
argo_task
cleanup_module
pthread_create pthread_cancel
insmodコマンド rmmodコマンド
1. insmodコマンドでsample2モジュールを組み込む時、init_module関数が呼ばれます。その中で、
pthread_create関数により、argo_task関数がRTLinuxスレッドとして生成,起動されます。
2. argo_taskスレッドは、pthread_make_periodic_np関数により、自身の実行周期を、1秒に設定しま す。
3. argo_taskスレッドの、whileループ内では、rtl_printf関数により、システム起動時からの時間を秒
単位で表示します。その後、pthread_wait_np関数により、自身のRTLinuxスレッドをスリープさせ、
システムから次の周期呼び出しまで待機します。
(周期呼び出しは、先に設定したように、1秒間隔です)
4. whileループ自体は永久ループなので、特に指示ない限り、回り続けます。
5. rmmodコマンドでsample2モジュールをRTLinuxから削除する時、cleanup_module関数が呼ばれま
す。その中で、pthread_cancel関数により、argo_taskスレッドに対してスレッド終了の通知が発 せられ、argo_taskスレッドは終了します。
6. 全ての終了処理を終え、sample2モジュールの解除は完了します。
では、上の図に沿って、もう少し詳しくプログラムを読み解きます。
まず、init_module関数から行きます。
★グローバルシンボル
モジュールがカーネル内に組み込まれると、公開されたシンボル(グローバル変数や、グローバル関数等) は、他のモジュールから参照できるようになっています。それを防ぐためにEXPORT_NO_SYMBOLSマ クロを定義します。
もし、これを行わないと、どうなるのでしょうか?
例えば、Aのモジュールでcount変数を定義し、Bのモジュールで何の気なしに参照すると、Bのモジュー ルはAのモジュールのcount変数と共有してしまうことになります。
この共有をプログラマが意図してなかった場合どうなるでしょうか?
恐らく、原因不明のバグとして、悩みつづけることになるでしょう。
EXPORT_NO_SYMBOLSマクロは、これら意図しないシンボルの共有を防ぐためのものです。
なお、グローバルシンボルは、/proc/ksymsで確認することができます。
以下に、EXPORT_NO_SYMBOLSを付けた場合と、付けない場合のシンボルを比較します。
<EXPORT̲NO̲SYMBOLS なし> <EXPORT̲NO̲SYMBOLS あり>
c88e8000
̲̲insmod̲sample2̲O/root/test/sample2.
o̲M3CBF7E4C̲V132100 [sample2]
c88e83a8 argo̲task̲info [sample2]
c88e83a8 ̲̲insmod̲sample2̲S.bss̲L4 [sample2]
c88e8060 argo̲task [sample2]
c88e8060 ̲̲insmod̲sample2̲S.text̲L384 [sample2]
c88e8000
̲̲insmod̲sample2̲O/root/test/sample2.o̲M 3CBF8491̲V132100 [sample2]
c88e8060 ̲̲insmod̲sample2̲S.text̲L384 [sample2]
c88e838c ̲̲insmod̲sample2̲S.bss̲L4 [sample2]
(41行目:pthread_create関数)
pthread_create関数では、RTLinuxモジュールを組み込んでいる間、動作し続けるRTLinuxスレッド を生成しています。
ここでは、argo_task関数をスレッドの実行単位として指定しています。
★RTLinuxスレッドを作らずに動かすことはできるか?
RTLinuxスレッドを作るのが面倒だからと言って、pthread_create関数を省き、argo_task関数の処理内容
を、init_module関数内に持って来てはいけません。
これを行うと、プログラムが完全に停止状態に陥ります。
これは、init_moduleの処理が、RTLinuxのカーネルスケジューラに戻らないためです。
(注意:真似をしないでください。どのキーも受けつけなくなります)
次は、argo_task関数本体に移ります。
(15,16行目:pthread_setschedparam、pthread_make_periodic_np関数)
ここでは、自身のRTLinuxスレッドに対する、スケジューリング,優先度の設定,実行周期を設定し ています。
実行周期は、pthread_make_periodic_np関数で設定します。
周期は第3引数で、ns(ナノ秒)単位で設定します。サンプルプログラムでは、”1 * 1000 * 1000 * 1000
= 1s”とし、1秒の周期を設定しています。
(19~27行目:whileループ)
while文により、永久ループしているのが目に付きます。
これは特に指示されない限り、RTLinuxスレッドを実行し続けるための工夫です。
もし、このwhileループを外してしまうと、すぐに終了してしまいます。
(中身はただの関数です)
(21~23行目:clock_gettime関数等)
21~23行は、現在のタイマー値を読み取り、rtl_printf関数でデバッグプリントを行っています。
(26行目:pthread_wait_np関数)
whileループ中のpthread_wait_np関数は、実行中のRTLinuxスレッドをスリープさせ、他のRTLinux スレッドに対する実行権を明示的に渡す関数です。
システムは、他のRTLinuxスレッドへ実行権を渡し処理を続けます。
他のRTLinuxスレッドの処理が終わると、システムは優先度の兼ね合いを判定し、このRTLinuxス
レッドに再び実行権を渡します。
★pthread_wait_npを外すと、どうなるか?
ところで、このサンプルから、pthread_wait_np関数を排除するとどうなるでしょう?
結果は、他のどのRTLinuxスレッドにも実行権が移らなくなり、いわゆる固まった状態に陥ります。
(注意:真似をしないでください。どのキーも受けつけなくなります)
通常の Linux プログラムならば、別途シェルを起動して、永久ループに陥ったプロセスを kill して復帰
することも可能ですが、RTLinuxプログラムでは、そんなに甘くありません。文字通り固まります。
これは、見方を変えると、RTLinux では、他の何者に対しても優先的に処理することが可能だと言う証 拠でもあります。
(他のプロセスを立ち上げkillできるということは、実行しているプロセスより優先的に動くプロセスが
存在するということです)
最後に、cleanup_module関数に目を向けます。
(51,52行目:pthread_cancel関数、pthread_join関数)
これは、このRTLinuxモジュールをシステムから削除するので、実行中のRTLinuxスレッドを終了
■関数 & 用語解説
先程出た用語について、幾つか簡単に解説します。
★init_moduleでの禁止事項
RTLinuxモジュールは、カーネル空間に存在するため、ユーザ空間でできないことができる反面、一歩
間違えると、カーネルやファイルシステムを破壊してしまう恐れがあります。
代表的な禁止事項として、init_moduleや割り込みハンドラ内でusleep関数を呼び出してはいけません。
Linuxが起動しなくなってしまう場合があります。
他にも、禁止事項が幾つかあります。RTLinux関連のドキュメントをよく読んでください。
★pthread_create
RTLinuxスレッドを作成する関数です。
第1引数にはRTLinuxスレッドのIDを格納するpthread_t変数へのポインタを指定します。
第2引数にはRTLinuxスレッドの属性を指定するpthread_attr_t変数へのポインタを指定します。
第3引数には作られたRTLinuxスレッドで処理する関数を指定します。
第4引数にはその関数に渡す引数を指定します。
プログラム例では、RTLinuxスレッドの属性は指定しないため、NULLを指定しています。
★pthread_self
呼び出した自身の、RTLinuxスレッドのIDを取得することができます。スレッドIDを引数パラメータ に指定する関数に対して自身のスレッドIDを指定する場合等に使用します。
★pthread_setschedparam
RTLinuxスレッドのスケジューリング方法,優先度の設定を行う関数です。
第1引数では、RTLinuxスレッドのIDを指定します。(ここでは、自身のスレッドIDを返すpthread_self 関数により、自身のIDを取得しています)
第2引数では、スケジューリング方法を指定します。ここではSCHED_FIFOをパラメータ値に指定して います。
第3引数では、プライオリティ等の設定を行うsched_param構造体のポインタを渡しています。ここで はプライオリティ値を5に設定しています。
なお、プライオリティとは日本語で「優先度」を意味し、複数のスレッドを動かした際、どのスレッド から実行を行うか、指定を行うことができます。
★pthread_make_periodic_np
あるRTLinuxスレッドに対し、実行周期の間隔を指定する関数です。
第1引数にはスレッドのIDを指定します(ここでは、自身のスレッドのIDを返すpthread_self関数によ り、自身のIDを取得しています)。
第2引数には、リアルタイム処理の実行を開始する時間をhrtime_t型で指定します(ここではシステム起 動からの絶対時間を取得するgethrtime関数を用いて、開始時間を関数呼び出しと同時にしています)。
第3引数には、リアルタイム処理を実行する周期をns(ナノ秒)単位で設定します。おなじくhrtime_t型 で指定します。
リストでは、pthread_wait_npによるスレッドの待機箇所を起点として、指定された周期でwhileループ 中の処理が実行されます。
★pthread_wait_np
pthread_wait_np関数は、pthread_make_periodic_np関数により、指定された周期まで待つ関数です。
例えば、pthread_make_periodic_np関数により10msの周期を指定された場合、10msが経過するまで、こ
のpthread_wait_np関数で待つことになります。
また、pthread_wakeup_np関数を呼ぶことにより、実行を再開することもできます。
(但し、別のRTLinuxスレッドからスリープ状態のRTLinuxスレッドに対して、pthread_wakeup_np関数
で呼び起こす必要があります)
★終了時の関数
pthread_cancel関数は、引数に終了要求を出すスレッドIDを指定します。pthread_join関数も第1引数に
同じくスレッドIDを指定します。第2引数は、ここではNULLにしています。
なお、pthread_exit関数でRTLinuxスレッドの処理を終わらせた場合、pthread_exit関数の引数が、
pthread_join関数の第2引数に格納されます。
以下に例を示します。
void* input_task(void *arg) {
int status = 10;
・・・
pthread_exit(status);
・・・
}
void cleanup_module(void) {
int value;
pthread_join(send_thread, &value); // valueには10が格納されます ・・・
}