初めてアセンブラーを覚えたときCPUに使われるのではなくてCPUを思うままに操作する感覚が楽しすぎてマルチタスクスケジューラーなるものをいくつも自作した時があった。今や、組み込み系含め汎用OS全盛の時代でありそんなもの作ったってしょうがないと言う意見もあるが開発者の知識の高まりとともに主義主張ばかりがまかり通る時代になってきたのは少し残念というしかない。
そこで、多くの人たちが実装が不完全であるとか危険だからという理由で使うべきではないと虐められているsetjmp/longjmp/allocaを利用したC++言語に特化したタスクスケジューラークラスを作ってみた。今朝、ふと思い出して速攻で作ったものであるが似たようなものは数多く公開されているので興味があれば調べてもらいたい。恐らく地球上で一番コンパクトかつシンプルなものに仕上がったのではと思う。(笑)
必須要件はsetjmp/longjmp/allocaが正しく実装されている開発環境であることのみ。Arduino/Linux/Windows/etc…等の数多くの環境で動作するはずだ。
完全なマルチタスクではなくコンテクストスイッチングは不完全だし割り込み処理からの利用もできないが、組み込み系特有の細かく処理を分けて記述する煩雑なコードをマルチタスク風にシンプルに書けるようになるのが一番のメリットだろう。
ちなみにタスク管理領域はスタック領域に確保され次の図のようなメモリ構成になる。管理可能なタスク数に上限はなくスタック領域をどれだけ分割できるかに制限されるだけであるがスタック領域に関するチェックは行っていないので実際の利用にあたっては事前に割り込みを含めたスタックの利用状況の調査が必要であることに注意してほしい。
機能はタスクの切り替えを行うyieldしかないがArduino向けとして特別なdelay/delayMicroseconds(内部でyieldを実行)がおまけで実装してある。
仕組みが理解できればEvent/Semaphore等の機能を追加するのは簡単に出来るはず。やる気のある方のために宿題としておこう。(笑)
※この投稿の進化系は、次の投稿を参照するべし。
Micro Core Library for Arduino
【修正履歴】
[2020-02-11]
基本的なタスク同期クラス(CSemaphore/CMutex/CEvent)を追加してみた。ついでにクラス構成を整理整頓し、usedStackSize/freeStackSizeは、CStackクラスに移動。
[2020-02-01]
前回追加したメソッド名を変更。及び、AVR専用ではあるが空きスタック領域サイズを調べるメソッドを新たに追加(AVR以外では間違った結果が返されることがある)。空きスタック領域サイズには割り込み処理で使われるスタック領域(usedStackSizeの戻り値)が含まれることに注意すべし。
unsigned int usedStackSize(unsigned int seconds = 3); // 名称変更
unsigned int freeStackSize(int dummy = 0);
[2020-01-31]
スタック領域サイズ決定の参考用に下記メソッドを追加してみた。戻り値は各タスクに最低必要なスタック領域を返すので、あとは各タスクのネストする関数パラメタ+オート変数領域の合計値に余裕を足したサイズでadd()のスタックパラメタを調整すれば良い。戻り値は正確なものではないが参考用としては使えるだろう。
unsigned int stack(unsigned int seconds = 5);
【C++サンプル】
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 |
#include <stdio.h> #include "CScheduler.h" void task1(void) { while (1) { printf("task-1\n"); Scheduler.yield(); } } void task2(void) { while (1) { printf("task-2\n"); Scheduler.yield(); } } void task3(void) { while (1) { printf("task-3\n"); Scheduler.yield(); } } int main(int argc, char **argv) { Scheduler.add(task1, 2048); Scheduler.add(task2, 1024); Scheduler.add(task3); // run scheduler. not returned. return 0; } |
【Arduinoサンプル】
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 |
#include "CScheduler.h" void task1(void) { while (1) { Serial.println("task-1"); Scheduler.yield(); } } void task2(void) { while (1) { Serial.println("task-2"); Scheduler.yield(); } } void task3(void) { while (1) { Serial.println("task-3"); Scheduler.yield(); } } void setup(void) { Serial.begin(115200); while (!Serial) continue; Scheduler.add(task1, 256); Scheduler.add(task2, 128); Scheduler.add(task3); // run scheduler. not returned. } void loop(void) { } |
【CScheduler.h】
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 |
/* CScheduler.h - Task Scheduler for C++ Copyright (c) 2020 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 __CSCHEDULER_H #define __CSCHEDULER_H #include <stdlib.h> #include <alloca.h> #include <setjmp.h> #ifdef ARDUINO #include "Arduino.h" #endif class CTask { protected: typedef void (*func_t)(void); typedef struct task { jmp_buf context; func_t start; struct task *next; } task_t; task_t *_head; task_t *_tail; public: CTask(void) : _head(0) , _tail(0) { } virtual ~CTask(void) { } }; class CScheduler : public CTask { protected: friend class CSynchronize; public: CScheduler(void) { } virtual ~CScheduler(void) { } void add(func_t start, unsigned int stack = 0) { task_t *task = (task_t *)alloca(stack + sizeof(*task)); task->next = 0; task->start = start; if (_tail) _tail = (_tail->next = task); else _tail = _head = task; if (setjmp(task->context)) { _head->start(); if (_head != _tail) longjmp((_head = _head->next)->context, 1); exit(EXIT_SUCCESS); } if (stack == 0) longjmp(_head->context, 1); } void yield(void) { if (_head != _tail) { if (setjmp(_head->context) == 0) { _tail = (_tail->next = _head); _head = _head->next; _tail->next = 0; longjmp(_head->context, 1); } } } #ifdef ARDUINO void delay(unsigned long ms) { unsigned long t = millis(); do yield(); while (millis() - t < ms); } void delayMicroseconds(unsigned int us) { unsigned int t = (unsigned int)micros(); do yield(); while ((unsigned int)micros() - t < us); } #endif }; extern CScheduler Scheduler; #endif |
【CScheduler.cpp】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* CScheduler.cpp - Task Scheduler for C++ Copyright (c) 2020 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 "CScheduler.h" CScheduler Scheduler; |
【CSynchronize.h】
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 |
/* CSynchronize.h - Task Synchronize for CScheduler Copyright (c) 2020 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 __CSYNCHRONIZE_H #define __CSYNCHRONIZE_H #include "CScheduler.h" class CSynchronize : public CTask { protected: task_t *waiting(void) { return _head; } task_t *running(void) { return Scheduler._head; } void pause(void) { if (Scheduler._head != Scheduler._tail) { task_t *t = Scheduler._head; Scheduler._head = t->next; if (_tail) _tail = (_tail->next = t); else _head = _tail = t; t->next = 0; if (setjmp(t->context) == 0) longjmp(Scheduler._head->context, 1); } } void resume(void) { task_t *t; if ((t = _head) != _tail) _head = t->next; else _head = _tail = 0; t->next = 0; Scheduler._tail = (Scheduler._tail->next = t); } void release(void) { if (_head) { Scheduler._tail->next = _head; Scheduler._tail = _tail; _head = _tail = 0; } } public: CSynchronize(void) { } virtual ~CSynchronize(void) { release(); } }; #endif |
【CSemaphore.h】
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 |
/* CSemaphore.h - Semaphore for CScheduler Copyright (c) 2020 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 __CSEMAPHORE_H #define __CSEMAPHORE_H #include "CSynchronize.h" class CSemaphore : public CSynchronize { protected: unsigned int _value; public: CSemaphore(unsigned int value = 0) : _value(value) { } virtual ~CSemaphore(void) { } void wait(void) { if (_value) --_value; else pause(); } void post(void) { if (waiting()) resume(); else ++_value; } }; #endif |
【CMutex.h】
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 |
/* CMutex.h - Mutext for CScheduler Copyright (c) 2020 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 __CMUTEX_H #define __CMUTEX_H #include "CSynchronize.h" class CMutex : public CSynchronize { protected: unsigned int _count; task_t *_owner; public: CMutex(void) : _count(1) , _owner(0) { } virtual ~CMutex(void) { } void lock(void) { if (!_owner) _owner = running(); else if (_owner == running()) ++_count; else pause(); } void unlock(void) { if (_owner == running()) { if (--_count == 0) { _count = 1; _owner = waiting(); if (_owner) resume(); } } } }; #endif |
【CEvent.h】
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 |
/* CEvent.h - Event for CScheduler Copyright (c) 2020 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 __CEVENT_H #define __CEVENT_H #include <stdbool.h> #include "CSynchronize.h" class CEvent : public CSynchronize { protected: bool _event; public: CEvent(bool event = false) : _event(event) { } virtual ~CEvent(void) { } void wait(void) { if (_event) _event = false; else pause(); } void signal(void) { if (waiting()) resume(); else _event = true; } }; #endif |
【CStack.h】
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 |
/* CStack.h - Stack Utility for CScheduler Copyright (c) 2020 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 __CSTACK_H #define __CSTACK_H #include <stdlib.h> #include <string.h> #include <alloca.h> #include "CScheduler.h" #ifdef ARDUINO #include "Arduino.h" #else #include <time.h> #endif class CStack : public CTask { private: CStack(void) { } public: static unsigned int usedStackSize(unsigned int seconds = 3) { static const unsigned char MAGIC = 0xCC; static const unsigned char SAFETY = 8; unsigned char *bottom, *top = (unsigned char *)alloca(0) - SAFETY; unsigned int count, next, size; next = 32; do { if ((size = next) == 0) return 0; next <<= 1; bottom = top - size; memset(bottom, MAGIC, size); #ifdef ARDUINO ::delay(seconds * 1000); #else for (time_t t = time(NULL); time(NULL) - t < seconds; ) continue; #endif count = 0; do { if (*bottom != MAGIC) break; ++count; } while (++bottom < top); } while (next && (count < 8)); return sizeof(task_t) + SAFETY + (size - count); } static unsigned int freeStackSize(int dummy = 0) { #ifdef __AVR__ extern char * __brkval; #endif char *heap, *stack = (char *)&dummy + sizeof(dummy); #ifdef __AVR__ if (__brkval) return (unsigned int)(stack - __brkval); #endif free(heap = (char *)malloc(0)); return (unsigned int)(stack - heap); } }; #endif |