前回はDelayに関するAPIでした。今回はTaskのPriorityを操作してみます。今までPriorityはみんな「1」みたいなテキトーな設定をしてきました。優先順位を設定する実験してみることで勉強になるんでないかい、という目論見。しかし、改めて FreeRTOSConfig.h を読んでみると思っていたのと違う?
※「モダンOSのお砂場」投稿順Indexはこちら
FreeRTOS、taskのPriority
FreeRTOSでは、複数のtaskが並行に動作する(ように見える)わけですが、どのtaskを実行するのかはスケジューラ様に任されてます。簡単にまとめてしまうと、Ready状態にあるtaskの中で「一番優先順位の高いやつ」がRun(実行)状態になります。ReadyやらRunやらタスクの状態については以下の過去回で図を描いてますぞ。
モダンOSのお砂場(29) FreeRTOS、プリエンプティブなスケジューリング
御本家の以下のドキュメントもどうぞ。
さてこのように重要なTaskのPriorityですが、今までTaskを作るときにはテキトーに1などと書いてみな一緒にしてました。その心は、
-
- どのTaskもタイムスライスを使いきることなく制御権を短時間で返納
するからみんな同じで良いよね。だいたいタスクの数も2個3個だし。そこで心を入れ替えて、Taskのプライオリティについて改めてまとめると以下のようです。
-
- プライオリティには0からconfigMAX_PRIORITES -1までの段階がある
- タスク生成時にプライオリティの初期値を与える
- プライオリティの数が多い方が優先度が高い
- スケジューラは次のタスクを選ぶとき、優先度が一番高いものを選ぶ
- 何もすることが無いときに走るらしい ILDEタスクのプライオリティは0である。
- 同一レベルに複数のタスクを置いても良い。
- 同一レベルにおかれたタスクを「ラウンドロビン」スケジュールすることもできるが設定による。
-
- configMAX_PRIORITIESは5と設定されていた
Priority制御のためのAPI
プライオリティはタスク生成時に初期設定するようになっていますが、2つあるAPIで読んだり書いたりすることが可能です。
-
- uxTaskPriorityGet()、タスクハンドルのプライオリティを返す
- vTaskPrioritySet()、 タスクにプライオリティを設定する
実験用のソース
さて、今回の実験で明示的に作ったタスクは4個です。
TASK1~TASK3は、プライオリティにより実行順序が変わることを「体感」するためのTASKです。各TASKとも、実行を開始すると割り当てられた出力端子をHighにし、制御権を手放す前にLowにします。3個とも1ms毎に定周期で実行するように設定してます。外部で出力を観察していれば、いつどのTASKが活動しているのか分かる筈。3個のタスクとも初期のプライオリティは1ですが、以下のタスクにより変更されます。
LOOP TASKは、初期プライオリティは1のまま変わらず、10秒に1回実行される「ゆるゆる」タスクです。このタスクの中で、TASK1~TASK3のうち1個だけにプライオリティを+1します。つまり設定されたタスクは10秒間優先度が高くなりっぱなしっす。優先度をプラスするのは「ラウンドロビン」的な順番です。勿論、優先度があがっても10秒後には元の木阿弥、プライオリティは1に戻されます。
TASKの優先度の様子が以下に(TASK2の優先度を「盛っている」ときの様子。)
実験に使ったソースコード、Arduino環境向け「スケッチ」.ino 形式が以下です。なお以下でvTaskPrioritySet(tasks[taskidx], PRIOBASE + 1);のところをコメントアウトするとプライオリティを変更しないケースとなります。
#include <Arduino_FreeRTOS.h> #define T3PIN (7) #define T2PIN (6) #define T1PIN (5) #define LWAIT (10000) #define NTASKS (3) #define PRIOBASE (1) TaskHandle_t loop_task; TaskHandle_t tasks[NTASKS] = {NULL, NULL, NULL}; const char taskname[NTASKS][6] = {"Task1", "Task2", "Task3"}; pin_size_t taskParam[NTASKS] = {T1PIN, T2PIN, T3PIN}; void initPins() { pinMode(T1PIN, OUTPUT); digitalWrite(T1PIN, 0); pinMode(T2PIN, OUTPUT); digitalWrite(T2PIN, 0); pinMode(T3PIN, OUTPUT); digitalWrite(T3PIN, 0); } void loop_thread_func(void *pvParameters) { int taskidx = 0; while (1) { vTaskDelay(LWAIT); vTaskPrioritySet(tasks[taskidx], PRIOBASE); taskidx = taskidx < (NTASKS -1) ? taskidx + 1 : 0; vTaskPrioritySet(tasks[taskidx], PRIOBASE + 1); for (int i=0; i<NTASKS; i++) { UBaseType_t prio = uxTaskPriorityGet(tasks[i]); Serial.print("Task# "); Serial.print(i); Serial.print(" Priority="); Serial.println(prio); } } } void task_func(void *pvParameters) { TickType_t xLastWakeTime; const TickType_t xFrequency = 1; pin_size_t pinNumber = *(pin_size_t *)pvParameters; xLastWakeTime = xTaskGetTickCount(); while (1) { vTaskDelayUntil(&xLastWakeTime, xFrequency); digitalWrite(pinNumber, 1); delayMicroseconds(100); digitalWrite(pinNumber, 0); } } void setup() { Serial.begin(115200); while (!Serial) { } initPins(); auto const rc_loop = xTaskCreate ( loop_thread_func, static_cast<const char*>("Loop Thread"), 512 / 4, nullptr, PRIOBASE, &loop_task ); if (rc_loop != pdPASS) { Serial.println("Failed to create 'loop' thread"); return; } TaskHandle_t xHandle = NULL; BaseType_t xReturned; for (int i=0; i<NTASKS; i++) { xReturned = xTaskCreate ( task_func, taskname[i], 512 / 4, (void *)&taskParam[i], PRIOBASE, &xHandle ); if (xReturned != pdPASS) { Serial.println("Failed to create task."); return; } else { tasks[i] = xHandle; } } Serial.println("Starting scheduler ..."); vTaskStartScheduler(); for( ;; ); /* Never! */ } /* NEVER CALLED! */ void loop() { }
実機上での実行結果
外部端子をDigilent Analog Discovery2のロジアナ機能で観察しました。時間軸の1Divは1ms=FreeRTOSの1TICK時間です。HIGHになっているところが各タスクが走っている期間。
プライオリティを変更しないケース。
上記の順番でずっと実行が続きました。同じレベルの中では生成順なのか順番が決まってるみたいね。
本題のプライオリティを変更するケース。
最初にTASK2が勝っているので、TASK2が優先度高くなってるみたい。
お次はTASK1が優先。プライオリティの変更が時々かかっているので順序も洗い替えされている?知らんけど。
思ったように順序を扱えるようになった?まだまだじゃのう。