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

Parents
  • ふぐりんです。
    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のレジュームを確認しました。

Reply
  • ふぐりんです。
    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のレジュームを確認しました。

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

    またしてもいろいろと実験していただきありがとうございます。

    つまりタイムスライスがOFFの場合、単純に2つの同一優先度のタスクだけが動いている限りはBlockedになるまでもう一方のタスクに切り替わることはないが、他のいろいろが動いている中で「Yield」されるとそれをきっかけにタスクが遷移されることがある。

    というわけですね。

    確かにこれまでの結果を踏まえるとそういう仕様なら辻褄が合いそうな気がしますね。

  • Joさんこんにちは!

    参考にしていただけるとうれしいです。(今回の問題の原因かどうかは置いといてw)
    私も良い勉強になりました。


    ただですねえ・・オシロの画像よく見るとTaskGの処理が終わるまでの時間が全て160usecぐらいなのに気づきまして。
    「あれー?実験によってはTaskAやTaskXの実行時間(dummy(500)=20usec弱)は後ろにずれるはずなのに」
    て思って確認してました。(よっぽど暇なんだと思われたくないけど、気になったら仕方ないですよねw)

    で、わかりました。
    テストコマンドは実行直後にコンソールにプロンプト">"を出すんですが、その出力待ちでした。

    コンソール機能にはエミュレータ経由のデバッグコンソールを使っています。
    これは1文字づつ送受信するんですが、FINEボーレートが1500000bps(1.5Mbps)だと1文字送信するのに実測で158.8usecかかってます。(前出力文字の送信が終わるまで次の出力要求がダイナミックループで待たされる)

    この待ちはFINE通信にかかる時間なのでTaskAが動こうとTaskXが動こうと変わりません。

    (あーすっきりした)

  • ふぐりんさん、Joさん

    シェルティです、こんにちは。ルネサスの中の人です。今はセキュリティやRTOS開発、GitLabを用いた社内CI/CD環境整備などを担当しています。

    種々実験いただきありがとうございます。シェルティが実験しようとしていた内容と同じ(タスク遷移時にポートで動作を追う(さらにそのあとTracealyzerでも確認する))ですね。シェルティのほうでもふぐりんさんと同じく「再現しない」ところまでは確認しました。

    私もふぐりんさんやJoさんのように、なんでこうなるのか気になってしまい、ふぐりんさんと同じ実験を考えて詳細を追ってみようとしていたところ、実はゴールデンウィーク中に家族に不幸がありお葬式などを仕切っていて実験時間が消えてしまいました。

    ざっと見たところYieldでタスクが動き出すのは合っているように思いますね。タイマで起動するスケジューラのコードをステップ実行で追っていって、どの瞬間にタスクが動き出すか捕まえればカラクリが分かると思います。

    #すみませんシェルティも自分でFreeRTOSを設計したわけではないのでこのあたり暗記が出来てないですね。詳細などを知りたい場合はAWSの技術チームに質問して教えてもらってます。

    またこちらでも実験出来て何か追加で分かったことがあれば書き込みます。

    以上です

  • シェルティさんこんにちは!
    すいません。やりとりが数日あいてたみたいだったんでしゃしゃり出てしまいました。
    おかげで勉強になって楽しかったです。

    Tracealyzerというのを使われるんですね。今調べてました。
    似たようなので私もDT10(今はDT+)持ってるんですがそういえば最近使わないなあ・・
    Tracealyzerのレコーダ部分は公開されてるようですね。で、そういうツールと連携できる仕組みがFreeRTOS側にすでに用意されてるみたいで、ちょっと今興奮してますw