GR-ROSEで FreeRTOS vs Azure RTOS

この記事は がじぇるねGR Advent Calendar 2021 の14日目です。

GR-ROSEでは、FreeRTOSとAzure RTOSという2つのRTOSをお手軽に使うことができます。
この記事ではGR-ROSEでこの2つのRTOSを比較します。

FreeRTOSとAzure RTOSについて

FreeRTOSはオープンソースRTOSで、2017年にAmazonに買収され現在はMITライセンスで公開されています。(買収以前はGPLライセンスでした。)

いっぽう、Azure RTOSは元の名をThreadXといい、2019年にMicrosoftに買収されました。ソースコードGitHubで公開されてはいますが、プロプライエタリなライセンスになっています。Microsoftは特定のマイコンメーカーとOEMライセンス契約を結んでおり、ライセンスされたマイコンでは無償でAzure RTOSを利用できます。逆に言えば、ライセンスされていないマイコンにAzure RTOSを移植して勝手に再配布したり本番利用したりはできません。別途ライセンス料金を支払う必要があります。

f:id:licheng:20211204111701j:plain:w400

この2つのRTOSのどちらが良いかというのは一概に言えませんが、IoTに関して言えば答えは簡単です。クラウド側がAWSならFreeRTOS、AzureならAzure RTOSでしょう。そもそもAmazonMicrosoftも自社のIoTソリューションのためにこれらのRTOSを買収したわけですから、とうぜん自社のIoTクラウドサービスとの連携をサポートしています。

f:id:licheng:20211204102653j:plain:w500

ベンダ クラウド RTOS
Amazon AWS FreeRTOS
Microsoft Azure Azure RTOS

この記事ではIoTサポートの話は横に置いて、RTOSの基本的な部分について2つのRTOSを比較していきます。

GR-ROSEでFreeRTOSとAzure RTOS

GR-ROSEの開発環境はWebコンパイラ、e2studio、IDE for GRの3種があり、どれもFreeRTOSベースのArduino互換SDKが基本です。Arduino互換のAPIが使えるので、FreeRTOSをまったく意識しなくともプログラムできますが、FreeRTOSのAPIを使用すればマルチタスクなプログラミングも可能です。

#include <Arduino.h>
#include <FreeRTOS.h> // FreeRTOSのAPIを使う場合はインクルード
#include <task.h>     // タスク関係
#include <semphr.h>   // セマフォ、ミューテックス関係
#include <queue.h>    // キュー関係

いっぽう、Azure RTOSに関してはWebコンパイラとe2studioでAzure IoT Central用のサンプルプロジェクトが提供されています。こちらもArduino互換SDKですが、Azure RTOSAPIを使用すればマルチタスクなプログラミングも可能です。IDE for GRはサポートしてません。

#include <Arduino.h>
#include <tx_api.h> // Azure RTOSのAPIを使う場合はインクルード

マルチタスク

まずはRTOSの根幹であるマルチタスクから。(FreeRTOSではタスク、Azure RTOSではスレッドという用語を使いますが、方言の差くらいに捉えておきます。)

FreeRTOSでは次のように書きます。タスク関数へ渡す引数やハンドルの取得は不要ならNULLを渡します。

結果 = xTaskCreate(
    タスク関数, タスクの名前, スタックサイズ,
    タスク関数へ渡す引数のポインタ, 優先度, ハンドルを返すポインタ );

Azure RTOSでは次のように書きます。スタックに用いるメモリは予め確保した領域を渡します。プリエンプション閾値とタイムスライス値については後述します。自動スタートは、スレッドをすぐに開始するならTX_AUTO_STARTを、保留状態にするならTX_DONT_STARTを渡します。

結果 = tx_thread_create(
    ハンドルを返すポインタ, スレッドの名前, スレッド関数,
    スレッド関数へ渡す引数, スタック先頭へのポインタ, スタックサイズ,
    優先度, プリエンプション閾値, タイムスライス値, 自動スタート);

優先度の数字に注意してください。FreeRTOSでは数字の大きいほうが優先度が高くなるのに対し、Azure RTOSでは数字の小さいほうが優先度が高くなります。

GR-ROSEのメインタスク( setup()とloop() )は、FreeRTOS環境では優先度3、スタック512バイトに設定されています。(FreeRTOS/src/amazon_freertos_common/freertos_start.c)
いっぽう、Azure RTOS環境では優先度4、スタック4キロバイトに設定されています。(src/main.c)

排他制御

マルチタスクで並列処理をすれば、たいていの場合はミューテックスなどを使った排他制御が必要になります。

FreeRTOSでは次のように書きます。最大待ち時間はtick数で指定します。後述しますが、GR-ROSEでは tick = 1msec となっています。portMAX_DELAYを指定すると永久に待ちます。

SemaphoreHandle_t mutex;
mutex = xSemaphoreCreateMutex();
結果 = xSemaphoreTake(mutex, 最大待ち時間); // ミューテックスを獲得する (ロックする)
結果 = xSemaphoreGive(mutex); // ミューテックスを返却する (アンロックする)

Azure RTOSでは次のように書きます。優先度継承については後述しますが、 TX_INHERITを指定すると優先度継承が有効となり、TX_NO_INHERITを指定すると無効になります。待ちオプションは、TX_NO_WAITを指定すると待たずに即座に戻り、TX_WAIT_FOREVER を指定すると永久に待ち、それ以外の値の場合は指定したtick数まで待ちます。GR-ROSEでは tick = 10msec となっています。

TX_MUTEX mutex;
結果 = tx_mutex_create(&mutex, ミューテックスの名前, 優先度継承);
結果 = tx_mutex_get(&mutex, 待ちオプション); // ミューテックスを獲得する (ロックする)
結果 = tx_mutex_put(&mutex); // ミューテックスを返却する (アンロックする)

優先度継承(Priority Inheritance)とは、スレッド間で待ち合いをする場合にそれらのスレッドの中の最高の優先度でクリティカルセクションを実行するというものです。優先度の高いスレッドが優先度の低いスレッドに待たされて遅れるケース(優先度の逆転)を緩和する狙いがあります。

FreeRTOSではミューテックスは必ず優先度継承になります。タスク間の同期など優先度継承をすべきでない場合にはミューテックスではなくバイナリーセマフォを用います。

タスク間通信

マルチタスクでもう一つよく使うのが、キューを使ったタスク間のメッセージのやりとりです。

FreeRTOSでは次のように書きます。最大待ち時間は前述のミューテックスの場合と同様です。

QueueHandle_t queue;
queue= xQueueCreate(キューの長さ, 要素のサイズ);
結果= xQueueSend(queue, 要素へのポインタ, 最大待ち時間); // キューの末尾に要素を送る
結果 = xQueueReceive(queue,  要素を返すポインタ, 最大待ち時間); // キューから要素を受け取る

Azure RTOSでは次のように書きます。要素のサイズは32ビットワード単位であることに注意してください。指定できる値は1~16です。キューに用いるメモリは予め確保した領域を渡します。キューのサイズはトータルのバイト数を指定します。待ちオプションは前述のミューテックスの場合と同様です。

TX_QUEUE queue;
結果 = tx_queue_create(&queue, キューの名前, 要素のサイズ, キューの先頭へのポインタ, キューのサイズ);
結果 = tx_queue_send(&queue, 要素へのポインタ, 待ちオプション); // キューの末尾に要素を送る
結果 = tx_queue_receive(&queue, 要素を返すポインタ, 待ちオプション); // キューから要素を受け取る

プリエンプション

プリエンプションとは、現在実行中のタスクより優先度の高いタスクが実行可能である場合にOSが強制的にタスクスイッチすることです。プリエンプションが無いと、タスクが自発的に待ち状態に入らないかぎりタスクスイッチは起こりません。これを協調的マルチタスクと言います。

【FreeRTOSの場合】
FreeRTOSはプリエンプティブマルチタスクにも協調的マルチタスクにも設定できます。GR-ROSEのFreeRTOSはプリエンプティブマルチタスク、同一優先度タスクのタスクスイッチ(タイムスライス)あり、システム割り込み周期(tick)は1msecに設定されています。

#define configUSE_PREEMPTION       1      // プリエンプション有効
#define configUSE_TIME_SLICING     1      // 同一優先度タスクのタイムスライスあり
#define configTICK_RATE_HZ         (1000) // tick = 1msec
// configUSE_PREEMPTIONが0のときはconfigUSE_TIME_SLICINGは無視されます。

【Azure RTOSの場合】
Azure RTOSは基本的にプリエンプティブマルチタスクですが、スレッドごとに生成時にtx_thread_createの引数で優先度の他にプリエンプション閾値、タイムスライス値が設定できます。また TX_DISABLE_PREEMPTION_THRESHOLD を定義することでプリエンプションを無効にも設定できるようです。

プリエンプション閾値より優先度の高いスレッドのみがプリエンプションの対象になります。したがって、プリエンプション閾値は優先度以下の値を指定します。プリエンプション閾値に0を設定するとプリエンプションはおこなわれません。

タイムスライス値は、同じ優先度の実行可能状態のスレッドがあるときにタスクスイッチをおこなう時間をtick数で指定します。優先度とプリエンプション閾値に同じ値を設定したときのみ有効です。タイムスライス値にTX_NO_TIME_SLICE (=0)を設定するとタイムスライスが無効になります。つまり同じ優先度のスレッド間ではプリエンプションがおこなわれなくなります。

GR-ROSEのAzure RTOSでは、メインスレッドは優先度=プリエンプション閾値、タイムスライス値はTX_NO_TIME_SLICEに設定されています。

※ 現状、GR-ROSEのAzure RTOSではプリエンプションがうまく機能していないようです。この件はRulzにて問い合わせ中です。

APIリファレンス