forked from includeos/IncludeOS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtimers.cpp
More file actions
253 lines (216 loc) · 6.64 KB
/
timers.cpp
File metadata and controls
253 lines (216 loc) · 6.64 KB
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#include <kernel/timers.hpp>
#include <map>
#include <vector>
#include <kernel/os.hpp>
#include <statman>
using namespace std::chrono;
typedef Timers::id_t id_t;
typedef Timers::duration_t duration_t;
typedef Timers::handler_t handler_t;
static void sched_timer(duration_t when, id_t id);
struct Timer
{
Timer(duration_t p, handler_t cb)
: period(p), callback(cb), deferred_destruct(false) {}
bool is_alive() const noexcept {
return deferred_destruct == false;
}
bool is_oneshot() const noexcept {
return period.count() == 0;
}
void reset() {
callback.reset();
deferred_destruct = false;
}
duration_t period;
handler_t callback;
bool deferred_destruct = false;
};
/**
* 1. There are no restrictions on when timers can be started or stopped
* 2. A period of 0 means start a one-shot timer
* 3. Each timer is a separate object living in a "fixed" vector
* 4. A dead timer is simply a timer which has its handler reset, as well as
* having been removed from schedule
* 5. No timer may be scheduled more than once at a time, as that will needlessly
* inflate the schedule container, as well as complicate stopping timers
* 6. Free timer IDs are retrieved from a stack of free timer IDs (or through
* expanding the "fixed" vector)
**/
static bool signal_ready = false;
static bool is_running = false;
static uint32_t dead_timers = 0;
static Timers::start_func_t arch_start_func;
static Timers::stop_func_t arch_stop_func;
static std::vector<Timer> timers;
static std::vector<id_t> free_timers;
// timers sorted by timestamp
static std::multimap<duration_t, id_t> scheduled;
/** Stats */
static uint64_t& oneshot_started_{Statman::get().create(Stat::UINT64, "timers.oneshot_started").get_uint64()};
static uint64_t& oneshot_stopped_{Statman::get().create(Stat::UINT64, "timers.oneshot_stopped").get_uint64()};
static uint32_t& periodic_started_{Statman::get().create(Stat::UINT32, "timers.periodic_started").get_uint32()};
static uint32_t& periodic_stopped_{Statman::get().create(Stat::UINT32, "timers.periodic_stopped").get_uint32()};
void Timers::init(const start_func_t& start, const stop_func_t& stop)
{
// architecture specific start and stop functions
arch_start_func = start;
arch_stop_func = stop;
}
void Timers::ready()
{
assert(signal_ready == false);
signal_ready = true;
// begin processing timers if any are queued
if (is_running == false) {
timers_handler();
}
}
id_t Timers::periodic(duration_t when, duration_t period, const handler_t& handler)
{
id_t id;
if (UNLIKELY(free_timers.empty())) {
if (LIKELY(dead_timers)) {
// look for dead timer
auto it = scheduled.begin();
while (it != scheduled.end()) {
// take over this timer, if dead
id_t id = it->second;
if (timers[id].deferred_destruct) {
dead_timers--;
// remove from schedule
scheduled.erase(it);
// reset timer
timers[id].reset();
// reuse timer
new (&timers[id]) Timer(period, handler);
sched_timer(when, id);
// Stat increment timer started
if(timers[id].is_oneshot())
oneshot_started_++;
else
periodic_started_++;
return id;
}
++it;
}
}
id = timers.size();
// occupy new slot
timers.emplace_back(period, handler);
}
else {
// get free timer slot
id = free_timers.back();
free_timers.pop_back();
// occupy free slot
new (&timers[id]) Timer(period, handler);
}
// immediately schedule timer
sched_timer(when, id);
// Stat increment timer started
if(timers[id].is_oneshot())
oneshot_started_++;
else
periodic_started_++;
return id;
}
void Timers::stop(id_t id)
{
if (LIKELY(timers[id].deferred_destruct == false)) {
// mark as dead already
timers[id].deferred_destruct = true;
// free resources immediately
timers[id].callback.reset();
dead_timers++;
if (timers[id].is_oneshot())
oneshot_stopped_++;
else
periodic_stopped_++;
}
}
size_t Timers::active()
{
return scheduled.size();
}
/// time functions ///
inline std::chrono::microseconds now() noexcept
{
return microseconds(OS::micros_since_boot());
}
/// scheduling ///
void Timers::timers_handler()
{
// assume the hardware timer called this function
is_running = false;
while (LIKELY(!scheduled.empty()))
{
auto it = scheduled.begin();
auto when = it->first;
id_t id = it->second;
// remove dead timers
if (timers[id].deferred_destruct) {
dead_timers--;
// remove from schedule
scheduled.erase(it);
// delete timer
timers[id].reset();
free_timers.push_back(id);
}
else
{
auto ts_now = now();
if (ts_now >= when) {
// erase immediately
scheduled.erase(it);
// call the users callback function
timers[id].callback(id);
// if the timers struct was modified in callback, eg. due to
// creating a timer, then the timer reference below would have
// been invalidated, hence why its BELOW, AND MUST STAY THERE
auto& timer = timers[id];
// oneshot timers are automatically freed
if (timer.deferred_destruct || timer.is_oneshot())
{
timer.reset();
if (timer.deferred_destruct) dead_timers--;
free_timers.push_back(id);
}
else if (timer.is_oneshot() == false)
{
// if the timer is recurring, we will simply reschedule it
// NOTE: we are carefully using (when + period) to avoid drift
scheduled.emplace(std::piecewise_construct,
std::forward_as_tuple(when + timer.period),
std::forward_as_tuple(id));
}
} else {
// not yet time, so schedule it for later
is_running = true;
arch_start_func(when - ts_now);
// exit early, because we have nothing more to do,
// and there is a deferred handler
return;
}
}
}
// stop hardware timer, since no timers are enabled
arch_stop_func();
}
static void sched_timer(duration_t when, id_t id)
{
scheduled.emplace(std::piecewise_construct,
std::forward_as_tuple(now() + when),
std::forward_as_tuple(id));
// dont start any hardware until after calibration
if (UNLIKELY(!signal_ready)) return;
// if the hardware timer is not running, try starting it
if (UNLIKELY(is_running == false)) {
Timers::timers_handler();
return;
}
// if the scheduled timer is the new front, restart timer
auto it = scheduled.begin();
if (it->second == id)
Timers::timers_handler();
}