SOLID-OSの特徴 (連載3)
いつも読んでくださりありがとうございます。
今回は、SOLID-OSの特徴についてご紹介します。
大きな特徴は、以下3点です。
LinuxとCPUリソースを分け合って共存することができる
リアルタイムOSである。
リアルタイムOS特有のデバッグ機能がある
1.Linuxとの共存
SOLID-OSは、Linuxと共存して動作するための仕掛けを持っています。
このSOLID for Raspberry Pi 4で、体験することができます。
1.1 LinuxとSOLID-OSの配置
連載1で記載したとおり、Raspberry Pi 4Bには、CPUコアとしてARM Cortex A72コアが4個入っています。
その4つのCPUコアの分担は以下のとおりです。
CPU2つ: Linux
CPU2つ: SOLID-OS
詳細はこちら。
1.2 PC上のSOLID-IDEとSOLID-OSへの通信
SOLID-IDEとSOLID-OSの間に、ICEデバッガは存在しません。
デバッグ機能を実現のために、Linuxのネットワーク機能であるSSHを使用しています。
ビルドしたオブジェクトのダウンロードも同様、Linuxのネットワーク機能を使用しています。
すなわち、Linux側でSOLID-OSのオブジェクトを書き換えることができるため、うまく実装すればSOLID-OS 部のFOTA(Firmware update Over The Air)も可能という事になります。
リアルタイムOS部をまるまる、まるっと書き換えることができるので、いろんな用途が考えられそうですね。
ちなみに、Linux側も、カーネルやドライバ、アプリのファイルを書き換ることにより、アップデートできます。
という事は、Linux側もSOLID-OS側もどちらもアップデートすることが可能。メンテナンス性に優れたシステムが構築できますね。
デバッグ機能にもSOLID-OSのオブジェクトを書き換えにも、Linuxのネットワーク機能を使用しているため、当然ながらLinuxとSOLID-OS間でOS通信を頻繁に行っています。
ユーザプログラム側から見える代表的なOS間通信としては、前回ご紹介したLinuxファイルシステムのファイルを読み書きする機能が挙げられます。
他にも、ネットワークソケット機能を使用する場合、Linuxのネットワークドライバを使用するために、OS間通信を使用しています。
ARM Cortex A72マルチコア上で、LinuxとリアルタイムOSを同時に実行させることができ、それらのOS間通信でファイルのやり取りもできるため、いろいろな応用が考えられます。それを簡単にお試しできるのが、SOLID for Raspberry pi 4です。
2.リアルタイムOSであること
SOLID-OSはマルチコア対応のTOPPERS/FMP3 RTOSカーネルの独自拡張版を使用しています。正真正銘、リアルタイムOSです。
今まで見てきたように、SOLID-IDEを使えば、ICEを使う場合と同じようなデバッグを行う事ができます。
ブレークポイント、Watch機能、、、等、普通にできます。
では、リアルタイムOSに特化したデバッグ機能はどうでしょうか。
リアルタイムOSカーネル部はバイナリ提供なので、カーネル内部のデバッグはできません。
ですが、タスク間のやり取りなど、リアルタイムOSを使ったシステムのデバッグをするための機能は一通りそろっています。
例えば、
タスクビューアー、イベントフラグ、並列タスク(マルチタスク呼び出し履歴)、等。
文章が長くなってしまったので、この辺でそういったデバッグ機能のご紹介に入って行こうと思います。
3.リアルタイムOS上プログラム - タスク起動
まずタスクを二つ起動させ、相互にイベントフラグでやり取りをする、簡単なプログラムを書いてみます。
SOLID for Raspberry pi 4では、動的生成可能なカーネル資源数は固定されており、カーネルコンフィグを行えない仕様です。
制限事項についてはこちらに記載されています。
※カーネルコンフィグをしたい場合、有償版を入手すれは可能です。
という事で、acre_tsk を使ってタスクを動的生成します。新規ワークスペースを作成し、main.cppに書いたコードは以下です。
・インクルードファイルや外部変数などの定義
#include <solid_log.h>
#include <itron.h>
#include <kernel.h>
#include <stdlib.h>
#ifdef SOLID_SMP
#include <solid_smp.h>
#define __cpuid() SOLID_SMP_GetCpuId()
#endif
/*
* 優先度の定義
*/
#define HIGH_PRIORITY 4 /* 高優先度 */
#define MID_PRIORITY 9 /* 中優先度 */
#define LOW_PRIORITY 14 /* 低優先度 */
/*
* ターゲットに依存する可能性のある定数の定義
*/
#ifndef STACK_SIZE
#define STACK_SIZE 4096 /* タスクのスタックサイズ */
#endif /* STACK_SIZE */
extern void test_task1(VP_INT exinf);
extern void test_task2(VP_INT exinf);
FLGPTN setptn_wakeup_from_task1 = 0x01;
FLGPTN setptn_stop_from_task1 = 0x02;
FLGPTN setptn_notify_to_task1 = 0x10;
・main関数
extern "C" void slo_main()
{
ER ercd;
static T_CTSK tsk1, tsk2;
ID flag;
T_CFLG cflg;
// 各タスクと同期を行うためのイベントフラグを生成
cflg.flgatr = TA_WMUL | TA_TPRI;
cflg.iflgptn = 0;
ercd = acre_flg(&cflg);
if (ercd < 0)
{
SOLID_LOG_printf("error:acre_flg, err=%d\n", ercd);
while (1)
;
}
flag = ercd;
// タスク動的生成のための情報設定
tsk1.tskatr = TA_NULL;
tsk1.exinf = (ID)flag;
tsk1.task = test_task1;
tsk1.itskpri = MID_PRIORITY;
tsk1.stksz = STACK_SIZE;
tsk1.stk = NULL; // スタックはカーネルが自動的に割り当てる(SOLIDのスタックフェンスが有効)
tsk1.iprcid = 1;
tsk1.affinity = UINT_MAX;
// タスク動的生成のための情報設定
tsk2.tskatr = TA_NULL;
tsk2.exinf = (ID)flag;
tsk2.task = test_task2;
tsk2.itskpri = MID_PRIORITY;
tsk2.stksz = STACK_SIZE;
tsk2.stk = NULL; // スタックはカーネルが自動的に割り当てる(SOLIDのスタックフェンスが有効)
tsk2.iprcid = 2;
tsk2.affinity = UINT_MAX;
ercd = acre_tsk(&tsk1);
SOLID_LOG_printf("tsk1: acre_tsk() returned: %d\n", ercd);
if (ercd > 0)
{
ercd = act_tsk(ercd);
SOLID_LOG_printf("tsk1: act_tsk() returned: %d\n", ercd);
}
else
{
SOLID_LOG_printf("tsk1: faild to create task\n", ercd);
}
ercd = acre_tsk(&tsk2);
SOLID_LOG_printf("tsk2: acre_tsk() returned: %d\n", ercd);
if (ercd > 0)
{
ercd = act_tsk(ercd);
SOLID_LOG_printf("tsk2: act_tsk() returned: %d\n", ercd);
}
else
{
SOLID_LOG_printf("tsk2: faild to create task\n", ercd);
}
return;
}
・task1
void test_task1(VP_INT exinf)
{
FLGPTN ptn;
ER ercd;
ID flag;
SOLID_LOG_printf("[TASK1] Start TASK1\n");
flag = (ID)exinf;
dly_tsk(1000'000); //1sec
while (1)
{
SOLID_LOG_printf("[TASK1] Wakeup TASK2.\n");
ercd = set_flg(flag, setptn_wakeup_from_task1);
SOLID_LOG_printf("[TASK1] set_flg() returned: %d\n", ercd);
// イベントフラグ待ち処理
SOLID_LOG_printf("[TASK1] Waiting reply from TASK2...\n");
ercd = wai_flg(flag, setptn_notify_to_task1, TWF_ORW, &ptn);
SOLID_LOG_printf("[TASK1] wai_flg() returned: %d\n", ercd);
SOLID_LOG_printf("[TASK1] Got reply from TASK2.\n");
ercd = clr_flg(flag, ~setptn_notify_to_task1);
SOLID_LOG_printf("[TASK1] clr_flg() returned: %d\n", ercd);
dly_tsk(1000'000); //1sec
// イベントフラグセット
SOLID_LOG_printf("[TASK1] Stop TASK2.\n");
ercd = set_flg(flag, setptn_stop_from_task1);
SOLID_LOG_printf("[TASK1] set_flg() returned: %d\n", ercd);
// イベントフラグ待ち処理
SOLID_LOG_printf("[TASK1] Waiting reply from TASK2...\n");
ercd = wai_flg(flag, setptn_notify_to_task1, TWF_ORW, &ptn);
SOLID_LOG_printf("[TASK1] wai_flg() returned: %d\n", ercd);
SOLID_LOG_printf("[TASK1] Got reply from TASK2.\n");
ercd = clr_flg(flag, ~setptn_notify_to_task1);
SOLID_LOG_printf("[TASK1] clr_flg() returned: %d\n", ercd);
dly_tsk(1000'000); //1sec
}
return;
}
・task2
void test_task2(VP_INT exinf)
{
FLGPTN ptn;
ER ercd;
ID flag;
SOLID_LOG_printf("[TASK2] Start TASK2\n");
flag = (ID)exinf;
while (1)
{
// イベントフラグ待ち処理
SOLID_LOG_printf("[TASK2] Waiting reply from TASK1...\n");
ercd = wai_flg(flag, setptn_wakeup_from_task1, TWF_ORW, &ptn);
SOLID_LOG_printf("[TASK2] wai_flg() returned: %d\n", ercd);
SOLID_LOG_printf("[TASK2] Got reply from TASK1.\n");
ercd = clr_flg(flag, ~setptn_wakeup_from_task1);
SOLID_LOG_printf("[TASK2] clr_flg() returned: %d\n", ercd);
// イベントフラグセット
SOLID_LOG_printf("[TASK2] Reply to TASK1.\n");
ercd = set_flg(flag, setptn_notify_to_task1);
SOLID_LOG_printf("[TASK2] set_flg() returned: %d\n", ercd);
// イベントフラグ待ち処理
SOLID_LOG_printf("[TASK2] Waiting reply from TASK1...\n");
ercd = wai_flg(flag, setptn_stop_from_task1, TWF_ORW, &ptn);
SOLID_LOG_printf("[TASK2] wai_flg() returned: %d\n", ercd);
SOLID_LOG_printf("[TASK2] Got reply from TASK1.\n");
ercd = clr_flg(flag, ~setptn_stop_from_task1);
SOLID_LOG_printf("[TASK2] clr_flg() returned: %d\n", ercd);
// イベントフラグセット
SOLID_LOG_printf("[TASK2] Reply to TASK1.\n");
ercd = set_flg(flag, setptn_notify_to_task1);
SOLID_LOG_printf("[TASK2] set_flg() returned: %d\n", ercd);
}
return;
}
このプログラムは、以下の動作をします。
※タスク生成・操作やイベントフラグ生成・操作のパラメータについては、uITRONの仕様書を参照してください。
実行すると、UART経由で以下のログが表示されます。
機会があれば、リアルタイムOSがどういう風に動作しているのか、についても解説しようと思います。
3.リアルタイムOS上プログラム - デバッグ機能
ここで、リアルタイムOSで動作するプログラムを開発する際に便利なデバッグ機能を見てみましょう。
タスク状態表示
イベントフラグ表示
を見ていきます。
3.1 RTOS
[デバッグ]⇒[ウインドウ]⇒[RTOSビューア] を選択します。
※RTOSビューアは、OSからたくさんの情報を取得するため、ビューアが開くまで少し待ちます。
以下のウインドウが開きます。
ここに、リアルタイムOSに特化したデバッグ機能が集まっています。
3.2 タスク状態表示
RTOSビューアで各タスクの状態を表示できます。
上記タスクの動作表の中の、⑥実行前でブレークポイントを貼った場合、以下のように表示されます。
test_tsk1 がRunning, test_tsk2 がWaiting(FLG)と表示されており、動作表のとおりです。
3.3 イベントフラグ状態表示
次に、イベントフラグ状態を見てみます。
⑥実行前でブレークポイントを貼った場合、以下のように表示されます。
setptn_notify_to_task1( 0x10 ) が設定されたまま、クリアする前であることが示されています。
その他の機能は、RTOSウインドウ左側にリストされていますので、ご興味ある方ぜひ見てみてくださいね。
4.リアルタイムOSはLinuxに比べどれくらいリアルタイムか
リアルタイムOSはLinuxに比べどれくらいリアルタイムか。
一度は気になったことがあるはず、です。
あるいは実測された方もいらっしゃるのでは?
1m秒間隔でGPIO変化させるプログラムを実行させた場合、LinuxとリアルタイムOSで実際どう違いが出るのか。
測定結果をご紹介します。
※1m秒なので目視はできません。オシロスコープでの確認です。
本実験、Raspberry pi 4Bでの測定ではありません。
ボード名はシリコンリナックス株式会社製の「CAT845」です。
Cortex-A7 1.0GHz Dual Coreを搭載しています。
[Linux]
周期タイマーを 1m秒周期で起動しハンドラ内で、pthread_cond_signal を実行
pthread_cond_wait で待っていたスレッドにて GPIOを反転後、nanosleep() で500μ秒スリープ
起床後 GPIOを再反転し、pthread_cond_wait 実行
GPIOの変化を計測した結果はこちら。
※本測定においては、Linux上のアプリという位置づけで書いたプログラムで1m秒周期測定しました。
[リアルタイムOS]
周期起動ハンドラを 1m秒周期で起動しタスクを起床させる
起床したタスクにて GPIOを反転後、500μ秒のアラームをセットして再度 slp_tsk で就寝
アラームハンドラでは、GPIOを再反転
GPIOの変化を計測した結果はこちら。
Linuxの場合にはずいぶん揺らぎがあるのに対し、リアルタイムOSの場合では全く揺らがず1m秒でGPIOが制御されていることがわかります。
Linuxの場合、もっとハードウェアに近いレイヤで同じことを実現した場合、よりリアルタイムに動作させることができるはずです。
例えば本プログラムをドライバとして開発する、等。
ただ、なかなか手間がかかるので、よっぽどでない限りはそういうことをしないのではないかと思います。
そうはいっても、豊富なミドルウェアがあり資源豊かなLinuxも魅力的です。
近年はCPUのマルチコア化が進んでおり、せっかく複数CPUコアが搭載されているので、その中の1つや2つにリアルタイムOSを割り当てて、マルチOSで動作させれば、LinuxとリアルタイムOSの良い所取りができて便利じゃないかなと思います。
今回はここまでです。
次回はいよいよRustでプログラムを作っていきます。