TaskChanger - askn37/askn37.github.io GitHub Wiki

TaskChanger ツールリファレンス

協調的マルチタスク支援ツール。 これは最大3個のタスクをloop関数の他に登録することができる。 各関数は namespaceTaskChanger空間にある。

reduceAVR は未対応

用例

#include <TaskChanger.h>

void yield (void) { TaskChanger::yield(); }

volatile char task1_stack[64];
volatile char task2_stack[64];
volatile char task3_stack[64];

void setup (void) {
  TaskChanger::attach_task_1st(task1_stack, sizeof(task1_stack), &task1);
  TaskChanger::attach_task_2nd(task2_stack, sizeof(task2_stack), &task2);
  TaskChanger::attach_task_3rd(task3_stack, sizeof(task3_stack), &task3);
}

void loop (void) { yield(); }
void task1 (void) { while(true) yield(); }
void task2 (void) { while(true) yield(); }
void task3 (void) { while(true) yield(); }

最初はloopから実行を開始するがyieldが呼ばれると 現在のタスク状態を保存し、次の登録タスクに実行権を移す。 これを順次繰り返して少なくとも4度目のyieldで最初の loopタスクに実行権が(ラウンドロビン動作で)戻る。 yieldが呼ばれない限り現在のタスクが実行権を握り続けるので、 この形態は 協調的マルチタスク(ノンプリエンプティブ・マルチタスク)と呼ばれる。

loopは無条件に繰り返されるが、その他のタスクはそうではない。 それぞれのタスクは自身の中で必要なだけ処理ループを続けなければならない。 そうでなければタスクは終了し、登録エントリから破棄され、 改めて再登録されかつ呼び出されるまで再び実行権を得ることはない。

<TaskChanger.h>はインクルードされた時点で <TimeoutTimer.h> ライブラリも有効化する。 TimeoutTimer::delayはその内部で遅延時間中にグローバル空間のyieldを繰り返し呼びだす。 従って次例のように、異なるLEDを個別の周期でLチカさせるようなコードが容易く書ける。

void yield (void) { TaskChanger::yield(); }

void loop  (void) {             digitalWrite(LED0, TOGGLE); delay(1000); }
void task1 (void) { while(true) digitalWrite(LED1, TOGGLE); delay(1100); }
void task2 (void) { while(true) digitalWrite(LED2, TOGGLE); delay(1200); }
void task3 (void) { while(true) digitalWrite(LED3, TOGGLE); delay(1300); }

またyieldはタイマー割込ベクタの中から呼ぶこともできる。 するとこれはタイマー間隔で強制的にタスク実行権を横取り(プリエンプション)するようになるので プリエンプティブ・マルチタスク にすることができる。 ただしこれは現在の不可分処理が中断されたくない場合でも実行権が他へ移りうるため、 用途によっては好ましくない結果になる。実装可否は慎重に吟味しなければならない。

制約

1. <TimeoutTimer.h>TIMEOUT_BLOCKおよびsleep_cpu_tickssleep_cpu_timer 実行中にyieldが呼ばれてもタスク切替はされない。 これらの内部処理は不可分である。

2. 各タスクには登録時に個別の専用ローカルスタックメモリを必ず割り当てなければならない。 これは該当タスク実行中に不足するようなことが絶対にあってはならない。 スタックメモリ不足は スタック・オーバーフロー を発生せしめ、CPU動作を不正な状態に招く。

3. yieldはタスク切換えに少なくとも 140 CPUサイクルを消費する。 これは CPUが 1MHz 動作の場合は 140usの、20MHz動作なら 7us の遅延に相当する。 この間は他の割込実行は待たされる。 遅延が許されないタイムクリティカルな処理があるなら、 その間はタスク切換が発生しないように配慮しなければならない。

4. 異なるタスク間の変数共有はvolatile宣言を伴っていなければ保証されない。 かつその書換はクリティカル・セクションでなければならない。

5. 再入不能コードの実行中はプリエンプティブ・マルチタスクを使用できない。

<TaskChanger.h>

依存性:<TimeoutTimer.h>

void TaskChanger::attach_task_1st (volatile char __local_stack[], size_t __local_stack_size, void (*__start_task)())

void TaskChanger::attach_task_2nd (volatile char __local_stack[], size_t __local_stack_size, void (*__start_task)())

void TaskChanger::attach_task_3rd (volatile char __local_stack[], size_t __local_stack_size, void (*__start_task)())

第1〜第3タスクのそれぞれにローカルスタックメモリ先頭番地__local_stackと、 その量__local_stack_sizeと、タスク実行関数void __start_task(void)を関連付ける。 ローカルスタックメモリの確保にはvolatile宣言を必要とし、かつグローバル空間に置かなければならない。 必要量はタスク実行内容に依存するが、完全に満足できる量でなければならない。

volatile char task1_stack[64]; /* global */

TaskChanger::attach_task_1st(task1_stack, sizeof(task1_stack), &task1);

普通は少なくとも 64byte 以上だろう。remaining_stack_XXXを参照のこと。

タスクは登録しただけでは実行されない。 タスク実行権の委譲はyieldが行う。

各タスク関数はreturnで終了するとそこで破棄され、 各エントリは未登録状態に戻る。 その後のローカルスタックメモリは再利用しても良い。

各タスクは自身の中でこれらを再実行した場合は次のような挙動となる。

  • 実行中の自分自身のスタックを書き換えることは出来ず、無視される。
  • 他のタスク設定変更はそれを上書きし、元のタスクには復帰せず、継続しない。次からは新たなタスクとなる。

void TaskChanger::detach_task_1st (void)

void TaskChanger::detach_task_2nd (void)

void TaskChanger::detach_task_3rd (void)

現在実行中ではない該当タスクをエントリから取り除き、次回実行権も取り消す。 実行中の自分自身への指定は無視される。

TaskChanger::detach_task_1st();

bool TaskChanger::joined_task_1st (void)

bool TaskChanger::joined_task_2nd (void)

bool TaskChanger::joined_task_3rd (void)

対応するタスクが終了しているならば、真を返す。 該当エントリは未使用なのでattach_task_XXXを再利用できる。

if ( TaskChanger::joined_task_1st() ) {
  /* 1stタスクは終了済 */
}

タスク関数は void 型であり、引数を渡すことも返すことも出来ない。 タスク間の変数受け渡しは volatile 宣言されたグローバル変数を用いることでのみ行える。

size_t TaskChanger::remaining_stack_1st (volatile char __local_stack[])

size_t TaskChanger::remaining_stack_2nd (volatile char __local_stack[])

size_t TaskChanger::remaining_stack_3rd (volatile char __local_stack[])

引数にローカルスタックメモリ先頭番地__local_stackを与えると、 個々のタスクの現在のスタック残量(Byte)を計算して返す。

参照されるのはyieldが呼ばれた時に保存されたスタックポインタなので、 結果はタスク切換に必要な最低量からの余裕値である。 ローカルスタックメモリ初期量はこれを目安に判断するとよいが、 あまりタイトに切り詰めると割込等による観測範囲外のスタック消費で溢れる場合もある。 故に 20〜30byte か それ以上の余裕を持たせるべきである。

size_t remaining_stack_free_size = TaskChanger::remaining_stack_1st(task1_stack);

スタックが不足(オーバーフロー)しても セグメンテーションフォルトのような例外は発生しない。 実行環境は黙して破壊される。

void TaskChanger::yield (void)

現在のタスクを中断し、全レジスタを保存し、次のタスクに実行権を移す。

名前空間のないほうはグローバルな yield仮関数(weak属性登録)の書換であり delay 関数等の内部から呼ばれる想定である。 他のAPI との併用で yield 関数の実体が不明瞭な場合は 名前空間付きで使用するのがよい。

/* week宣言されているグローバルな yield フックを置き換える */
void yield (void) { TaskChanger::yield(); }

TaskChanger::yield(); /* タスク実行権委譲 */

#define NOTUSED_OVERWRITE_YIELD (obsolete)

以前はグローバルのyieldを強制的に置き換えていたが、これは廃止された。 v0.1.2以降では、明示的に記述しなければyieldを置き換えない。

⚠️ **GitHub.com Fallback** ⚠️