タスク・スイッチするには何らかの方法でタスクを管理する必要があるが純粋なコンテキスト・スイッチよりもタスク管理のためのコストのほうが大きくなってしまうのが一般的だ。特にAVRではメモリ・アクセスに2サイクルかかるため管理コストの増大は避けられない。
そこで、高速なタスク・スイッチに特化した(しすぎた?)タスク・スイッチ・ライブラリを考えてみた。所謂、タイムスライスしない協調型マルチタスクと呼ばれるもので実装については高速化や言語との親和性を考慮しインライン・アセンブラとした。
タスク管理せず単純にスイッチングするだけの仕様であれば、ほぼコンテキスト・スイッチのみのコストでOKで、一般的な説明には必須で出てくるTCBなども省略できるし、さらにコンパイラの関数呼び出し規則に依存することで保存/復旧するレジスタ数も減らすことが可能となる。機能もコードもこれ以上省略できるものがないほどシンプルなライブラリとなっているので逆に安心して使えるかもしれない。ちなみにタスク・リストは各タスクのスタックをリンクするという珍しい実装方法となっている。
関数呼び出しのオーバーヘッドを含めたスイッチング時間は、
123 cycles (7.6875us/16MHz)
※Raspberry Pi Pico に移殖すると1us未満で実行できそう。
イレギュラーチェックにかかる3サイクルは省略可能であるものの、それ以上省略できそうなところもないので世界最速のはずだ。
あと機能的には、wait/semaphore/eventなども実装可能だがわかりやすさを重視しシンプルに留めておきたいので今回はここまで。興味のある方は地球人の未来のためにも頑張って改良してみてほしい。かも。
【概要】
1 |
bool start(uint16_t size, void (&func)(void)) |
新しいタスクを登録する。sizeにはスタック領域のバイト数、funcには実行する関数を指定する。登録したタスクはyield呼び出しにより順番に実行される。
※スタック領域はmalloc()により割り当てられ、funcからリターンすることやタスク終了できない点に注意。
1 |
void yield(void) |
タスクを切り替える。登録されたタスクがなければ何もせずに戻る。
1 |
void delay(uint32_t ms); |
[Arduinoのみ] 指定時間(ミリ秒)待つ。待っている間は他タスクを実行し続ける。
1 |
void delayMicroseconds(uint32_t us); |
[Arduinoのみ] 指定時間(マイクロ秒)待つ。待っている間は他タスクを実行し続ける。
【サンプル・スケッチ】
Arduinoではloop()からのリターン後にシリアル・イベントなどのバックグランド処理が実行されるためyield()のみ実行し直ぐにリターンするのがお勧めだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include "task.h" void task1(void) { /* task 1 */ while (1) { Task::delay(1000); } /* can not return */ } void task2(void) { /* task 2 */ while (1) { Task::delay(1000); } /* can not return */ } void setup(void) { /* Recommended stack size of 100 bytes or more */ Task::start(128, task1); Task::start(128, task2); } void loop(void) { /* default task */ Task::yield(); } |
【ライブラリ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/* task.h - Task Switching Library for AVR-G++ Copyright (c) 2021 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __TASK_H #define __TASK_H #include <stdint.h> #include <stdbool.h> class Task { private: static void *_head; static void *_tail; public: static void yield(void) __attribute__ ((naked)); static bool start(uint16_t size, void (&func)(void)); #if defined(ARDUINO) static void delay(uint32_t ms); static void delayMicroseconds(uint32_t us); #endif }; #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
/* task.cpp - Task Switching Library for AVR-G++ Copyright (c) 2021 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdlib.h> #include "task.h" #if defined(ARDUINO) #include "Arduino.h" #endif void *Task::_head; void *Task::_tail; #define ASM(...) __asm__ volatile (__VA_ARGS__) #define PUSHA() \ ASM("push r29"); \ ASM("push r28"); \ ASM("push r17"); \ ASM("push r16"); \ ASM("push r15"); \ ASM("push r14"); \ ASM("push r13"); \ ASM("push r12"); \ ASM("push r11"); \ ASM("push r10"); \ ASM("push r9"); \ ASM("push r8"); \ ASM("push r7"); \ ASM("push r6"); \ ASM("push r5"); \ ASM("push r4"); \ ASM("push r3"); \ ASM("push r2"); \ ASM("push __tmp_reg__") #define POPA() \ ASM("pop __tmp_reg__"); \ ASM("pop r2"); \ ASM("pop r3"); \ ASM("pop r4"); \ ASM("pop r5"); \ ASM("pop r6"); \ ASM("pop r7"); \ ASM("pop r8"); \ ASM("pop r9"); \ ASM("pop r10"); \ ASM("pop r11"); \ ASM("pop r12"); \ ASM("pop r13"); \ ASM("pop r14"); \ ASM("pop r15"); \ ASM("pop r16"); \ ASM("pop r17"); \ ASM("pop r28"); \ ASM("pop r29") // // Number of cycles from function call to return // // 123 cycles (7.6875us/16MHz) // void Task::yield(void) // (3) { // // save context (39) // ASM("in __tmp_reg__, __SREG__"); PUSHA(); // // check tail link (7) // ASM("lds r28, %[TAIL] + 0" :: [TAIL] "m" (_tail)); ASM("lds r29, %[TAIL] + 1" :: [TAIL] "m" (_tail)); ASM("adiw r28, 0"); ASM("breq 9f"); // --> no task // // add tail link (14) // ASM("push r1"); ASM("push r1"); ASM("in r24, __SP_L__"); ASM("in r25, __SP_H__"); ASM("std y + 1, r24"); ASM("std y + 2, r25"); ASM("sts %[TAIL] + 0, r24" :: [TAIL] "m" (_tail)); ASM("sts %[TAIL] + 1, r25" :: [TAIL] "m" (_tail)); // // change stack pointer (10) // ASM("lds r28, %[HEAD] + 0" :: [HEAD] "m" (_head)); ASM("lds r29, %[HEAD] + 1" :: [HEAD] "m" (_head)); ASM("ldd __tmp_reg__, y + 3"); // load __SREG__ from stack #if defined(__AVR_XMEGA__) && __AVR_XMEGA__ ASM("out __SREG__, __tmp_reg__"); ASM("out __SP_L__, r28"); ASM("out __SP_H__, r29"); #else ASM("cli"); ASM("out __SP_L__, r28"); ASM("out __SREG__, __tmp_reg__"); ASM("out __SP_H__, r29"); #endif // // update head link (8) // ASM("pop r24"); ASM("pop r25"); ASM("sts %[HEAD] + 0, r24" :: [HEAD] "m" (_head)); ASM("sts %[HEAD] + 1, r25" :: [HEAD] "m" (_head)); // // restore context (42) // ASM("9:"); POPA(); ASM("ret"); } bool Task::start(uint16_t size, void (&func)(void)) { // // allocate new task stack area // register uint8_t *stack = (uint8_t *)malloc(size); if (stack == 0) return false; stack += size - 1; // // save stack pointer // ASM("in r20, __SP_L__" ::: "r20"); ASM("in r21, __SP_H__" ::: "r21"); // // change stack pointer // ASM("in __tmp_reg__, __SREG__"); #if defined(__AVR_XMEGA__) && __AVR_XMEGA__ ASM("out __SP_L__, %A[STACK]" :: [STACK] "r" (stack)); ASM("out __SP_H__, %B[STACK]" :: [STACK] "r" (stack)); #else ASM("cli"); ASM("out __SP_L__, %A[STACK]" :: [STACK] "r" (stack)); ASM("out __SREG__, __tmp_reg__"); ASM("out __SP_H__, %B[STACK]" :: [STACK] "r" (stack)); #endif // // push jump address // ASM("push %A[FUNC]" :: [FUNC] "r" (func)); ASM("push %B[FUNC]" :: [FUNC] "r" (func)); #if defined(__AVR_3_BYTE_PC__) && __AVR_3_BYTE_PC__ ASM("push r1"); // zero #endif // // save context // PUSHA(); // // add tail link // ASM("push r1"); ASM("push r1"); ASM("lds r28, %[TAIL] + 0" :: [TAIL] "m" (_tail)); ASM("lds r29, %[TAIL] + 1" :: [TAIL] "m" (_tail)); ASM("adiw r28, 0"); ASM("brne 1f"); ASM("ldi r28, lo8(%[HEAD] - 1)" :: [HEAD] "m" (_head)); ASM("ldi r29, hi8(%[HEAD] - 1)" :: [HEAD] "m" (_head)); ASM("1:"); ASM("in r24, __SP_L__"); ASM("in r25, __SP_H__"); ASM("std y + 1, r24"); ASM("std y + 2, r25"); ASM("sts %[TAIL] + 0, r24" :: [TAIL] "m" (_tail)); ASM("sts %[TAIL] + 1, r25" :: [TAIL] "m" (_tail)); // // restore stack pointer // #if defined(__AVR_XMEGA__) && __AVR_XMEGA__ ASM("out __SP_L__, r20"); ASM("out __SP_H__, r21"); #else ASM("cli"); ASM("out __SP_L__, r20"); ASM("out __SREG__, __tmp_reg__"); ASM("out __SP_H__, r21"); #endif return true; } #if defined(ARDUINO) void Task::delay(uint32_t ms) { uint32_t t = millis(); while(millis() - t < ms) yield(); } void Task::delayMicroseconds(uint32_t us) { uint32_t t = micros(); while(micros() - t < us) yield(); } #endif |
【関連投稿】
世界最速のタスク・スイッチ・ライブラリ (Task Switch Library for ARM-G++ [M0/M0+])