SuperTinyKernel™ RTOS 1.08.x
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_strategy_pthreshold.h
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#ifndef STK_STRATEGY_PTHRESHOLD_H_
11#define STK_STRATEGY_PTHRESHOLD_H_
12
18
19#include "stk_common.h"
20
21namespace stk {
22
82template <uint8_t MAX_PRIORITIES, bool TIMESLICE = false>
84{
85public:
98
103 static constexpr Weight MakeWeight(uint8_t priority, uint8_t threshold)
104 {
105 return static_cast<Weight>((static_cast<Weight>(priority) << 8) | static_cast<Weight>(threshold));
106 }
107
112 static constexpr Weight FromThreadX(uint8_t tx_priority, uint8_t tx_threshold)
113 {
114 return MakeWeight(static_cast<uint8_t>((MAX_PRIORITIES - 1U) - tx_priority),
115 static_cast<uint8_t>((MAX_PRIORITIES - 1U) - tx_threshold));
116 }
117
121 {
122 STK_STATIC_ASSERT_DESC(MAX_PRIORITIES <= 32U, "MAX_PRIORITIES exceeds 32-bit bitmap width");
123 }
124
129
134 void AddTask(IKernelTask *task) override
135 {
136 STK_ASSERT(task != nullptr);
137 STK_ASSERT(task->GetHead() == nullptr);
138 STK_ASSERT(GetTaskPriority(task) < MAX_PRIORITIES);
139 STK_ASSERT(GetRawThreshold(task) < MAX_PRIORITIES);
141
142 const Priority prio = GetTaskPriority(task);
143 const bool is_tail = (m_prev[prio] == m_tasks[prio].GetLast());
144
145 AddActive(task);
146
147 // non-sticky mode: if the cursor is on the tail, move it to the new task so that the next
148 // GetNext() returns the head instead of the new task (the new task waits for its turn),
149 // sticky mode (TIMESLICE): the cursor stays on the running task and the new task is
150 // appended behind it, the CPU is not taken over until the rotation is requested
151 if (is_tail && !TIMESLICE)
152 {
153 m_prev[prio] = task;
154 }
155 }
156
159 void RemoveTask(IKernelTask *task) override
160 {
161 STK_ASSERT(task != nullptr);
162 STK_ASSERT(GetSize() != 0U);
163 STK_ASSERT((task->GetHead() == &m_tasks[GetTaskPriority(task)]) || (task->GetHead() == &m_sleep));
164
165 if (task->GetHead() == &m_sleep)
166 {
167 m_sleep.Unlink(task);
168 }
169 else
170 {
171 RemoveActive(task, GetTaskPriority(task));
172 }
173 }
174
188 {
189 IKernelTask *next = nullptr;
190
191 if (m_ready_bitmap != 0U)
192 {
194
195 if (m_held_bitmap != 0U)
196 {
198 STK_ASSERT(holder != nullptr);
199
200 if (hi <= GetTaskThreshold(holder))
201 {
202 next = holder;
203 }
204 }
205
206 if (next == nullptr)
207 {
208 bool advance = true;
209
210 if __stk_constexpr_cpp17 (TIMESLICE)
211 {
212 // sticky: the cursor moves only when a rotation was requested for this level
213 advance = ((m_advance_bitmap & (1U << hi)) != 0U);
214 m_advance_bitmap &= ~(1U << hi);
215 }
216
217 if (advance)
218 {
219 next = (*m_prev[hi]->GetNext());
220 m_prev[hi] = next;
221 }
222 else
223 {
224 next = m_prev[hi];
225 }
226
227 if (GetTaskThreshold(next) > hi)
228 {
229 m_held[hi] = next;
230 m_held_bitmap |= (1U << hi);
231 }
232 }
233 }
234
235 m_running = next;
236
237 return next;
238 }
239
243 {
244 STK_ASSERT(GetSize() != 0U);
245
246 IKernelTask *first_task = nullptr;
247
248 if (m_ready_bitmap == 0U)
249 {
250 first_task = (*m_sleep.GetFirst());
251 }
252 else
253 {
254 first_task = (*m_tasks[GetHighestReadyPriority(m_ready_bitmap)].GetFirst());
255 }
256
257 return first_task;
258 }
259
262 size_t GetSize() const override
263 {
264 size_t total = m_sleep.GetSize();
265 for (Priority i = 0U; i < MAX_PRIORITIES; ++i)
266 {
267 total += m_tasks[i].GetSize();
268 }
269
270 return total;
271 }
272
277 void OnTaskSleep(IKernelTask *task) override
278 {
279 STK_ASSERT(task != nullptr);
280 STK_ASSERT(task->IsSleeping());
281 STK_ASSERT(task->GetHead() == &m_tasks[GetTaskPriority(task)]);
282
283 RemoveActive(task, GetTaskPriority(task));
284 m_sleep.LinkBack(task);
285 }
286
291 void OnTaskWake(IKernelTask *task) override
292 {
293 STK_ASSERT(task != nullptr);
294 STK_ASSERT(!task->IsSleeping());
295 STK_ASSERT(task->GetHead() == &m_sleep);
296
297 m_sleep.Unlink(task);
298 AddActive(task);
299 }
300
305 void OnTaskWeightChange(IKernelTask *task, Weight old_weight) override
306 {
307 const Priority old_prio = DecodePriority(old_weight);
308 const Priority new_prio = GetTaskPriority(task);
309
310 STK_ASSERT(new_prio < MAX_PRIORITIES);
311 STK_ASSERT(old_prio < MAX_PRIORITIES);
312
313 if (task->GetHead() != &m_sleep)
314 {
315 STK_ASSERT(task->GetHead() == &m_tasks[old_prio]);
316
317 if (old_prio == new_prio)
318 {
319 // threshold-only change (IKernelService::SetWeight): no relinking needed
320 UpdateHold(task, new_prio);
321 }
322 else
323 {
324 const bool was_held = (m_held[old_prio] == task);
325
326 RemoveActive(task, old_prio);
327 AddActive(task);
328
329 // migrate the hold to the new level if the task still has a protected band
330 // and the level's (single) hold slot is free
331 if (was_held && (GetTaskThreshold(task) > new_prio) && (m_held[new_prio] == nullptr))
332 {
333 m_held[new_prio] = task;
334 m_held_bitmap |= (1U << new_prio);
335 }
336 }
337 }
338 }
339
354 bool OnTaskYield(IKernelTask *task) override
355 {
356 STK_ASSERT(task != nullptr);
357
358 bool handled = false;
359 const Priority prio = GetTaskPriority(task);
360
361 if ((prio < MAX_PRIORITIES) && (task->GetHead() == &m_tasks[prio]))
362 {
363 if (m_held[prio] == task)
364 {
365 m_held[prio] = nullptr;
366 m_held_bitmap &= ~(1U << prio);
367 }
368
369 m_prev[prio] = task;
370 handled = true;
371
372 if __stk_constexpr_cpp17 (TIMESLICE)
373 {
374 m_advance_bitmap |= (1U << prio);
375 }
376 }
377
378 return handled;
379 }
380
390 bool OnTaskTimeSlice(IKernelTask *task) override
391 {
392 bool rotated = false;
393
394 if __stk_constexpr_cpp17 (TIMESLICE)
395 {
396 STK_ASSERT(task != nullptr);
397
398 const Priority prio = GetTaskPriority(task);
399
400 // rotation requires a same-priority peer: a lone task simply continues (with a fresh slice
401 // reloaded by the kernel), no rotation request is left pending for the level
402 if ((prio < MAX_PRIORITIES) && (task->GetHead() == &m_tasks[prio]) && (m_held[prio] != task) &&
403 (m_tasks[prio].GetSize() > 1U))
404 {
405 m_prev[prio] = task;
406 rotated = true;
407
408 m_advance_bitmap |= (1U << prio);
409 }
410 }
411
412 return rotated;
413 }
414
427 static uint8_t ChangeThreshold(IKernelService &service, TId tid, uint8_t priority, uint8_t threshold)
428 {
429 STK_ASSERT(priority < MAX_PRIORITIES);
430 STK_ASSERT((threshold >= priority) && (threshold < MAX_PRIORITIES));
431
432 return DecodeThreshold(service.SetWeight(tid, MakeWeight(priority, threshold)));
433 }
434
435protected:
437 typedef uint8_t Priority;
438
440 {
441 const Priority prio = GetTaskPriority(task);
442
443 m_tasks[prio].LinkBack(task);
444
445 if (m_tasks[prio].GetSize() == 1U)
446 {
447 m_prev[prio] = task;
448 m_ready_bitmap |= (1U << prio);
449 }
450 }
451
452 void RemoveActive(IKernelTask *task, const Priority prio)
453 {
454 IKernelTask *const next = (*task->GetNext());
455
456 m_tasks[prio].Unlink(task);
457
458 if (m_running == task)
459 {
460 m_running = nullptr;
461 }
462
463 // release preemption-threshold hold
464 if (m_held[prio] == task)
465 {
466 m_held[prio] = nullptr;
467 m_held_bitmap &= ~(1U << prio);
468 }
469
470 if (next == task)
471 {
472 m_prev[prio] = nullptr;
473 m_ready_bitmap &= ~(1U << prio);
474
475 if __stk_constexpr_cpp17 (TIMESLICE)
476 {
477 m_advance_bitmap &= ~(1U << prio);
478 }
479 }
480 else if (m_prev[prio] == task)
481 {
482 // cursor was on the removed task: step back so GetNext() returns its successor;
483 // otherwise leave the rotation untouched
484 m_prev[prio] = (*next->GetPrev());
485
486 if __stk_constexpr_cpp17 (TIMESLICE)
487 {
488 // sticky mode: the successor must run next
489 m_advance_bitmap |= (1U << prio);
490 }
491 }
492 }
493
498 void UpdateHold(IKernelTask *task, const Priority prio)
499 {
500 if (GetTaskThreshold(task) > prio)
501 {
502 if ((task == m_running) && (m_held[prio] == nullptr))
503 {
504 m_held[prio] = task;
505 m_held_bitmap |= (1U << prio);
506 }
507 }
508 else if (m_held[prio] == task)
509 {
510 m_held[prio] = nullptr;
511 m_held_bitmap &= ~(1U << prio);
512 }
513 else
514 {
515 // nothing to update
516 }
517 }
518
520 {
521 return static_cast<Priority>((static_cast<uint32_t>(weight) >> 8) & 0xFFU);
522 }
523
525 {
526 return DecodePriority(task->GetWeight());
527 }
528
530 {
531 return static_cast<Priority>(static_cast<uint32_t>(weight) & 0xFFU);
532 }
533
539
544 {
545 const Priority prio = GetTaskPriority(task);
546 const Priority thr = GetRawThreshold(task);
547
548 return (thr > prio ? thr : prio);
549 }
550
552 {
553 return static_cast<Priority>(31U - CountLeadingZeros(bitmap));
554 }
555
556private:
558
561 uint32_t m_ready_bitmap;
562 uint32_t m_held_bitmap;
565 IKernelTask *m_prev[MAX_PRIORITIES];
566 IKernelTask *m_held[MAX_PRIORITIES];
567};
568
573
579
580} // namespace stk
581
582#endif /* STK_STRATEGY_PTHRESHOLD_H_ */
Contains interface definitions of the library.
#define __stk_forceinline
Forces compiler to always inline the decorated function, regardless of optimisation level.
Definition stk_defs.h:277
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:516
#define __stk_constexpr_cpp17
constexpr definition for C++17 and above.
Definition stk_defs.h:489
#define STK_STATIC_ASSERT_DESC(X, DESC)
Compile-time assertion with a custom error description. Produces a compilation error if X is false.
Definition stk_defs.h:536
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:261
Namespace of STK package.
static uint32_t CountLeadingZeros(const uint32_t value) noexcept
Count leading zeros.
Definition stk_defs.h:783
SwitchStrategyPreemptionThreshold< 32 > SwitchStrategyPT32
SwitchStrategyPreemptionThreshold with 32 priority levels.
SwitchStrategyPreemptionThreshold< 32, true > SwitchStrategyPT32_TS
SwitchStrategyPreemptionThreshold with 32 priority levels and per-task time-slicing,...
Word TId
Task (thread) id.
Definition stk_common.h:165
int32_t Weight
Weight value (aka priority).
Definition stk_common.h:190
Scheduling-strategy-facing interface for a kernel task slot.
Definition stk_common.h:963
virtual Weight GetWeight() const =0
Get static base weight assigned to the task.
virtual Weight GetBaseWeight() const =0
Get the base (configured) weight of the task, ignoring any priority-inheritance boost.
DLHeadType ListHeadType
List head type for IKernelTask elements.
Definition stk_common.h:968
virtual bool IsSleeping() const =0
Check whether the task is currently sleeping.
Interface for a task switching strategy implementation.
Interface for the kernel services exposed to the user processes during run-time when Kernel started s...
virtual Weight SetWeight(TId tid, Weight weight)=0
Change the base weight of the task at run-time (replaces ITask::GetWeight() value).
DLEntryType * GetNext()
Get the next entry in the list.
DLHeadType * GetHead()
Get the list head this entry currently belongs to.
DLEntryType * GetPrev()
Get the previous entry in the list.
Fixed-priority scheduling with Preemption-Threshold (PTS), modelled on Azure RTOS / ThreadX....
bool OnTaskTimeSlice(IKernelTask *task) override
Time slice of the running task expired: rotate to the next task of the same priority.
void AddTask(IKernelTask *task) override
Add task to the runnable set at its priority level.
SwitchStrategyPreemptionThreshold()
Construct an empty strategy.
static __stk_forceinline Priority GetHighestReadyPriority(uint32_t bitmap)
static __stk_forceinline Priority GetTaskThreshold(IKernelTask *task)
static __stk_forceinline Priority GetTaskPriority(IKernelTask *task)
static __stk_forceinline Priority GetRawThreshold(IKernelTask *task)
Configured threshold from the task's base weight (unaffected by priority inheritance).
void UpdateHold(IKernelTask *task, const Priority prio)
Apply a threshold change to a runnable task that stays on the same level.
static uint8_t ChangeThreshold(IKernelService &service, TId tid, uint8_t priority, uint8_t threshold)
Change the preemption-threshold of a task at run-time (tx_thread_preemption_change).
static constexpr Weight FromThreadX(uint8_t tx_priority, uint8_t tx_threshold)
Build a task weight from ThreadX numbers (0 = highest priority).
void OnTaskWake(IKernelTask *task) override
Task became runnable again; re-enters its priority level (not held).
size_t GetSize() const override
Total number of tasks (runnable + sleeping).
void OnTaskSleep(IKernelTask *task) override
Task went to sleep: leaves the runnable set and releases its hold.
void RemoveActive(IKernelTask *task, const Priority prio)
EConfig
Compile-time capability flags reported to the kernel.
IKernelTask * GetNext() override
Select the next task to run.
static __stk_forceinline Priority DecodePriority(Weight weight)
void RemoveTask(IKernelTask *task) override
Remove task from whichever list it occupies (drops its hold if any).
IKernelTask * GetFirst() override
First task in the managed set (highest ready level, else first sleeping).
static __stk_forceinline Priority DecodeThreshold(Weight weight)
STK_NONCOPYABLE_CLASS(SwitchStrategyPreemptionThreshold)
static constexpr Weight MakeWeight(uint8_t priority, uint8_t threshold)
Build a task weight from an STK priority and threshold.
IKernelTask::ListHeadType m_tasks[MAX_PRIORITIES]
bool OnTaskYield(IKernelTask *task) override
Yield: hand the CPU to the next task of the same priority.
STK_VIRT_DTOR ~SwitchStrategyPreemptionThreshold()=default
Destructor.
void OnTaskWeightChange(IKernelTask *task, Weight old_weight) override
Task weight changed (priority inheritance / restore): move to the new level.