TaskChanger - askn37/askn37.github.io GitHub Wiki
協調的マルチタスク支援ツール。
これは最大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_ticks
やsleep_cpu_timer
実行中にyield
が呼ばれてもタスク切替はされない。
これらの内部処理は不可分である。
2. 各タスクには登録時に個別の専用ローカルスタックメモリを必ず割り当てなければならない。 これは該当タスク実行中に不足するようなことが絶対にあってはならない。 スタックメモリ不足は スタック・オーバーフロー を発生せしめ、CPU動作を不正な状態に招く。
3. yield
はタスク切換えに少なくとも 140 CPUサイクルを消費する。
これは CPUが 1MHz 動作の場合は 140usの、20MHz動作なら 7us の遅延に相当する。
この間は他の割込実行は待たされる。
遅延が許されないタイムクリティカルな処理があるなら、
その間はタスク切換が発生しないように配慮しなければならない。
4. 異なるタスク間の変数共有はvolatile
宣言を伴っていなければ保証されない。
かつその書換はクリティカル・セクションでなければならない。
5. 再入不能コードの実行中はプリエンプティブ・マルチタスクを使用できない。
依存性:<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
で終了するとそこで破棄され、
各エントリは未登録状態に戻る。
その後のローカルスタックメモリは再利用しても良い。
各タスクは自身の中でこれらを再実行した場合は次のような挙動となる。
- 実行中の自分自身のスタックを書き換えることは出来ず、無視される。
- 他のタスク設定変更はそれを上書きし、元のタスクには復帰せず、継続しない。次からは新たなタスクとなる。
現在実行中ではない該当タスクをエントリから取り除き、次回実行権も取り消す。 実行中の自分自身への指定は無視される。
TaskChanger::detach_task_1st();
対応するタスクが終了しているならば、真を返す。
該当エントリは未使用なのでattach_task_XXX
を再利用できる。
if ( TaskChanger::joined_task_1st() ) {
/* 1stタスクは終了済 */
}
タスク関数は void 型であり、引数を渡すことも返すことも出来ない。 タスク間の変数受け渡しは volatile 宣言されたグローバル変数を用いることでのみ行える。
引数にローカルスタックメモリ先頭番地__local_stack
を与えると、
個々のタスクの現在のスタック残量(Byte)を計算して返す。
参照されるのはyield
が呼ばれた時に保存されたスタックポインタなので、
結果はタスク切換に必要な最低量からの余裕値である。
ローカルスタックメモリ初期量はこれを目安に判断するとよいが、
あまりタイトに切り詰めると割込等による観測範囲外のスタック消費で溢れる場合もある。
故に 20〜30byte か それ以上の余裕を持たせるべきである。
size_t remaining_stack_free_size = TaskChanger::remaining_stack_1st(task1_stack);
スタックが不足(オーバーフロー)しても セグメンテーションフォルトのような例外は発生しない。 実行環境は黙して破壊される。
現在のタスクを中断し、全レジスタを保存し、次のタスクに実行権を移す。
名前空間のないほうはグローバルな yield
仮関数(weak属性登録)の書換であり
delay
関数等の内部から呼ばれる想定である。
他のAPI との併用で yield
関数の実体が不明瞭な場合は
名前空間付きで使用するのがよい。
/* week宣言されているグローバルな yield フックを置き換える */
void yield (void) { TaskChanger::yield(); }
TaskChanger::yield(); /* タスク実行権委譲 */
以前はグローバルのyield
を強制的に置き換えていたが、これは廃止された。
v0.1.2以降では、明示的に記述しなければyield
を置き換えない。