├── articles └── ru │ ├── article1 │ ├── nano.png │ └── soidet.jpg │ └── article1.md ├── examples └── Colors │ ├── Makefile │ └── Colors.ino ├── library.properties ├── keywords.txt └── src ├── fiber.h ├── fiber.c └── list.h /articles/ru/article1/nano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unera/arduino-fibers/HEAD/articles/ru/article1/nano.png -------------------------------------------------------------------------------- /articles/ru/article1/soidet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unera/arduino-fibers/HEAD/articles/ru/article1/soidet.jpg -------------------------------------------------------------------------------- /examples/Colors/Makefile: -------------------------------------------------------------------------------- 1 | 2 | USER_LIB_PATH := $(realpath ../..) 3 | ARDUINO_LIBS = src 4 | BOARD_TAG = nano328 5 | #BOARD_TAG = nano 6 | BOARD_SUB = atmega328 7 | 8 | include /usr/share/arduino/Arduino.mk 9 | 10 | CFLAGS += -std=c99 11 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=fiber 2 | version=0.0.1 3 | author=Dmitry E. Oboukhov 4 | maintainer=Dmitry E. Oboukhov 5 | sentence=Light processes for arduino 6 | paragraph=This library provides non-concurrently process manager for arduino. 7 | category=Other 8 | includes=fiber.h 9 | url=https://github.com/unera/arduino-fibers 10 | #architectures=avr 11 | 12 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map fiber 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | fiber KEYWORD1 fiber 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | fiber_current KEYWORD2 16 | fiber_create KEYWORD2 17 | fiber_cancel KEYWORD2 18 | fibers_init KEYWORD2 19 | 20 | fiber_cede KEYWORD2 21 | fiber_schedule KEYWORD2 22 | fiber_wakeup KEYWORD2 23 | 24 | ####################################### 25 | # Constants (LITERAL1) 26 | ####################################### 27 | 28 | FIBER_STACK_SIZE KEYWORD2 29 | -------------------------------------------------------------------------------- /examples/Colors/Colors.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define FIBER_STACK_SIZE 80 5 | #include 6 | 7 | 8 | #define F_CLOCK 128 9 | 10 | static struct fiber *_delay[5] = {NULL, NULL, NULL, NULL, NULL}; 11 | #define SLOTS (sizeof(_delay) / sizeof(*_delay)) 12 | 13 | 14 | void 15 | fiber_delay(uint8_t clocks) 16 | { 17 | while(clocks--) { 18 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 19 | for (uint8_t i = 0; i < SLOTS; i++) { 20 | if (!_delay[i]) { 21 | _delay[i] = fiber_current(); 22 | break; 23 | } 24 | } 25 | } 26 | fiber_schedule(); 27 | } 28 | } 29 | 30 | /* data for all processes */ 31 | static struct color_out { 32 | uint8_t led, period, start; 33 | } colors[] = { 34 | { .led = 7, .period = 29, .start = LOW }, 35 | { .led = 8, .period = 30, .start = HIGH }, 36 | { .led = 9, .period = 31, .start = LOW }, 37 | }; 38 | 39 | /* fiber process */ 40 | static void 41 | led_X(void *data) 42 | { 43 | struct color_out *opts = (struct color_out *)data; 44 | 45 | pinMode(opts->led, OUTPUT); 46 | digitalWrite(opts->led, opts->start); 47 | 48 | for (;;) { 49 | fiber_delay(opts->period); 50 | if (digitalRead(opts->led)) 51 | digitalWrite(opts->led, LOW); 52 | else 53 | digitalWrite(opts->led, HIGH); 54 | } 55 | } 56 | 57 | 58 | 59 | 60 | void 61 | setup() { 62 | // configure timer 63 | OCR2A = F_CPU / 1024 / F_CLOCK; 64 | TCCR2A = (1 << WGM21) | (0 << WGM20); // CTC to OCR2A 65 | TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20); // divider = 1024 66 | TIMSK2 = (1 << OCIE2A); // interrupt enable 67 | sei(); // global interrupts 68 | 69 | // enable fibers 70 | fibers_init(); 71 | 72 | FIBER(led_X, (void *)&colors[0]); 73 | FIBER(led_X, (void *)&colors[1]); 74 | FIBER(led_X, (void *)&colors[2]); 75 | } 76 | 77 | SIGNAL(TIMER2_COMPA_vect) { 78 | TCNT2 = 0; 79 | for (uint8_t i = 0; i < SLOTS; i++) { 80 | fiber_wakeup(_delay[i]); 81 | _delay[i] = NULL; 82 | } 83 | } 84 | 85 | void 86 | loop() { 87 | fiber_schedule(); 88 | } 89 | -------------------------------------------------------------------------------- /src/fiber.h: -------------------------------------------------------------------------------- 1 | #ifndef FIBER_H 2 | #define FIBER_H 3 | #include 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /** 11 | * struct fiber 12 | * - a structure contains fiber private data. 13 | */ 14 | struct fiber; 15 | 16 | /** 17 | * typedef fiber_cb 18 | * fiber startup function 19 | */ 20 | typedef void (*fiber_cb)(void *); 21 | 22 | /** 23 | * fibers_init() - init fiber system 24 | * 25 | * Note: can be call once by session. Main process will 26 | * be registered as current fiber. The fiber can be cancelled 27 | * if the other fibers are present. 28 | */ 29 | void fibers_init(void); 30 | 31 | /** 32 | * fiber_create() - create new fiber 33 | * @cb - fiber startup function 34 | * @stack - stack for created fiber 35 | * @stack_size - stack size 36 | * @data - data that will passed to fiber startup function 37 | * 38 | * Note: the function will allocate struct fiber (in the stack) 39 | * and use the stack while doing switch to the fiber. 40 | * 41 | * New fiber creates with 'R' status. One of the following fiber_cede 42 | * or fiber_schedule calls will switch to the fiber. 43 | */ 44 | struct fiber * 45 | fiber_create(fiber_cb cb, void *stack, size_t stack_size, void *data); 46 | 47 | /** 48 | * fiber_current - get current fiber handler 49 | */ 50 | struct fiber * fiber_current(void); 51 | 52 | /** fiber_status - get fiber status 53 | * 54 | * - 'r' - normal processing fiber 55 | * - 's' - scheduled fiber (use fiber_wakeup to make the fiber ready) 56 | * - 'd' - dead fiber (callback was done) 57 | * - 'c' - cancelled by fiber_cancel 58 | */ 59 | unsigned char fiber_status(const struct fiber *f); 60 | 61 | /** fiber_cede - switch to the other processing fiber 62 | * 63 | * return immediately if there is no one other processing fibers 64 | */ 65 | void fiber_cede(void); 66 | 67 | /** fiber_cancel - cancel processing fiber 68 | * 69 | * Note: the function can use to cancel current fiber. 70 | */ 71 | void fiber_cancel(struct fiber *fiber); 72 | 73 | /** fiber_schedule - switch to the other ready fiber, 74 | * marks current fiber as scheduled. 75 | * 76 | */ 77 | void fiber_schedule(void); 78 | 79 | /** fiber_wakeup - wakeup scheduled fiber 80 | * 81 | * Note: the function can be call inside interrupt handler. 82 | */ 83 | void fiber_wakeup(struct fiber *w); 84 | 85 | 86 | #define FIBER_CREATE(__cb, __stack_size, __data) \ 87 | do { \ 88 | static unsigned char __stack[__stack_size]; \ 89 | fiber_create(__cb, __stack, __stack_size, __data); \ 90 | } while(0); 91 | 92 | #define FIBERV_CREATE(__cb, __stack_size) \ 93 | FIBER_CREATE(__cb, __stack_size, NULL) 94 | 95 | #ifdef FIBER_STACK_SIZE 96 | #define FIBER(__cb, __data) \ 97 | FIBER_CREATE(__cb, FIBER_STACK_SIZE, __data) 98 | 99 | #define FIBERV(__cb) \ 100 | FIBER_CREATE(__cb, FIBER_STACK_SIZE, NULL) 101 | 102 | #endif /* defined FIBER_STACK_SIZE */ 103 | 104 | 105 | #ifdef __cplusplus 106 | }; /* extern "C" */ 107 | #endif 108 | 109 | #endif /* FIBER_H */ 110 | -------------------------------------------------------------------------------- /src/fiber.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "list.h" 8 | 9 | enum fiber_state { 10 | READY = 'r', 11 | STARTING = 'R', 12 | WAKEUP = 'W', 13 | 14 | SCHEDULED = 's', 15 | DEAD = 'd', 16 | CANCELLED = 'c' 17 | }; 18 | 19 | typedef uint16_t code_t; 20 | struct fiber { 21 | struct list_head list; // member 22 | 23 | fiber_cb cb; // fiber function 24 | code_t sp; // stack pointer 25 | 26 | void *data; 27 | enum fiber_state state; 28 | }; 29 | 30 | static struct fiber *current; 31 | static LIST_HEAD(ready); 32 | static LIST_HEAD(sch); 33 | static LIST_HEAD(dead); 34 | 35 | static void _fiber_run(void); 36 | static struct fiber * _fiber_fetch_next_ready(void); 37 | void _fiber_switch(struct fiber *next, struct list_head *list); 38 | static void _fiber_schedule(enum fiber_state new_state, struct list_head *list); 39 | 40 | 41 | struct fiber * 42 | fiber_create(fiber_cb cb, void *stack, size_t stack_size, void *data) 43 | { 44 | struct fiber *c = (struct fiber *)stack; 45 | c->state = STARTING; 46 | c->cb = cb; 47 | c->sp = (code_t)(((uint8_t *)stack) + stack_size - 1); 48 | c->data = data; 49 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 50 | list_add_tail(&c->list, &ready); 51 | } 52 | return c; 53 | } 54 | 55 | // return current fiber 56 | struct fiber * 57 | fiber_current(void) 58 | { 59 | return current; 60 | } 61 | 62 | void 63 | fiber_cede(void) 64 | { 65 | if (!current) // not init yet 66 | return; 67 | 68 | struct fiber *next; 69 | if (!(next = _fiber_fetch_next_ready())) 70 | return; 71 | 72 | return _fiber_switch(next, &ready); 73 | } 74 | 75 | void 76 | fiber_schedule(void) 77 | { 78 | if (current->state == WAKEUP) { 79 | current->state = READY; 80 | return; 81 | } 82 | 83 | return _fiber_schedule(SCHEDULED, &sch); 84 | } 85 | 86 | void 87 | fiber_wakeup(struct fiber *f) 88 | { 89 | if (!f) 90 | return; 91 | if (f->state != SCHEDULED) { 92 | if (f == current && f->state == READY) { 93 | f->state = WAKEUP; 94 | return; 95 | } 96 | return; 97 | } 98 | 99 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 100 | list_del_init(&f->list); 101 | list_add_tail(&f->list, &ready); 102 | f->state = READY; 103 | } 104 | } 105 | 106 | void 107 | fibers_init(void) 108 | { 109 | static struct fiber _main; 110 | if (current) // already init done 111 | return; 112 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 113 | memset(&_main, 0, sizeof(_main)); 114 | _main.state = READY; 115 | list_add_tail(&_main.list, &ready); 116 | current = &_main; 117 | } 118 | } 119 | 120 | void 121 | fiber_cancel(struct fiber *f) 122 | { 123 | if (!f) 124 | return; 125 | switch(f->state) { 126 | case DEAD: 127 | case CANCELLED: 128 | return; 129 | case READY: 130 | case WAKEUP: 131 | break; 132 | case SCHEDULED: 133 | case STARTING: 134 | goto DROP; 135 | } 136 | if (f == current) { 137 | return _fiber_schedule(CANCELLED, &dead); 138 | } 139 | 140 | DROP: 141 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 142 | f->state = CANCELLED; 143 | list_del_init(&f->list); 144 | list_add_tail(&f->list, &dead); 145 | } 146 | } 147 | 148 | unsigned char 149 | fiber_status(const struct fiber *f) 150 | { 151 | if (!f) 152 | return 'U'; 153 | switch(f->state) { 154 | case READY: 155 | case STARTING: 156 | case WAKEUP: 157 | return READY; 158 | default: 159 | return f->state; 160 | } 161 | } 162 | 163 | /********** private functions ************************************/ 164 | 165 | static void 166 | _fiber_run(void) 167 | { 168 | for(;;) { 169 | current->state = READY; 170 | current->cb(current->data); 171 | current->state = DEAD; 172 | _fiber_schedule(DEAD, &dead); 173 | // someone woke up zombie 174 | } 175 | } 176 | 177 | void 178 | _fiber_switch(struct fiber *next, struct list_head *list) 179 | { 180 | struct fiber *prev; 181 | prev = current; 182 | 183 | static uint8_t real_sreg; 184 | 185 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 186 | list_del_init(&prev->list); 187 | if (list) 188 | list_add_tail(&prev->list, list); 189 | current = next; 190 | 191 | // hack!: we hope ATOMIC_RESTORESTATE is unchanged 192 | // in the future 193 | real_sreg = sreg_save; 194 | 195 | __asm__ __volatile__ ( 196 | 197 | "PUSH r2\n" 198 | "PUSH r3\n" 199 | "PUSH r4\n" 200 | "PUSH r5\n" 201 | "PUSH r6\n" 202 | "PUSH r7\n" 203 | "PUSH r8\n" 204 | "PUSH r9\n" 205 | "PUSH r10\n" 206 | "PUSH r11\n" 207 | "PUSH r12\n" 208 | "PUSH r13\n" 209 | "PUSH r14\n" 210 | "PUSH r15\n" 211 | "PUSH r16\n" 212 | "PUSH r17\n" 213 | 214 | "PUSH r28\n" 215 | "PUSH r29\n" 216 | ); 217 | 218 | /* switch stack! */ 219 | prev->sp = SP; 220 | SP = next->sp; 221 | 222 | if (current->state == STARTING) { 223 | SREG = real_sreg; 224 | // don't return here 225 | _fiber_run(); 226 | } 227 | 228 | __asm__ __volatile__ ( 229 | 230 | "POP r29\n" 231 | "POP r28\n" 232 | 233 | "POP r17\n" 234 | "POP r16\n" 235 | "POP r15\n" 236 | "POP r14\n" 237 | "POP r13\n" 238 | "POP r12\n" 239 | "POP r11\n" 240 | "POP r10\n" 241 | "POP r9\n" 242 | "POP r8\n" 243 | "POP r7\n" 244 | "POP r6\n" 245 | "POP r5\n" 246 | "POP r4\n" 247 | "POP r3\n" 248 | "POP r2\n" 249 | ); 250 | } 251 | 252 | // stack was switched, so SREG could be changed 253 | // TODO: review 254 | SREG = real_sreg; 255 | current->state = READY; 256 | } 257 | 258 | static void 259 | _fiber_schedule(enum fiber_state new_state, struct list_head *list) 260 | { 261 | if (!current) 262 | return; 263 | 264 | current->state = new_state; 265 | struct fiber *next; 266 | 267 | // Deadlock: there is no fiber ready 268 | // wait until next fiber is present 269 | // or current is ready 270 | for (;;) { 271 | next = _fiber_fetch_next_ready(); 272 | 273 | if (current->state == READY && new_state == SCHEDULED) { 274 | return; 275 | } 276 | if (next) { 277 | return _fiber_switch(next, list); 278 | } 279 | 280 | } 281 | } 282 | 283 | static struct fiber * 284 | _fiber_fetch_next_ready(void) 285 | { 286 | struct list_head *pos; 287 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 288 | list_for_each(pos, &ready) { 289 | if (pos == ¤t->list) 290 | continue; 291 | return list_entry(pos, struct fiber, list); 292 | } 293 | } 294 | return NULL; 295 | } 296 | -------------------------------------------------------------------------------- /src/list.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIST_H 2 | #define __LIST_H 3 | #include 4 | 5 | /* This file is from Linux Kernel (include/linux/list.h) 6 | * and modified by simply removing hardware prefetching of list items. 7 | * Here by copyright, credits attributed to wherever they belong. 8 | * Kulesh Shanmugasundaram (kulesh [squiggly] isis.poly.edu) 9 | */ 10 | 11 | /* 12 | * Simple doubly linked list implementation. 13 | * 14 | * Some of the internal functions ("__xxx") are useful when 15 | * manipulating whole lists rather than single entries, as 16 | * sometimes we already know the next/prev entries and we can 17 | * generate better code by using them directly rather than 18 | * using the generic single-entry routines. 19 | */ 20 | 21 | struct list_head { 22 | struct list_head *next, *prev; 23 | }; 24 | 25 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 26 | 27 | #define LIST_HEAD(name) \ 28 | struct list_head name = LIST_HEAD_INIT(name) 29 | 30 | #define INIT_LIST_HEAD(ptr) do { \ 31 | (ptr)->next = (ptr); (ptr)->prev = (ptr); \ 32 | } while (0) 33 | 34 | /* 35 | * Insert a newl entry between two known consecutive entries. 36 | * 37 | * This is only for internal list manipulation where we know 38 | * the prev/next entries already! 39 | */ 40 | static inline void __list_add(struct list_head *newl, 41 | struct list_head *prev, 42 | struct list_head *next) 43 | { 44 | next->prev = newl; 45 | newl->next = next; 46 | newl->prev = prev; 47 | prev->next = newl; 48 | } 49 | 50 | /** 51 | * list_add - add a newl entry 52 | * @newl: newl entry to be added 53 | * @head: list head to add it after 54 | * 55 | * Insert a newl entry after the specified head. 56 | * This is good for implementing stacks. 57 | */ 58 | static inline void list_add(struct list_head *newl, struct list_head *head) 59 | { 60 | __list_add(newl, head, head->next); 61 | } 62 | 63 | /** 64 | * list_add_tail - add a newl entry 65 | * @newl: newl entry to be added 66 | * @head: list head to add it before 67 | * 68 | * Insert a newl entry before the specified head. 69 | * This is useful for implementing queues. 70 | */ 71 | static inline void list_add_tail(struct list_head *newl, struct list_head *head) 72 | { 73 | __list_add(newl, head->prev, head); 74 | } 75 | 76 | /* 77 | * Delete a list entry by making the prev/next entries 78 | * point to each other. 79 | * 80 | * This is only for internal list manipulation where we know 81 | * the prev/next entries already! 82 | */ 83 | static inline void __list_del(struct list_head *prev, struct list_head *next) 84 | { 85 | next->prev = prev; 86 | prev->next = next; 87 | } 88 | 89 | /** 90 | * list_del - deletes entry from list. 91 | * @entry: the element to delete from the list. 92 | * Note: list_empty on entry does not return true after this, the entry is in an undefined state. 93 | */ 94 | static inline void list_del(struct list_head *entry) 95 | { 96 | __list_del(entry->prev, entry->next); 97 | entry->next = (struct list_head *) 0; 98 | entry->prev = (struct list_head *) 0; 99 | } 100 | 101 | /** 102 | * list_del_init - deletes entry from list and reinitialize it. 103 | * @entry: the element to delete from the list. 104 | */ 105 | static inline void list_del_init(struct list_head *entry) 106 | { 107 | __list_del(entry->prev, entry->next); 108 | INIT_LIST_HEAD(entry); 109 | } 110 | 111 | /** 112 | * list_move - delete from one list and add as another's head 113 | * @list: the entry to move 114 | * @head: the head that will precede our entry 115 | */ 116 | static inline void list_move(struct list_head *list, struct list_head *head) 117 | { 118 | __list_del(list->prev, list->next); 119 | list_add(list, head); 120 | } 121 | 122 | /** 123 | * list_move_tail - delete from one list and add as another's tail 124 | * @list: the entry to move 125 | * @head: the head that will follow our entry 126 | */ 127 | static inline void list_move_tail(struct list_head *list, 128 | struct list_head *head) 129 | { 130 | __list_del(list->prev, list->next); 131 | list_add_tail(list, head); 132 | } 133 | 134 | /** 135 | * list_empty - tests whether a list is empty 136 | * @head: the list to test. 137 | */ 138 | static inline int list_empty(struct list_head *head) 139 | { 140 | return head->next == head; 141 | } 142 | 143 | static inline void __list_splice(struct list_head *list, 144 | struct list_head *head) 145 | { 146 | struct list_head *first = list->next; 147 | struct list_head *last = list->prev; 148 | struct list_head *at = head->next; 149 | 150 | first->prev = head; 151 | head->next = first; 152 | 153 | last->next = at; 154 | at->prev = last; 155 | } 156 | 157 | /** 158 | * list_splice - join two lists 159 | * @list: the newl list to add. 160 | * @head: the place to add it in the first list. 161 | */ 162 | static inline void list_splice(struct list_head *list, struct list_head *head) 163 | { 164 | if (!list_empty(list)) 165 | __list_splice(list, head); 166 | } 167 | 168 | /** 169 | * list_splice_init - join two lists and reinitialise the emptied list. 170 | * @list: the newl list to add. 171 | * @head: the place to add it in the first list. 172 | * 173 | * The list at @list is reinitialised 174 | */ 175 | static inline void list_splice_init(struct list_head *list, 176 | struct list_head *head) 177 | { 178 | if (!list_empty(list)) { 179 | __list_splice(list, head); 180 | INIT_LIST_HEAD(list); 181 | } 182 | } 183 | 184 | /** 185 | * list_entry - get the struct for this entry 186 | * @ptr: the &struct list_head pointer. 187 | * @type: the type of the struct this is embedded in. 188 | * @member: the name of the list_struct within the struct. 189 | */ 190 | #define list_entry(ptr, type, member) \ 191 | ((type *)((char *)(ptr)-(uint16_t)(&((type *)0)->member))) 192 | 193 | /** 194 | * list_for_each - iterate over a list 195 | * @pos: the &struct list_head to use as a loop counter. 196 | * @head: the head for your list. 197 | */ 198 | #define list_for_each(pos, head) \ 199 | for (pos = (head)->next; pos != (head); \ 200 | pos = pos->next) 201 | /** 202 | * list_for_each_prev - iterate over a list backwards 203 | * @pos: the &struct list_head to use as a loop counter. 204 | * @head: the head for your list. 205 | */ 206 | #define list_for_each_prev(pos, head) \ 207 | for (pos = (head)->prev; pos != (head); \ 208 | pos = pos->prev) 209 | 210 | /** 211 | * list_for_each_safe - iterate over a list safe against removal of list entry 212 | * @pos: the &struct list_head to use as a loop counter. 213 | * @n: another &struct list_head to use as temporary storage 214 | * @head: the head for your list. 215 | */ 216 | #define list_for_each_safe(pos, n, head) \ 217 | for (pos = (head)->next, n = pos->next; pos != (head); \ 218 | pos = n, n = pos->next) 219 | 220 | /** 221 | * list_for_each_entry - iterate over list of given type 222 | * @pos: the type * to use as a loop counter. 223 | * @head: the head for your list. 224 | * @member: the name of the list_struct within the struct. 225 | */ 226 | #define list_for_each_entry(pos, head, member) \ 227 | for (pos = list_entry((head)->next, typeof(*pos), member); \ 228 | &pos->member != (head); \ 229 | pos = list_entry(pos->member.next, typeof(*pos), member)) 230 | 231 | /** 232 | * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry 233 | * @pos: the type * to use as a loop counter. 234 | * @n: another type * to use as temporary storage 235 | * @head: the head for your list. 236 | * @member: the name of the list_struct within the struct. 237 | */ 238 | #define list_for_each_entry_safe(pos, n, head, member) \ 239 | for (pos = list_entry((head)->next, typeof(*pos), member), \ 240 | n = list_entry(pos->member.next, typeof(*pos), member); \ 241 | &pos->member != (head); \ 242 | pos = n, n = list_entry(n->member.next, typeof(*n), member)) 243 | 244 | 245 | #endif 246 | -------------------------------------------------------------------------------- /articles/ru/article1.md: -------------------------------------------------------------------------------- 1 | # fiber — легковесные процессы для Arduino 2 | 3 | ![](https://habrastorage.org/webt/ha/ti/3u/hati3ucwxiuhtyb00ac2fojx42w.png) 4 | 5 | А давайте притащим мир большого программирования в Arduino! 6 | 7 | Любая программа, а тем более программа близкая к аппаратуре (а какие еще на arduino бывают?) при рассмотрении представляет собой множество параллельно работающих ветвей. 8 | 9 | При этом в реальной жизни обработка **большинства** вещей в реальном времени не требуется. Достаточно иметь нечто похожее на реальное время. 10 | 11 | Например если мы программируем скажем гистерезисный регулятор температуры, то как правило совершенно не важно прямо сейчас сработает включатель нагревателя или через пару милисекунд. 12 | 13 | А вот если мы программируем скажем регулятор ШИМ (не рассматриваем аппаратные способы), то тут нам возможно потребуется считать каждый такт процессора, чтобы обеспечить приемлемую точность регулирования. 14 | 15 | Если рассмотреть структуру произвольного **сложного** программно-аппаратного проекта в том числе на Arduino, то увидим, что задач требующих "реального" (с жесткими требованиями) реалтайма - меньшинство, а большинству задач достаточно условного реалтайма. 16 | 17 | Программирование реального реалтайма - это как правило прерывания и аппаратные хитрости. В этой статье поговорим о программировании реалтайма условного. 18 | 19 | 20 | 21 | Давайте представим что мы разрабатываем скажем систему управления обычным бытовым холодильником. Эта система включает в себя: 22 | 23 | 1. Регулятор температуры 24 | 2. Органы управления этой самой температурой (пусть будет переключатель на три положения) 25 | 3. Датчик открытия двери 26 | 4. Свет в холодильнике 27 | 5. Ну и например выход в интернет, куда ж без него. 28 | 29 | 30 | ### Управляем температурой 31 | 32 | Давайте попробуем рассмотреть для начала регулятор температуры. Как правило в холодильниках используют гистерезисное регулирование. Мы не будем придумывать велосипед и просто его реализуем. 33 | 34 | ```c 35 | 36 | void 37 | temperature_regulator(void) { 38 | 39 | for (;;) { 40 | uint16_t current_t = get_adc(INPUT_T); 41 | 42 | if (current_t > t_on) 43 | enable_cooler(); 44 | 45 | if (current_t < t_off) 46 | disable_cooler(); 47 | } 48 | } 49 | 50 | ``` 51 | 52 | Где: `t_on` и `t_off` - температуры гистерезиса. `INPUT_T` - вход АЦП измерения температуры. `get_adc` - некая функция производящая измерение АЦП. Функции `enable_cooler` и `disable_cooler` - соответственно включают и выключают охладитель. 53 | 54 | Вроде просто? 55 | 56 | Рассматривая поближе составляющие сразу натыкаемся на то, что многие вещи включают в себя циклы ожидания. Например функция `get_adc` могла бы выглядеть как-то так: 57 | 58 | ```c 59 | uint16_t 60 | get_adc(uint8_t input) 61 | { 62 | while (adc_is_busy()); 63 | adc_switch_mux(input); 64 | adc_start(); 65 | while (adc_is_busy()); 66 | return adc_value(); 67 | } 68 | ``` 69 | 70 | Где `adc_switch_mux` - переключает входной мультиплексор АЦП на нужный вход, `adc_start` запускает АЦП преобразование. Пока преобразование выполняется нам приходится ждать - пустой цикл пока `adc_is_busy` возвращает истину. Когда преобразование выполнится `adc_value` вернет нам результат. 71 | 72 | ### управляем светом 73 | 74 | Управление светом в холодильнике тривиально: 75 | 76 | ```c 77 | void 78 | light_control(void) 79 | { 80 | for (;;) { 81 | if (sensor_door() == DOOR_OPEN) 82 | light_on(); 83 | else 84 | light_off(); 85 | } 86 | } 87 | ``` 88 | 89 | О внедрении сюда выключения света по таймеру мы поговорим немного позднее. Сейчас попробуем соединить эти две программы. 90 | 91 | Обе программы вполне наглядны понять и написать их сможет школьник. Но как соединить их в один процесс? 92 | 93 | Самое простое - преобразовать программы в функции и вызывать их в бесконечном цикле. Именно этот подход предлагает нам Arduino с его традиционной функцией `loop`: 94 | 95 | ```c 96 | void 97 | temperature_regulator(void) { 98 | 99 | uint16_t current_t = get_adc(INPUT_T); 100 | 101 | if (current_t > t_on) 102 | enable_cooler(); 103 | 104 | if (current_t < t_off) 105 | disable_cooler(); 106 | } 107 | 108 | void 109 | light_control(void) 110 | { 111 | if (sensor_door() == DOOR_OPEN) 112 | light_on(); 113 | else 114 | light_off(); 115 | } 116 | 117 | 118 | void 119 | loop(void) 120 | { 121 | temperature_regulator(); 122 | light_control(); 123 | ... 124 | } 125 | ``` 126 | 127 | Вроде все просто? Но давайте вернемся к `get_adc`. Просто так уже эта функция не разворачивается. Можно конечно оставить все как есть (АЦП преобразование надолго нас не задержит), для случая холодильника возможно и подойдет, но давайте попробуем развернуть и этот цикл. 128 | 129 | Какие сложности возникают: 130 | 131 | 1. Поскольку имеется возвращаемое значение `get_adc`, то нужно его где-то хранить 132 | 2. Если АЦП не используется нигде в другом месте, то у разработчика возникает большой соблазн взять и сунуть измерение АЦП прямо внутрь `temperature_regulator`: 133 | 134 | ```c 135 | 136 | enum adc_state { FREE, BUSY, VERYBUSY } state = VERYBUSY; 137 | 138 | void 139 | temperature_regulator(void) 140 | { 141 | uint16_t current_t; 142 | switch(state) { 143 | case VERYBUSY: // АЦП не нами занято 144 | if (adc_is_busy()) 145 | return; 146 | state = FREE; 147 | case FREE: 148 | adc_switch_mux(input); 149 | adc_start(); 150 | state = BUSY; 151 | return; 152 | case BUSY: 153 | if (adc_is_busy()) 154 | return; 155 | current_t = adc_value(); 156 | state = FREE; 157 | break; 158 | } 159 | 160 | if (current_t > t_on) 161 | enable_cooler(); 162 | 163 | if (current_t < t_off) 164 | disable_cooler(); 165 | } 166 | ``` 167 | 168 | Вроде не сильно сложно? Но из неприятностей: 169 | 170 | 1. появилось внешнее по отношению к функции хранилище состояния АЦП-модуля 171 | 2. мы смешали код работы с АЦП с кодом регулятора (инкапсуляция нарушена) 172 | 173 | Если мы АЦП захотим использовать еще в паре мест, то придется тщательно работать над рефакторингом: 174 | 175 | - восстанавливать инкапсуляцию (котлеты отдельно, мухи - отдельно) 176 | - организовать еще одно хранилище - результаты работы АЦП между вызовами надо где-то хранить 177 | 178 | Итого получается у нас при таком подходе недостатки: 179 | 180 | 1. Резко возрастает сложность программ; 181 | 2. Появляются внешние (по отношению к программным сущностям) хранилища данных (где мы их храним: в статических переменных или глобальных - вопрос стиля); 182 | 3. Либо если мы хотим отказаться от внешних хранилищ данных, появляется протокол обмена данными (каждая функция может вернуть данные или признак их отсутствия. Другие функции будут обрабатывать данные или ничего не делать при их отсутствии. 183 | 184 | Если мы решим вывести наш холодильник в интернет, то программирование его в такой парадигме может стать адом. 185 | 186 | Как бороться с этим адом? 187 | 188 | ### Резкое занижение требований к ПО 189 | 190 | Это нормальный метод, если он подходит, то можно на нем остановиться. Помните выше мы сформулировали что можно не разворачивать функцию `get_adc`, а оставить как есть. 191 | 192 | ![И так сойдёт!](https://habrastorage.org/webt/sh/7g/8o/sh7g8ovomjjzyocoeffelrwhw68.jpeg) 193 | 194 | Вполне себе работоспособный подход, для холодильника (без сложного интернета) подойдет вполне. 195 | 196 | На этом пути по мере наращивания сложности проекта обычно приходится наращивать аппаратную сложность, компенсируя ей сложность программную. 197 | 198 | ### Треды и процессы 199 | 200 | В какой-то момент возникает соблазн даже взять и портировать на нашу систему полноценные треды/процессы. Гугля находим массу проектов, [например вот этот](https://github.com/ivanseidel/ArduinoThread). 201 | 202 | [Заглядывая в код треда](https://github.com/ivanseidel/ArduinoThread/blob/master/examples/ControllerInController/ControllerInController.ino#L30) видим все те же функции. Разглядывая код поближе - видим попытку организовать периодический вызов функций через примерно равные интервалы. 203 | 204 | Например 205 | 206 | ```c 207 | loop() { ... } 208 | ``` 209 | 210 | Вызывается максимально часто, а вот `Thread.onRun` можно сконфигурировать чтобы вызывался скажем раз в две секунды. 211 | 212 | То есть человек назвал тредом то что не является тредом в смысле CPU. Увы. 213 | 214 | Реальных тредов в том понимании как их понимают в "большом" мире я не нашел. Буду благодарен, если кто-то подбросит мне ссылку на такой проект. 215 | 216 | Однако в рамках обсуждения тредов и процессов скажу еще что в "большом" мире для решения задач треды обычно не применяют. 217 | Почему? Реализация тредов (процессов) неизбежно приводит нас к введению понятия "квант времени": каждый тред/процесс выполняется определенный квант времени, после чего управление у него отнимается и передается другому треду/процессу. 218 | 219 | Такая многозадачность называется вытесняющей: текущий процесс вытесняется следующим. 220 | 221 | Почему в рамках "больших" проектов треды в основном не применяются? Для того чтобы заставить работать множество тредов на одном CPU необходимо делать очень маленький квант времени. Частота квантования например на современном Linux - равна 1000Гц. То есть если у Вас в системе выполняется 1000 процессов одновременно, то каждый из них будет получать 1 квант времени на 1мс один раз в секунду (это если оверхеда на вытеснение нет), а в реальном мире переключая 1000 процессов хорошо если получится выдать каждому по милисекунде раз в десять секунд. 222 | 223 | Кроме того, поскольку многозадачность вытесняющая, то возникает масса вопросов по межпроцессному взаимодействию. Возились с гонками между прерыванием Arduino и основной программой? И здесь те же проблемы. 224 | 225 | В общем все нагруженные, так называемые HighLoad проекты в "большом" мире делают без массового использования тредов. Проекты HighLoad делают с применением кооперативной, а не вытесняющей многозадачности. 226 | 227 | Чтобы обслужить 1000 клиентов выделив каждому тред - нужно примерно 20 современных компьютеров. При том что на практике достаточно одного сервера чтобы обслужить 50 тыс клиентов. 228 | 229 | 230 | ### Кооперативная многозадачность 231 | 232 | Что это такое? Вот типовой `loop()` проекта Arduino и есть один из вариантов кооперативной многозадачности: переключение к следующей функции не произойдет до тех пор пока предыдущая не завершится. Все функции стараемся писать чтобы они возвращали управление максимально быстро и таким способом решаем задачу. 233 | 234 | Этот способ реализации кооперативной многозадачности можно называть колбечным (или функциональным). 235 | Если обратиться к "большому" миру, там есть проекты для HighLoad построенные исключительно на этом способе, например тот же [Node.JS](https://nodejs.org/en/). 236 | 237 | Если Вы почитаете отзывы о Node.JS, то увидите весь набор от восторженных "наконец я нашел инструмент на котором МОЖНО реализовать мою задачу", до типовых: "callback hell!". 238 | 239 | Существует второй способ реализации кооперативной многозадачности - сопрограммы (корутины, файберы). Идея тут примерно такая же как в традиционных тредах: каждый процесс работает как бы независимо от других. Однако ключевое отличие тут в том, что переключение между процессами производится не по таймеру, а тогда, когда сам процесс решит что ему процессор больше не нужен. 240 | 241 | Какие прелести дает подобный подход? 242 | 243 | 1. Нет ада функций; 244 | 2. Межпроцессное взаимодействие очень простое (ведь если процесс не прервут в критической секции, то и само понятие "критическая секция" нивелируется): мютексы, семафоры - все или резко упрощается или заменяется простыми переменными; 245 | 246 | "Лучшие умы человечества" (ц) разрабатывавшие в прошлом веке для нас язык C и писавшие о нем книги по которым многие из нас учились читать, попытались обобщить все достоинства и тредов и кооперативной нефункциональной многозадачности и в итоге родился язык для HighLoad - Go. 247 | 248 | Но впрочем давайте вернемся из большого мира в наш мир Arduino. Go у нас нет, поэтому будем работать с тем что есть. 249 | 250 | ### Кооперативная многозадачность в Arduino 251 | 252 | Итак нам нужны: 253 | 254 | 1. Возможность создать процесс; 255 | 2. Возможность переключиться на другой процесс из процесса. 256 | 257 | Традиционное название функции переключения между процессами - `yield` или `cede`. 258 | 259 | Вернемся к нашей функции `get_adc`: 260 | 261 | ```c 262 | uint16_t 263 | get_adc(uint8_t input) 264 | { 265 | while (adc_is_busy()); 266 | adc_switch_mux(input); 267 | adc_start(); 268 | while (adc_is_busy()); 269 | return adc_value(); 270 | } 271 | ``` 272 | 273 | Если бы у нас были кооперативные процессы, то в их среде ее бы следовало доработать до следующего вида: 274 | 275 | 276 | ```c 277 | uint16_t 278 | get_adc(uint8_t input) 279 | { 280 | while (adc_is_busy()) 281 | cede(); // пока ждем - пусть другие работают 282 | adc_switch_mux(input); 283 | adc_start(); 284 | while (adc_is_busy()) 285 | cede(); // пока ждем - пусть другие работают 286 | return adc_value(); 287 | } 288 | ``` 289 | 290 | Температурный регулятор выглядел бы так: 291 | 292 | ```c 293 | 294 | void 295 | temperature_regulator(void) { 296 | 297 | for (;;) { 298 | uint16_t current_t = get_adc(INPUT_T); 299 | 300 | if (current_t > t_on) 301 | enable_cooler(); 302 | 303 | if (current_t < t_off) 304 | disable_cooler(); 305 | } 306 | } 307 | 308 | ``` 309 | 310 | Но позвольте! Тут же никаких изменений нет! Скажете Вы. А все изменения вошли в `get_adc`, зачем нам еще? 311 | 312 | Ну и управление светом тоже доработаем: 313 | 314 | 315 | ```c 316 | void 317 | light_control(void) 318 | { 319 | for (;;) { 320 | if (sensor_door() == DOOR_OPEN) 321 | light_on(); 322 | else 323 | light_off(); 324 | 325 | cede(); // сам поработал - дай другому 326 | } 327 | } 328 | ``` 329 | 330 | Красиво? Наглядно? По моему максимально наглядно насколько это возможно. 331 | 332 | Все используемые нами функции подразделяются на два вида: 333 | 334 | 1. вызывающие `yield`/`cede` внутри себя 335 | 2. остальные 336 | 337 | Если в Вашем цикле есть хоть одна функция гарантировано вызывающая `yield`/`cede` внутри себя, то добавлять вызовы `cede()`/`yield()` не нужно. 338 | 339 | В "большом" мире хорошим тоном считается писать вызовы `cede()`/`yield()` внутри так называемых низкоуровневых функций и сводить вызовы этих операторов к минимуму. 340 | 341 | 342 | #### Сколько это будет стоить? 343 | 344 | Поскольку процессы все-таки слабо зависимы друг с другом (хоть и передают друг другу управление), то, очевидно, у каждого процесса должен быть собственный стек. 345 | 346 | Собственный стек - понятие относительно дорогое. Происходит например прерывание. В стек попадает текущий адрес выполнения программы. Так же в стек попадают все регистры которые будут использованы в прерывании. 347 | Вы вызываете функцию: в стек попадают адрес возврата, все ее аргументы и ее временные переменные. 348 | 349 | Я занимался замерами, практика показывает что прерывание обслуживающее например таймер (проинкрементировать счетчик, послать уведомление) занимает на стеке 16-30 байт. Ну и обычный цикл тоже доходит до 16-30 байт глубины. Например наш `temperature_regulator` занимает на стеке: 350 | 351 | 1. всего 2 байта под свою переменную 352 | 2. 2 байта на вызов `get_adc` 353 | 3. 1 байт на аргумент для `get_adc` 354 | 4. 2 байта на вызов `adc_switch_mux` 355 | 5. 1 байт на ее аргумент 356 | 357 | Итого 2 + 2 + 1 + 2 + 1 = 8 байт. Плюс компилятор посохраняет регистры в стек/подостает их оттуда. Умножим на два. Плюс возможный вектор прерывания. Итого получается где-то 50-60 байт на файбер нам было бы достаточно. Сколько файберов можем запустить на Arduino nano328? 5-10 штук со стеком 64-128 и еще останется память для всего остального. 358 | 359 | Расходы памяти на 5-10 полноценных файберов стоят упрощения реализации алгоритмов программы? Ну и поскольку ~~640 килобайт хватит всем~~ 5-10 файберов хватит для того чтобы комфортно написать не только холодильник но и скажем http-клиента займемся написанием такой библиотеки! 360 | 361 | ### Прототип 362 | 363 | Мной реализован [прототип](https://github.com/unera/arduino-fibers) (уже можно пользоваться но API будет расширяться) библиотеки файберов для Arduino. Пока только AVR (есть завязки на `avr-libc`). 364 | 365 | Данная статья пишется в гит того же проекта и оригинал ее (будет дорабатываться) лежит [здесь](https://github.com/unera/arduino-fibers/blob/master/articles/ru/article1.md). 366 | 367 | Библиотека написана на чистом C (не C++). 368 | 369 | 370 | ### API 371 | 372 | Начинается все с инклюда и инициализации: 373 | 374 | ```C 375 | #include 376 | 377 | void 378 | setup() 379 | { 380 | ... 381 | fibers_init(); 382 | } 383 | ``` 384 | 385 | Для создания файбера используется функция `fiber_create`, принимающая ссылку на заранее выделенный для него стек, его размер и ссылку на данные которые будут переданы в файбер: 386 | 387 | ```C 388 | struct fiber * 389 | fiber_create(fiber_cb cb, void *stack, size_t stack_size, void *data); 390 | ``` 391 | 392 | Для того чтобы не мучиться в программе над выделением стека, предусмотрены пара макросов: 393 | 394 | ```C 395 | FIBER_CREATE(__cb, __stack_size, __data); 396 | FIBERV_CREATE(__cb, __stack_size); 397 | ``` 398 | 399 | Которые за Вас выделят память в статической области (обратите внимание: `malloc` не используется, поэтому нельзя применять эти макросы в цикле). 400 | 401 | Или даже если Вы определите заранее какой размер стека будут иметь все Ваши файберы то два макроса: 402 | 403 | ```C 404 | FIBER(__cb, __data); 405 | FIBERV(__cb); 406 | 407 | ``` 408 | для определения файбера. 409 | 410 | Файбер может иметь несколько состояний, вот базовые: 411 | 412 | - 'работает' - обычное состояние 413 | - 'усыплен' - не автопланируется 414 | 415 | Усыпить можно только текущий файбер (то есть сам файбер себя усыпляет, получив ссылку на себя `fiber_current`) вызвав функцию `fiber_schedule`, разбудить - функцией `fiber_wakeup`: 416 | 417 | ```C 418 | 419 | struct fiber *waiter; 420 | 421 | // где-то в файбере 422 | waiter = fiber_current(); 423 | fiber_schedule(); 424 | 425 | // где-то в другом месте, например в прерывании или другом файбере 426 | if (waiter) { 427 | fiber_wakeup(waiter); 428 | waiter = NULL; 429 | } 430 | ``` 431 | 432 | На механизме усыпления/пробуждения можно сокращать использование CPU на циклах ожидания: если Ваш файбер сейчас ждет и есть кому разбудить (например прерывание), то можно его усыпить: другие файберы получат больше процессорного времени на работу. 433 | 434 | Передача управления из процесса в процесс выполняется вызовом `fiber_cede()`: 435 | 436 | ```c 437 | void 438 | light_control(void) 439 | { 440 | for (;;) { 441 | if (sensor_door() == DOOR_OPEN) 442 | light_on(); 443 | else 444 | light_off(); 445 | 446 | fiber_cede(); // сам поработал - дай другому 447 | } 448 | } 449 | ``` 450 | 451 | 452 | #### Тонкости 453 | 454 | - Никакие функции (кроме `fiber_wakeup`) нельзя вызывать из прерываний. Это обстоятельство видимо не преодолеть; 455 | - Нет возможности контроллировать автоматически переполнение стека; 456 | 457 | Соответственно некоторые файберные паттерны из "большого" мира тут применять не получится. Данная библиотека годится под паттерн: на стадии инициализации запускаем N файберов и дальше они работают. 458 | 459 | Паттерн "при событии запускаем файбер", при повторном - еще один - тут увы не проходит. Ресурсов Arduino не хватит. Но можете "разбудить" того кто обрабатывает редкие события. 460 | 461 | 462 | Паттерн: положи ссылку на себя в переменную, засни, а прерывание тебя разбудит - имеет тонкости: прерывание может прийти раньше чем мы заснём. На эту тему решение внедрено, но требует дополнительного разъяснения. Просто исходите из того что так делать МОЖНО. 463 | 464 | #### Что не доделано 465 | 466 | 1. Пока не дооформил как библиотеку Arduino: не смог понять пока как заставить его компилировать (из GUI имеется ввиду, с Makefile-то все просто) C'шные файлы в стандарте C99 (даже заголовки avr-libc предполагают C99). Не хочу оформлять как C++ (потому что не только Arduino в планах); 467 | 2. В большом мире есть `fiber_join`, так и не знаю стоит ли его реализовывать. 468 | 3. Больной вопрос: если программа использует регистровые переменные и при этом переключает файберы, то предсказать поведение невозможно. Сохранять ВСЕ регистры на стеке - дополнительный оверхед. Пока не натыкался на эти проблемы. Возможно придется сделать опцию: будут файберы более требовательны к памяти, но более надёжные; 469 | 470 | 471 | В общем буду рад коментариям, дельным предложениям и pull-реквестам :) 472 | 473 | Пинг-понг на файберах: 474 | 475 | ```C 476 | #define FIBER_STACK_SIZE 64 477 | #include 478 | #include 479 | 480 | void 481 | ping(void *data) 482 | { 483 | printf("ping\n"); 484 | fiber_cede(); 485 | } 486 | 487 | void 488 | pong(void *data) 489 | { 490 | printf("pong\n"); 491 | fiber_cede(); 492 | } 493 | 494 | void 495 | setup(void) 496 | { 497 | fibers_init(); 498 | FIBERV(ping); 499 | FIBERV(pong); 500 | } 501 | 502 | void 503 | loop() 504 | { 505 | fiber_schedule(); // этот файбер нам не нужен 506 | } 507 | ``` 508 | 509 | ### Ссылки 510 | 511 | 1. [Библиотека fiber](https://github.com/unera/arduino-fibers/); 512 | 2. [Эта статья в Git](https://github.com/unera/arduino-fibers/blob/master/articles/ru/article1.md); 513 | 3. [Библиотека Thread, в тексте статьи](https://github.com/ivanseidel/ArduinoThread); 514 | 4. [Многозадачность в терминах википедии](https://ru.wikipedia.org/wiki/%D0%9C%D0%BD%D0%BE%D0%B3%D0%BE%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%BD%D0%BE%D1%81%D1%82%D1%8C); 515 | 5. [Когда-то на HighLoad выступал о аналогичном но в "большом" мире](https://github.com/unera/presentations/blob/master/highload.pdf); 516 | 6. [Node.JS](https://nodejs.org/en/). 517 | --------------------------------------------------------------------------------