FreeRTOSにおけるタスクの遷移について

RX66N + FreeRTOSの環境で開発をしております。

FreeRTOSがらみの質問なので場違いかもしれませんが、アドバイスいただけたらお願いします。

まず、OSの設定としては

・プリエンプティブスケジュール(configUSE_PREEMPTION=1)

・タイムスライスOFF(configUSE_TIME_SLICING=0)

で使用しています。

同じ優先度のタスクAとタスクBがあり、タスクAがRunning、タスクBがBlocked状態とします。

この時タスクAがタスクBのBlocked状態を解除するシステムコールを呼んだとします。

タスクの状態としてはAがRunning、BがReadyになると思いますが、

タスクAがBlockedに入る前にタスクBに遷移するのは仕様ですか?

また、上記のように同じ優先度の2つのタスクがRunningとReadyステータスだった場合、

割込みやより優先度の高いタスクの実行により切り替わることはありますか?

同じ優先度でタイムスライスがオフであれば、実行中のタスクがBlockedに入るまでもう一方のタスクには

切り替わらないつもりでいたので戸惑っております。

(以前使用していたitron準拠のOSではそのような動きはしなかった認識です)

よろしくお願い致します。

■環境

・RX66N

・RX ファミリ Renesas FreeRTOS

・e2studio

・CC-RX v3

  • Joさん、こんにちは。NoMaYと申します。

    ここまで詳細な動作の問い合わせは、本家FreeRTOSのフォーラムに問い合わせた方が良さそうに思います。(それが一番近道だと思うのです。) ただ、以下のウェブページを読んでみた印象では、「そのような場合には、実行中のタスクがブロック状態になるまで、同一優先度の他のタスクに切り替わることは無い」と明言されていないことから推測すると、仕様未定義なのではないでしょうか。(仕様というのを、意図してそういう動作にした、みたいな感じで捉えてのことですけれども。)

    素朴には、同一優先度ですから、明言されていなければ、単に動くように動く、みたいなところではないかなぁ、と思ってしまうのです。記憶では、μITRONにはタイムスライス機能は無かったと思う一方で、FreeRTOSはタイムスライス機能ありで使うのがデフォルトだったと思うのですけれども、そのあたりから、このような場合の「単に動くように動く」という観点での挙動の違いも現れて来そうに思うのです。

    タイムスライス機能無し → ブロック状態に遷移してレディキューの先頭から外されるまで実行され続けるのが自然な挙動な気がしますね(正しい言葉使いでは無いかも知れませんけれども)

    タイムスライス機能あり → もともと同一優先度のタスクはラウンドロビンで切り替わっていくのだからブロック状態になるまで実行され続けることは自然では無い挙動な気がしますね

    www.freertos.org/RTOS-task-states.html
    www.freertos.org/RTOS-task-priority.html
    www.freertos.org/single-core-amp-smp-rtos-scheduling.html
     

  • Joさん、こんにちは。NoMaYです。

    > もともと同一優先度のタスクはラウンドロビンで切り替わっていく

    一晩明けて思ったのですけれども、「そうだとしても、タイムスライス切り替え以外の要因で、同一優先度の他のタスクに切り替わってしまうとしたら、それはそれで仕様がザルなのではないですか?」と突っ込まれたとしますと、確かにそれもそうかも知れません、とも思ったのが今朝のことでした。

  • Joさん、NoMaYさん

    こんにちは、シェルティです。ルネサスの中の人です。

    ご報告いただき感謝いたします。

    ただ何か本件変ですね。ちょっと連休中に実験して確かめてみます。例示いただいたtaskAとtaskBを生成し、Tracealyzerというツールを使って内部状態をモニタしてみようかと思います。そのあと必要に応じてAWS本社の技術メンバに確認してみようかと思います。

    JoさんやNoMaYさんと同じくタイムスライスOFFなのにタスクが切り替わるのは変だとシェルティは思っています。

    以上です

  • シェルティさん、こんにちは。NoMaYです。

    実験する時、以下の点がミソ、なのではないかなぁ、と思ったのでした。

    > この時タスクAがタスクBのBlocked状態を解除するシステムコールを呼んだとします。

    あと、どのシステムコールで解除したかも影響するかも知れません。RTOSというと、プライオリティインバージョン(だったでしょうかね、、、)とか、本件とは直接関係無いかも知れませんけれども、特殊な事例があることですので。

  • NoMaYさん、シェルティさん

    こんにちは、早速のご連絡ありがとうございます。

    たしかにドキュメントに明言はされてないので、そういうものだと言われたらあきらめるしかないのですが、これまでμITRONで慣れていたので違和感がありました。

    シェルティさん

    実験までしていただけるということで、ありがとうございます。なお、使用したシステムコールは「xEventGroupSetBits」です。タスクBが「xEventGroupWaitBits」で待っている状態でした。

    こちらからも進展ありましたらご報告したいと思います。

  • こんにちは。NoMaYです。

    私の書いた以下の文面は、意図が伝わらない文面だったかも、という気がしてきました。

    > タイムスライス機能無し → ブロック状態に遷移してレディキューの先頭から外されるまで実行され続けるのが自然な挙動な気がしますね(正しい言葉使いでは無いかも知れませんけれども)

    > タイムスライス機能あり → もともと同一優先度のタスクはラウンドロビンで切り替わっていくのだからブロック状態になるまで実行され続けることは自然では無い挙動な気がしますね

    上側はμITRONのことを指していて、そもそもOSにタイムスライス機能が無いのだから、このようにしか動きようが無い、と思いました、という感じのことを意図していました。

    下側はFreeRTOSのことを指していて、タイムスライス機能があって、かつ、それを使うのがデフォルトであるようなOSなのであり、まずそのデフォルトのことを考えると、一旦選択されたタスクがブロック状態になるまで実行され続ける、という仕様は根本的に無いわけで(そういう動作ではラウンドロビンで切り替わることにならないですので)、にも関わらず、そのような仕様がコンフィグレーションでタイムスライス機能を無効にした途端に発現する、ということは考え難いよなぁ、と思いました、という感じのことを意図していました。

    ただ、今朝思ったのは、そのデフォルトの場合で、タイムスライス切り替え以外の要因で、同一優先度の他のタスクに切り替わってしまうことが無いような(ちゃんとした?(というのは適切な言い回しでは無いのかも知れませんけれども))作りであったなら、結果的に、μITRONの場合と同じ動作になる筈かなぁ、とも思ったのでした。

    逆に言うと、FreeRTOSでは、そのデフォルトの場合で、タイムスライス切り替え以外の要因で、同一優先度の他のタスクにタイムスライスの途中であってもポンポンと切り替わってしまっているのだろうなぁ、という気がしたのでした。それでも、そのうちラウンドロビンで、またタイムスライスの順番が回って来ますので、さほど差し障りは無さそう、と思いました。

    #すみません、知ったかぶりをしているだけ、だったとしたらちょっと恥ずかしいですけれども、、、

  • JoさんNoMaYさんシェルティさんこんにちは。
    ふぐりんです。


    私も実験してみました。
    ちょっと長くなりますが、せっかく実験したんでご報告します。
    (じつは失敗談とか書きたいことがいっぱいあるんですが、時間がw)


    【結果】
    うーん、Joさんのおっしゃる現象「タスクAがBlockedに入る前にタスクBに遷移する」は再現しませんでした。
    「タスクAがBlockedに入ったらタスクBに遷移する」でした。

    【材料】
    機材:Target Board for RX65N
    環境:CS+/CCRX v3.05.00
    ソフト:
    kern-gt(sh-goto)さんのFreeRTOSデモを利用させていただきました。(楽ちんでした)
    FreeRTOS Ver10.1.1 DemoProject for Renesas RX65N (CS+,CC-RX)
    https://github.com/kern-gt/Demo_FreeRTOS_v10.1.1_for_Renesas_RX65N_with_CSplus_CC-RX
    あとmkogax(私)の GG for CCRX(EMU版) も追加して
    https://github.com/mkogax/GG_for_CCRX
    テストプログラムを組んで実験しました。

    【実験】
    TaskG:無限ループ内で1msecディレイしながらコンソールチェック。コンソールからの指示でTaskGからイベントフラグセット。
    TaskA:無限ループ内でイベント待ち。イベント待ちタイムアウトは1msec。
    *カーネルtickは1000Hz(1msec周期)、タスク優先度はどちらも1(同じ)。

    ソース抜粋:

    FreeRTOS_test_source.txt
    void vTaskG(void *pvParameters)     // TaskG
    {
        while(1) {
            gg_con_Check();     // console processing (returns immediately if nothing is done)
            GG_TP_OFF(100);     // オシロのCH1をOFF
            vTaskDelay(1/portTICK_PERIOD_MS);
        }
    }
    int C_test(int argc, char **argv)   // testコマンド実行部(コンソールから't'改行で実行)
    {
        GG_TP_ON(100);          // オシロのCH1をON
        xEventGroupSetBits(xCreatedEventGroup, 1);      // イベントフラグ(b0)をON
        GG_TP_OFF(100);         // オシロのCH1をOFF
        GG_TP_ON(100);          // オシロのCH1をON
        return 0;
    }
    void vTaskA(void *pvParameters)     // TaskA
    {
        while(1) {
            // イベント待ち(自動クリア,TMO=1msec)
            xEventGroupWaitBits(xCreatedEventGroup, 1, 1, 1, 1/portTICK_PERIOD_MS);
            GG_TP_ON(903);      // オシロのCH2をON
            dummy(500);         // ダミー負荷(20usec弱)
            GG_TP_OFF(903);     // オシロのCH2をOFF
        }
    }
    

    結果:
    TaskGでイベントセットしたあとBlockedになったら(vTaskDelay()したら)、TaskAはRunningになる(xEventGroupWaitBits()から抜ける)。


    TaskA(CH2=紫)は1msec周期でタイムアウトしたところ(カーネルtick)でONしますが、イベント発生すると1msec(カーネルtick)以外のところでONします。
    (TaskGのdelay抜けとTaskAのタイムアウトのどちらが先かは・・審議中)


    拡大

    ちなみに以下の条件で同じ結果でした。
    (1)configUSE_PREEMPTION=0,configUSE_TIME_SLICING=0
    (2)configUSE_PREEMPTION=1,configUSE_TIME_SLICING=0
    (3)configUSE_PREEMPTION=0,configUSE_TIME_SLICING=1
    (4)configUSE_PREEMPTION=1,configUSE_TIME_SLICING=1

    【実験2】

    タイムスライスはtick単位でしか観測してないと思うんで、あまり関係ないかも・・
    というのが私の感想です。
    でその確認でTaskGのdelay待ち無しの実験もしてみました。

    下記3条件では、TaskAはピクリとも動きませんでいた。
    (1)configUSE_PREEMPTION=0,configUSE_TIME_SLICING=0
    (2)configUSE_PREEMPTION=1,configUSE_TIME_SLICING=0
    (3)configUSE_PREEMPTION=0,configUSE_TIME_SLICING=1

    下記条件の時だけTaskAが動きました。
    (4)configUSE_PREEMPTION=1,configUSE_TIME_SLICING=1


    TaskAは1msec周期で動くだけに見えます。(イベントセットと無関係?)

    そこでTaskAのタイムアウトを3msecに変えたら・・

    TaskAは3msec周期で動いてますが、
    TaskGにイベントセットされると、3msec周期と無関係に直後のtickでTaskAに切り替わってます。

    うーん、なんかきちんと説明付けられる気がするけどもうおなかいっぱい・・

  • ふぐりんさん、こんにちは。

    お忙しいなか実験していただきありがとうございます。こちらでもこのような単純ケースで確認してみたいと思いながらなかなか時間がとれずできていませんでした。
    質問だけして何も進められておらず、皆さまには申し訳ありません。

    再現しませんでしたか。
    そうなるとより優先度の高いタスクでOSのシステムコールが使われることが影響しているのかもしれません。

    OSの設定関連だと、configUSE_PREEMPTION、configUSE_TIME_SLICING以外に関係しそうなものはないですよね。

  • Joさんこんにちは!
    だいたい今の季節はヒマなのでバッチこいです。
    とはいえ私も普段はNORTiとかTOPPERSとか使ってるんで、NoMaYさんシェルティさんみたいなFreeRTOSの専門家じゃないです。すいませんw

    うーん、他のタスクとか割込みが関係するかもですか・・ないともいえないですかねえ。
    昨晩からFreeRTOSの中を(わからないなりに)ちょっとだけのぞいてみて、Yieldというのを発行する条件判断がそのへんを左右しそうなんですが、Yield発行(予約)しても、その実行処理(ソフト割込み)が入るまでに他の割り込みによってさらに状態が変化する可能性はあるわけで・・「ほんとにこれどうなるんだ?」ってのが今のところの感想です。読込みが浅いからとは思いますが・・

    ちなみに chatGPT で 「RTOSでいうところのyieldってなんですか?」って質問すると結構詳しく教えてくれました。(ほんとかどうかはわかんないけど)

  • ふぐりんです。
    Joさん皆さんこんにちは。

    うーん、私も混同してたかもですが、タイムスライス機能とラウンドロビンは別と考える方が良いかもしれません。
    とりあえず以下の仮説を立てて実験してみたところ、どうもそうなってるっぽいです。
    Joさんの事例で参考になればいいのですが。

    【仮説】
    ・タイムスライスが有効だと、カーネルtickのタイミングでラウンドロビンすべきと判断したらYield発行する
    ・タイムスライスが無効だと上記理由でのYield発行はしない
    ・Yield発行=ソフト割込み(最低レベル割込み)の割り込みフラグが立つこと
    ・上記ソフト割込み処理(Yield処理)の目的は「ほかのタスクに処理を譲ること」である(chatGPTの回答w)
    ・Yield処理では実行中タスクと同レベルのタスクがあったらラウンドロビンする

    つまりタイムスライスが無効でも、何らかの理由でYield処理が動いたらラウンドロビンが発生するんじゃないかと思いました。

    「ほかのタスクに処理を譲る」必要が無いときはYield発行しないようにできてる。
    だからタイムスライスが無効だと、tickごとのラウンドロビン目的のYield発行はされないわけです。
    しかし上位レベルのタスク起動などなんらかの原因でYield発行はなされるわけで、例えば「上位レベルタスクを一回レジュームしたけどその割込み中でやっぱりサスペンドしなおした」なんてのが割込み処理の中に書いてあったりすると、Yield発行はするけど実質上位レベルタスクはピクリともしない、けどラウンドロビンだけ回っちゃった・・というのがありえるんじゃないかという仮説を立てて実験してみました。


    【実験】
    TaskG:優先度=1、無限ループ内で1msecディレイしながらコンソールチェック。
    TaskA:優先度=1、無限ループ内でイベント待ち。イベント待ちタイムアウトは1000msec。
    TaskX:優先度=2、無限ループ内でサスペンドしてるだけ。

    テストコマンド:
    t イベントセットのみ
    x イベントセット+割禁で上位タスク(TaskX)瞬間レジューム
    y イベントセット+Yield発行
    z イベントセット+上位タスク(TaskX)レジューム

    コンフィグレーション:
    configUSE_PREEMPTION=1,configUSE_TIME_SLICING=0

    ソース抜粋:

    FreeRTOS_test_source_20230510.txt
    ****************************
    
    	タスク
    
    	【生成部】
    	xTaskCreate(vTaskG,"TaskG",100,NULL,1,&xHandleG);
    	xTaskCreate(vTaskA,"TaskA",100,NULL,1,&xHandleA);    // タスクA 優先度=1 (MAX=6)
    	xTaskCreate(vTaskX,"TaskX",100,NULL,2,&xHandleX);    // タスクX 優先度=2 (MAX=6)
    
    ****************************
    
    void vTaskG(void *pvParameters)     // TaskG(優先度=1)
    {
        while(1) {
            gg_con_Check();     // console processing (returns immediately if nothing is done)
            GG_TP_OFF(100);     // オシロのCH1をOFF
            vTaskDelay(1/portTICK_PERIOD_MS);
        }
    }
    void vTaskA(void *pvParameters)		// TaskA(優先度=1)
    {
    	while(1) {
    		// イベント待ち(自動クリア)
    		xEventGroupWaitBits(xCreatedEventGroup, 1, 1, 1, 1000/portTICK_PERIOD_MS);
            GG_TP_ON(903);      // オシロのCH2をON
            dummy(500);         // ダミー負荷(20usec弱)
            GG_TP_OFF(903);     // オシロのCH2をOFF
    	}
    }
    void vTaskX(void *pvParameters)		// TaskX(優先度=2)
    {
    	while(1) {
            GG_TP_ON(904);  	// 
    		dummy(500);
            GG_TP_OFF(904);
    		vTaskSuspend(NULL);		// 自タスク(TaskX)をサスペンド
    	}
    }
    
    
    ****************************
    
    	コンソールコマンド
    
    	【登録部】
        GG_CON_CMDADD(C_t, "t", "", "ラウンドロビン実験 t イベントセットのみ" );
        GG_CON_CMDADD(C_x, "x", "", "ラウンドロビン実験 x イベントセット+割禁で上位タスク瞬間レジューム" );
        GG_CON_CMDADD(C_y, "y", "", "ラウンドロビン実験 y イベントセット+Yield発行" );
        GG_CON_CMDADD(C_z, "z", "", "ラウンドロビン実験 z イベントセット+上位タスクレジューム" );
    
    ****************************
    
    int C_t(int argc, char **argv)  // tコマンド実行部	イベントセット
    {
    	gg_printf("ラウンドロビン実験 t イベントセットのみ\n");
    
    	// イベントセット
        GG_TP_ON(100);  		//
    	xEventGroupSetBits(xCreatedEventGroup, 1);		// イベントフラグ(b0)をON
    	GG_TP_OFF(100);			// オシロCH1区切り
    
        GG_TP_ON(100);  		//
      	return 0;
    }
    int C_x(int argc, char **argv)  // xコマンド実行部	イベントセット+割禁で上位タスク瞬間レジューム
    {
    	gg_printf("ラウンドロビン実験 x イベントセット+割禁で上位タスク瞬間レジューム\n");
    
    	// イベントセット
        GG_TP_ON(100);  		//
    	xEventGroupSetBits(xCreatedEventGroup, 1);		// イベントフラグ(b0)をON
    	GG_TP_OFF(100);			// オシロCH1区切り
    
    	// 割込み禁止でTaskXをレジューム&サスペンド(状態を変えずにYield発行してみる)	2023.05.10 M.Kogan
    	GG_DI_BEGIN();			// 割込み禁止
        GG_TP_ON(100);  		//
    	vTaskResume(xHandleX);	// TaskXをレジューム
    	vTaskSuspend(xHandleX);	// TaskXを再サスペンド
    	GG_TP_OFF(100);			// オシロCH1区切り
    	GG_DI_END();			// 割込み禁止解除
    
        GG_TP_ON(100);  		//
      	return 0;
    }
    int C_y(int argc, char **argv)  // yコマンド実行部	イベントセット+Yield発行
    {
    	gg_printf("ラウンドロビン実験 y イベントセット+Yield発行\n");
    
    	// イベントセット
        GG_TP_ON(100);  		//
    	xEventGroupSetBits(xCreatedEventGroup, 1);		// イベントフラグ(b0)をON
    	GG_TP_OFF(100);			// オシロCH1区切り
    
    	// Yield発行
        GG_TP_ON(100);  		//
    	vPortYield();			// Yield発行
    	GG_TP_OFF(100);			// オシロCH1区切り
    
        GG_TP_ON(100);  		//
      	return 0;
    }
    int C_z(int argc, char **argv)  // xコマンド実行部	イベントセット+上位タスクレジューム
    {
    	gg_printf("ラウンドロビン実験 z イベントセット+上位タスクレジューム\n");
    
    	// イベントセット
        GG_TP_ON(100);  		//
    	xEventGroupSetBits(xCreatedEventGroup, 1);		// イベントフラグ(b0)をON
    	GG_TP_OFF(100);			// オシロCH1区切り
    
    	// TaskXをレジューム
        GG_TP_ON(100);  		//
    	vTaskResume(xHandleX);	// TaskXをレジューム
    	GG_TP_OFF(100);			// オシロCH1区切り
    
        GG_TP_ON(100);  		//
      	return 0;
    }
    


    【結果】

    コンソールログ:

    console_log_20230510.txt
    ** GG for CCRX **
    >configUSE_PREEMPTION = 1
    configUSE_TIME_SLICING = 0
    tp1=100, tp2=903
    
    >
    >help
            help [cmd..]                  : command help
    -- memory command --
              md [addr [alen]]            : mem dump
              ms addr data..              : mem set
              mf addr alen data           : mem fill
    -- Example of original command registration --
               t                          : ラウンドロビン実験 t イベントセットのみ
               x                          : ラウンドロビン実験 x イベントセット+割禁で上位タスク瞬間レジューム
               y                          : ラウンドロビン実験 y イベントセット+Yield発行
               z                          : ラウンドロビン実験 z イベントセット+上位タスクレジューム
    -- TP command --
              tp [#1 [#2]]                : TP(test point) select
    >
    >t
    ラウンドロビン実験 t イベントセットのみ
    >x
    ラウンドロビン実験 x イベントセット+割禁で上位タスク瞬間レジューム
    >y
    ラウンドロビン実験 y イベントセット+Yield発行
    >z
    ラウンドロビン実験 z イベントセット+上位タスクレジューム
    >

    オシロ  t イベントセットのみ
    TaskGでイベントセットしたあとBlockedになったら(vTaskDelay()したら)、TaskAはRunningになる(xEventGroupWaitBits()から抜ける)。

    オシロ  x イベントセット+割禁で上位タスク(TaskX)瞬間レジューム
    TaskGでイベントセットしたあとTaskXを瞬間レジュームしただけで、TaskAはRunningになる(xEventGroupWaitBits()から抜ける)。
    このときTaskXのGG_TP_ON(904)も観測しましたがピクリともしませんでした。(TaskXはレジュームしなかった)

    オシロ  y イベントセット+Yield発行
    TaskGでイベントセットしたあとYield発行したら、TaskAはRunningになる(xEventGroupWaitBits()から抜ける)。

    オシロ  z イベントセット+上位タスク(TaskX)レジューム
    TaskGでイベントセットしたあとTaskXをレジュームしたら、TaskAはRunningになる(xEventGroupWaitBits()から抜ける)。
    このときTaskXのGG_TP_ON(904)を観測してTaskXのレジュームを確認しました。