mbed os rtos
events/equeue/README.md@1:2b6e8130a0ac, 2018-02-22 (annotated)
- Committer:
- calmantara186
- Date:
- Thu Feb 22 14:05:19 2018 +0000
- Revision:
- 1:2b6e8130a0ac
- Parent:
- 0:f269e3021894
mbed os
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
elessair | 0:f269e3021894 | 1 | ## The equeue library ## |
elessair | 0:f269e3021894 | 2 | |
elessair | 0:f269e3021894 | 3 | The equeue library is designed as a simple but powerful library for scheduling |
elessair | 0:f269e3021894 | 4 | events on composable queues. |
elessair | 0:f269e3021894 | 5 | |
elessair | 0:f269e3021894 | 6 | ``` c |
elessair | 0:f269e3021894 | 7 | #include "equeue.h" |
elessair | 0:f269e3021894 | 8 | #include <stdio.h> |
elessair | 0:f269e3021894 | 9 | |
elessair | 0:f269e3021894 | 10 | int main() { |
elessair | 0:f269e3021894 | 11 | // creates a queue with space for 32 basic events |
elessair | 0:f269e3021894 | 12 | equeue_t queue; |
elessair | 0:f269e3021894 | 13 | equeue_create(&queue, 32*EQUEUE_EVENT_SIZE); |
elessair | 0:f269e3021894 | 14 | |
elessair | 0:f269e3021894 | 15 | // events can be simple callbacks |
elessair | 0:f269e3021894 | 16 | equeue_call(&queue, print, "called immediately"); |
elessair | 0:f269e3021894 | 17 | equeue_call_in(&queue, 2000, print, "called in 2 seconds"); |
elessair | 0:f269e3021894 | 18 | equeue_call_every(&queue, 1000, print, "called every 1 seconds"); |
elessair | 0:f269e3021894 | 19 | |
elessair | 0:f269e3021894 | 20 | // events are executed in equeue_dispatch |
elessair | 0:f269e3021894 | 21 | equeue_dispatch(&queue, 3000); |
elessair | 0:f269e3021894 | 22 | |
elessair | 0:f269e3021894 | 23 | print("called after 3 seconds"); |
elessair | 0:f269e3021894 | 24 | |
elessair | 0:f269e3021894 | 25 | equeue_destroy(&queue); |
elessair | 0:f269e3021894 | 26 | } |
elessair | 0:f269e3021894 | 27 | ``` |
elessair | 0:f269e3021894 | 28 | |
elessair | 0:f269e3021894 | 29 | The equeue library can be used as a normal event loop, or it can be |
elessair | 0:f269e3021894 | 30 | backgrounded on a single hardware timer or even another event loop. It |
elessair | 0:f269e3021894 | 31 | is both thread and irq safe, and provides functions for easily composing |
elessair | 0:f269e3021894 | 32 | multiple queues. |
elessair | 0:f269e3021894 | 33 | |
elessair | 0:f269e3021894 | 34 | The equeue library can act as a drop-in scheduler, provide synchronization |
elessair | 0:f269e3021894 | 35 | between multiple threads, or just act as a mechanism for moving events |
elessair | 0:f269e3021894 | 36 | out of interrupt contexts. |
elessair | 0:f269e3021894 | 37 | |
elessair | 0:f269e3021894 | 38 | ## Documentation ## |
elessair | 0:f269e3021894 | 39 | |
elessair | 0:f269e3021894 | 40 | The in-depth documentation on specific functions can be found in |
elessair | 0:f269e3021894 | 41 | [equeue.h](equeue.h). |
elessair | 0:f269e3021894 | 42 | |
elessair | 0:f269e3021894 | 43 | The core of the equeue library is the `equeue_t` type which represents a |
elessair | 0:f269e3021894 | 44 | single event queue, and the `equeue_dispatch` function which runs the equeue, |
elessair | 0:f269e3021894 | 45 | providing the context for executing events. |
elessair | 0:f269e3021894 | 46 | |
elessair | 0:f269e3021894 | 47 | On top of this, `equeue_call`, `equeue_call_in`, and `equeue_call_every` |
elessair | 0:f269e3021894 | 48 | provide easy methods for posting events to execute in the context of the |
elessair | 0:f269e3021894 | 49 | `equeue_dispatch` function. |
elessair | 0:f269e3021894 | 50 | |
elessair | 0:f269e3021894 | 51 | ``` c |
elessair | 0:f269e3021894 | 52 | #include "equeue.h" |
elessair | 0:f269e3021894 | 53 | #include "game.h" |
elessair | 0:f269e3021894 | 54 | |
elessair | 0:f269e3021894 | 55 | equeue_t queue; |
elessair | 0:f269e3021894 | 56 | struct game game; |
elessair | 0:f269e3021894 | 57 | |
elessair | 0:f269e3021894 | 58 | // button_isr may be in interrupt context |
elessair | 0:f269e3021894 | 59 | void button_isr(void) { |
elessair | 0:f269e3021894 | 60 | equeue_call(&queue, game_button_update, &game); |
elessair | 0:f269e3021894 | 61 | } |
elessair | 0:f269e3021894 | 62 | |
elessair | 0:f269e3021894 | 63 | // a simple user-interface framework |
elessair | 0:f269e3021894 | 64 | int main() { |
elessair | 0:f269e3021894 | 65 | equeue_create(&queue, 4096); |
elessair | 0:f269e3021894 | 66 | game_create(&game); |
elessair | 0:f269e3021894 | 67 | |
elessair | 0:f269e3021894 | 68 | // call game_screen_udpate at 60 Hz |
elessair | 0:f269e3021894 | 69 | equeue_call_every(&queue, 1000/60, game_screen_update, &game); |
elessair | 0:f269e3021894 | 70 | |
elessair | 0:f269e3021894 | 71 | // dispatch forever |
elessair | 0:f269e3021894 | 72 | equeue_dispatch(&queue, -1); |
elessair | 0:f269e3021894 | 73 | } |
elessair | 0:f269e3021894 | 74 | ``` |
elessair | 0:f269e3021894 | 75 | |
elessair | 0:f269e3021894 | 76 | In addition to simple callbacks, an event can be manually allocated with |
elessair | 0:f269e3021894 | 77 | `equeue_alloc` and posted with `equeue_post` to allow passing an arbitrary |
elessair | 0:f269e3021894 | 78 | amount of context to the execution of the event. This memory is allocated out |
elessair | 0:f269e3021894 | 79 | of the equeue's buffer, and dynamic memory can be completely avoided. |
elessair | 0:f269e3021894 | 80 | |
elessair | 0:f269e3021894 | 81 | The equeue allocator is designed to minimize jitter in interrupt contexts as |
elessair | 0:f269e3021894 | 82 | well as avoid memory fragmentation on small devices. The allocator achieves |
elessair | 0:f269e3021894 | 83 | both constant-runtime and zero-fragmentation for fixed-size events, however |
elessair | 0:f269e3021894 | 84 | grows linearly as the quantity of differently-sized allocations increases. |
elessair | 0:f269e3021894 | 85 | |
elessair | 0:f269e3021894 | 86 | ``` c |
elessair | 0:f269e3021894 | 87 | #include "equeue.h" |
elessair | 0:f269e3021894 | 88 | |
elessair | 0:f269e3021894 | 89 | equeue_t queue; |
elessair | 0:f269e3021894 | 90 | |
elessair | 0:f269e3021894 | 91 | // arbitrary data can be moved to a different context |
elessair | 0:f269e3021894 | 92 | int enet_consume(void *buffer, int size) { |
elessair | 0:f269e3021894 | 93 | if (size > 512) { |
elessair | 0:f269e3021894 | 94 | size = 512; |
elessair | 0:f269e3021894 | 95 | } |
elessair | 0:f269e3021894 | 96 | |
elessair | 0:f269e3021894 | 97 | void *data = equeue_alloc(&queue, 512); |
elessair | 0:f269e3021894 | 98 | memcpy(data, buffer, size); |
elessair | 0:f269e3021894 | 99 | equeue_post(&queue, handle_data_elsewhere, data); |
elessair | 0:f269e3021894 | 100 | |
elessair | 0:f269e3021894 | 101 | return size; |
elessair | 0:f269e3021894 | 102 | } |
elessair | 0:f269e3021894 | 103 | ``` |
elessair | 0:f269e3021894 | 104 | |
elessair | 0:f269e3021894 | 105 | Additionally, in-flight events can be cancelled with `equeue_cancel`. Events |
elessair | 0:f269e3021894 | 106 | are given unique ids on post, allowing safe cancellation of expired events. |
elessair | 0:f269e3021894 | 107 | |
elessair | 0:f269e3021894 | 108 | ``` c |
elessair | 0:f269e3021894 | 109 | #include "equeue.h" |
elessair | 0:f269e3021894 | 110 | |
elessair | 0:f269e3021894 | 111 | equeue_t queue; |
elessair | 0:f269e3021894 | 112 | int sonar_value; |
elessair | 0:f269e3021894 | 113 | int sonar_timeout_id; |
elessair | 0:f269e3021894 | 114 | |
elessair | 0:f269e3021894 | 115 | void sonar_isr(int value) { |
elessair | 0:f269e3021894 | 116 | equeue_cancel(&queue, sonar_timeout_id); |
elessair | 0:f269e3021894 | 117 | sonar_value = value; |
elessair | 0:f269e3021894 | 118 | } |
elessair | 0:f269e3021894 | 119 | |
elessair | 0:f269e3021894 | 120 | void sonar_timeout(void *) { |
elessair | 0:f269e3021894 | 121 | sonar_value = -1; |
elessair | 0:f269e3021894 | 122 | } |
elessair | 0:f269e3021894 | 123 | |
elessair | 0:f269e3021894 | 124 | void sonar_read(void) { |
elessair | 0:f269e3021894 | 125 | sonar_timeout_id = equeue_call_in(&queue, 300, sonar_timeout, 0); |
elessair | 0:f269e3021894 | 126 | sonar_start(); |
elessair | 0:f269e3021894 | 127 | } |
elessair | 0:f269e3021894 | 128 | ``` |
elessair | 0:f269e3021894 | 129 | |
elessair | 0:f269e3021894 | 130 | From an architectural standpoint, event queues easily align with module |
elessair | 0:f269e3021894 | 131 | boundaries, where internal state can be implicitly synchronized through |
elessair | 0:f269e3021894 | 132 | event dispatch. |
elessair | 0:f269e3021894 | 133 | |
elessair | 0:f269e3021894 | 134 | On platforms where multiple threads are unavailable, multiple modules |
elessair | 0:f269e3021894 | 135 | can use independent event queues and still be composed through the |
elessair | 0:f269e3021894 | 136 | `equeue_chain` function. |
elessair | 0:f269e3021894 | 137 | |
elessair | 0:f269e3021894 | 138 | ``` c |
elessair | 0:f269e3021894 | 139 | #include "equeue.h" |
elessair | 0:f269e3021894 | 140 | |
elessair | 0:f269e3021894 | 141 | // run a simultaneous localization and mapping loop in one queue |
elessair | 0:f269e3021894 | 142 | struct slam { |
elessair | 0:f269e3021894 | 143 | equeue_t queue; |
elessair | 0:f269e3021894 | 144 | }; |
elessair | 0:f269e3021894 | 145 | |
elessair | 0:f269e3021894 | 146 | void slam_create(struct slam *s, equeue_t *target) { |
elessair | 0:f269e3021894 | 147 | equeue_create(&s->queue, 4096); |
elessair | 0:f269e3021894 | 148 | equeue_chain(&s->queue, target); |
elessair | 0:f269e3021894 | 149 | equeue_call_every(&s->queue, 100, slam_filter); |
elessair | 0:f269e3021894 | 150 | } |
elessair | 0:f269e3021894 | 151 | |
elessair | 0:f269e3021894 | 152 | // run a sonar with it's own queue |
elessair | 0:f269e3021894 | 153 | struct sonar { |
elessair | 0:f269e3021894 | 154 | equeue_t equeue; |
elessair | 0:f269e3021894 | 155 | struct slam *slam; |
elessair | 0:f269e3021894 | 156 | }; |
elessair | 0:f269e3021894 | 157 | |
elessair | 0:f269e3021894 | 158 | void sonar_create(struct sonar *s, equeue_t *target) { |
elessair | 0:f269e3021894 | 159 | equeue_create(&s->queue, 64); |
elessair | 0:f269e3021894 | 160 | equeue_chain(&s->queue, target); |
elessair | 0:f269e3021894 | 161 | equeue_call_in(&s->queue, 5, sonar_update, s); |
elessair | 0:f269e3021894 | 162 | } |
elessair | 0:f269e3021894 | 163 | |
elessair | 0:f269e3021894 | 164 | // all of the above queues can be combined into a single thread of execution |
elessair | 0:f269e3021894 | 165 | int main() { |
elessair | 0:f269e3021894 | 166 | equeue_t queue; |
elessair | 0:f269e3021894 | 167 | equeue_create(&queue, 1024); |
elessair | 0:f269e3021894 | 168 | |
elessair | 0:f269e3021894 | 169 | struct sonar s1, s2, s3; |
elessair | 0:f269e3021894 | 170 | sonar_create(&s1, &queue); |
elessair | 0:f269e3021894 | 171 | sonar_create(&s2, &queue); |
elessair | 0:f269e3021894 | 172 | sonar_create(&s3, &queue); |
elessair | 0:f269e3021894 | 173 | |
elessair | 0:f269e3021894 | 174 | struct slam slam; |
elessair | 0:f269e3021894 | 175 | slam_create(&slam, &queue); |
elessair | 0:f269e3021894 | 176 | |
elessair | 0:f269e3021894 | 177 | // dispatches events from all of the modules |
elessair | 0:f269e3021894 | 178 | equeue_dispatch(&queue, -1); |
elessair | 0:f269e3021894 | 179 | } |
elessair | 0:f269e3021894 | 180 | ``` |
elessair | 0:f269e3021894 | 181 | |
elessair | 0:f269e3021894 | 182 | ## Platform ## |
elessair | 0:f269e3021894 | 183 | |
elessair | 0:f269e3021894 | 184 | The equeue library has a minimal porting layer that is flexible depending |
elessair | 0:f269e3021894 | 185 | on the requirements of the underlying platform. Platform specific declarations |
elessair | 0:f269e3021894 | 186 | and more information can be found in [equeue_platform.h](equeue_platform.h). |
elessair | 0:f269e3021894 | 187 | |
elessair | 0:f269e3021894 | 188 | ## Tests ## |
elessair | 0:f269e3021894 | 189 | |
elessair | 0:f269e3021894 | 190 | The equeue library uses a set of local tests based on the posix implementation. |
elessair | 0:f269e3021894 | 191 | |
elessair | 0:f269e3021894 | 192 | Runtime tests are located in [tests.c](tests/tests.c): |
elessair | 0:f269e3021894 | 193 | |
elessair | 0:f269e3021894 | 194 | ``` bash |
elessair | 0:f269e3021894 | 195 | make test |
elessair | 0:f269e3021894 | 196 | ``` |
elessair | 0:f269e3021894 | 197 | |
elessair | 0:f269e3021894 | 198 | Profiling tests based on rdtsc are located in [prof.c](tests/prof.c): |
elessair | 0:f269e3021894 | 199 | |
elessair | 0:f269e3021894 | 200 | ``` bash |
elessair | 0:f269e3021894 | 201 | make prof |
elessair | 0:f269e3021894 | 202 | ``` |
elessair | 0:f269e3021894 | 203 | |
elessair | 0:f269e3021894 | 204 | To make profiling results more tangible, the profiler also supports percentage |
elessair | 0:f269e3021894 | 205 | comparison with previous runs: |
elessair | 0:f269e3021894 | 206 | ``` bash |
elessair | 0:f269e3021894 | 207 | make prof | tee results.txt |
elessair | 0:f269e3021894 | 208 | cat results.txt | make prof |
elessair | 0:f269e3021894 | 209 | ``` |
elessair | 0:f269e3021894 | 210 |