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_c_pthread.cpp
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#include <cstddef> // for std::size_t
11#include <cstdint>
12#include <new> // placement new
13#include <cerrno>
14
15#include "stk.h" // C++ API: ITask / IKernel / hw::* / Sleep / Yield / GetTimeNowMs
16#include "sync/stk_sync.h" // C++ API: sync::Mutex / ConditionVariable / RWMutex / SpinLock / Barrier / Event / Semaphore
17
18#include "stk_c.h"
19#include "stk_c_memory.h"
20#include "stk_c_pthread.h"
21
22#if !STK_TLS
23 #error "stk_c_pthread.cpp requires STK_TLS (pthread_self()/pthread_getspecific() are built on the per-task TLS slot)."
24#endif
25
26/*
27 * MISRA C++ notes for this translation unit:
28 * - Every function has a single point of exit (Rule 6-6-5). Error paths are expressed with
29 * a 'result' variable and nested if/else, never early return.
30 * - No 'continue' / 'break' is used in loops (Rules 6-6-3, 6-6-4): loop exit conditions are
31 * expressed in the loop condition itself.
32 * - Every 'else if' chain is terminated by an 'else' (Rule 6-4-2).
33 * - Objects and functions private to this file have internal linkage (Rule 3-3-1).
34 * - Remaining, intentional deviations:
35 * Rule 3-9-2 : pthread API signatures mandate the use of 'int' / 'unsigned int'.
36 * Rule 5-2-5 : const_cast in pthread_setspecific() (POSIX takes 'const void *').
37 * Rule 5-2-7/8/9 : reinterpret_cast in ToKernel() and the stack pool bootstrap.
38 * Rule 7-3-4 : using-directives (namespace stk / stk::pthread).
39 * Rule 18-4-1 : malloc()/free() for non-default stack sizes, placement new for sync objects.
40 */
41
42// RAII critical section (same session semantics as stk_critical_section_enter_ex(DEFAULT)).
44
45// Private malloc/free declarations (mirrors stk_c_memory.cpp): overcomes absence of
46// declarations under a -ffreestanding compiler flag. Only used for the "unusual"
47// stack-size fallback path; the common-size path is served entirely from a static
48// BlockMemoryPool with zero heap use.
49extern "C" void *malloc(std::size_t size);
50extern "C" void free(void *ptr);
51
52// =============================================================================
53// Internal state
54// =============================================================================
55
56// Opaque type forward-declared in stk_c_pthread.h as
57// `typedef struct pthread_stk_ctrl_t *pthread_t;`. Must live at global scope
58// (matching the header's forward declaration) so that pthread_t in caller code and
59// this definition refer to the exact same type.
61
62using namespace stk;
63
64namespace stk {
65namespace pthread {
66
67// -----------------------------------------------------------------------------
68// PthreadTask: the kernel-facing task object embedded in every thread control block.
69// -----------------------------------------------------------------------------
70class PthreadTask final : public ITask
71{
72public:
73 explicit PthreadTask() : m_ctrl(nullptr), m_stack(nullptr), m_stack_size(0U), m_mode(ACCESS_USER)
74 {}
75
80
81 void Initialize(pthread_stk_ctrl_t *ctrl, stk_word_t *stack, size_t stack_size, EAccessMode mode)
82 {
83 m_ctrl = ctrl;
84 m_stack = stack;
85 m_stack_size = stack_size;
86 m_mode = mode;
87 }
88
89 // ITask
90 EAccessMode GetAccessMode() const override { return m_mode; }
91 int32_t GetWeight() const override { return DEFAULT_WEIGHT; }
92 const char *GetTraceName() const override { return "pthread"; }
93
94 // IStackMemory
95 const Word *GetStack() const override { return m_stack; }
96 size_t GetStackSize() const override { return m_stack_size; }
97
98private:
100
101 void Run() override; // thread trampoline, see below
102 void OnExit() override; // sets pthread_stk_ctrl_t::exited
103
108};
109
110} // namespace pthread
111} // namespace stk
112
114{
116 {
117 Reset();
118 }
119
120 // Returns the block to its pristine state. The embedded task object is NOT touched
121 // (it is non-copyable and is re-armed via PthreadTask::Initialize() on creation).
122 void Reset()
123 {
124 busy = false;
125 finished = false;
126 detached = false;
127 joined = false;
128 reclaimed = false;
129 exited = false;
130 adopted = false;
131 start_routine = nullptr;
132 arg = nullptr;
133 retval = nullptr;
134 stack = nullptr;
135 stack_owned = false;
136 stack_from_pool = false;
137 done_event = nullptr;
138
139 for (std::size_t i = 0U; i < STK_C_PTHREAD_KEYS_MAX; ++i)
140 {
141 tsd[i] = nullptr;
142 }
143 }
144
145 bool busy;
146 bool finished; // start_routine returned / pthread_exit() called
148 bool joined;
149 bool reclaimed; // resources already freed or handed to the reaper
150 bool exited; // kernel invoked PthreadTask::OnExit(): task is gone, stack no longer in use
151 bool adopted; // control block wraps a foreign task (e.g. bootstrap task), see CurrentCtrlOrAdopt()
152
154 void *(*start_routine)(void *);
155 void *arg;
156 void *retval;
157
159 bool stack_owned; // false => caller-supplied via pthread_attr_setstack()
160 bool stack_from_pool; // true => came from s_StackPool, else malloc()
161
162 stk::sync::Event *done_event; // constructed in place in done_event_buf by pthread_create()
163 struct {
164 alignas(stk::sync::Event) uint8_t data[sizeof(stk::sync::Event)];
166
167 void *tsd[STK_C_PTHREAD_KEYS_MAX]; // pthread_setspecific()/getspecific() storage
168};
169
170namespace stk {
171namespace pthread {
172
174
175constexpr std::size_t kDefaultStackBytes =
176 static_cast<std::size_t>(STK_C_PTHREAD_DEFAULT_STACK_WORDS) * sizeof(stk_word_t);
177
178// pthread_once_t::__state values
179constexpr int kOnceRunning = 1;
180constexpr int kOnceDone = 2;
181
183
184// -----------------------------------------------------------------------------
185// Small helpers
186// -----------------------------------------------------------------------------
187
188// Same cast the C layer (stk_c.cpp) uses: an stk_kernel_t* *is* the IKernel object.
189static inline IKernel *ToKernel(stk_kernel_t *k)
190{
191 return reinterpret_cast<IKernel *>(reinterpret_cast<void *>(k));
192}
193
194// Acquire/release accessors used by every lazy-init fast path below. The unlocked
195// "is it initialized yet?" check of a double-checked pattern must be an *acquire*
196// load, and the publishing store a *release* store; otherwise on a multi-core
197// configuration (STK_C_CPU_COUNT > 1) another core can observe the non-NULL handle
198// before it observes the object the handle points to.
199#if defined(__GNUC__) || defined(__clang__)
200template <typename T> static inline T LoadAcquire(T *p) { return __atomic_load_n(p, __ATOMIC_ACQUIRE); }
201template <typename T> static inline void StoreRelease(T *p, T v) { __atomic_store_n(p, v, __ATOMIC_RELEASE); }
202#elif defined(__ICCARM__)
203// IAR: no generic __atomic_load_n/__atomic_store_n for plain scalars (xatomic.h only offers
204// std::atomic internals and atomic_flag), so build acquire/release from a volatile access
205// plus a full data memory barrier. __DMB() is also a compiler barrier.
206template <typename T> static inline T LoadAcquire(T *p)
207{
208 const T v = *static_cast<volatile T *>(p);
209 __stk_dmb(); // acquire: later accesses cannot be reordered before this load
210 return v;
211}
212template <typename T> static inline void StoreRelease(T *p, T v)
213{
214 __stk_dmb(); // release: earlier accesses are visible before this store
215 *static_cast<volatile T *>(p) = v;
216}
217#else
218// Fallback for compilers without __atomic_*: correct on single-core targets only.
219template <typename T> static inline T LoadAcquire(T *p) { return *static_cast<volatile T *>(p); }
220template <typename T> static inline void StoreRelease(T *p, T v) { *static_cast<volatile T *>(p) = v; }
221#endif
222
223// Lazily constructs the object for statically-initialized pthread_* variables and returns it.
224// 'slot' (the pthread object's __handle) doubles as the "constructed" flag.
225template <typename T, typename Mem>
226static T *EnsureObject(void **slot, Mem &mem)
227{
228 void *h = LoadAcquire(slot);
229 if (h == nullptr)
230 {
231 const CsLock cs_;
232
233 h = *slot; // re-check under the lock
234 if (h == nullptr)
235 {
236 h = ConstructIn<T>(mem);
237 StoreRelease(slot, h);
238 }
239 }
240
241 return static_cast<T *>(h);
242}
243
244// Typed views of an already-constructed object (NULL-checks are the callers' job).
245static inline sync::Mutex *HandleOf(pthread_mutex_t *o) { return static_cast<sync::Mutex *>(o->__handle); }
246static inline sync::Mutex *HandleOf(pthread_once_t *o) { return static_cast<sync::Mutex *>(o->__handle); }
247static inline sync::ConditionVariable *HandleOf(pthread_cond_t *o) { return static_cast<sync::ConditionVariable *>(o->__handle); }
248static inline sync::RWMutex *HandleOf(pthread_rwlock_t *o) { return static_cast<sync::RWMutex *>(o->__handle); }
249static inline hw::SpinLock *HandleOf(pthread_spinlock_t *o) { return static_cast<hw::SpinLock *>(o->__handle); }
250static inline sync::Barrier *HandleOf(pthread_barrier_t *o) { return static_cast<sync::Barrier *>(o->__handle); }
251
252// -----------------------------------------------------------------------------
253// Local functions
254// -----------------------------------------------------------------------------
255static void RunKeyDestructors(ThreadCtrl *ctrl);
256
257// -----------------------------------------------------------------------------
258// Bound kernel
259// -----------------------------------------------------------------------------
260static stk_kernel_t *s_BoundKernel = nullptr;
261
262// -----------------------------------------------------------------------------
263// Static stack pool (zero heap) for default-size stacks
264// -----------------------------------------------------------------------------
266static stk_blockpool_t *s_StackPool = nullptr;
267
269{
271 if (pool == nullptr)
272 {
273 const CsLock cs_;
274
275 pool = s_StackPool;
276 if (pool == nullptr)
277 {
281 reinterpret_cast<uint8_t *>(s_PthreadStackStorage),
282 sizeof(s_PthreadStackStorage),
283 "pthread_stacks");
285 }
286 }
287
288 return pool;
289}
290
291// -----------------------------------------------------------------------------
292// Reaper task: reclaims detached-thread resources once they finish.
293// -----------------------------------------------------------------------------
295static std::size_t s_ReapHead = 0U;
296static std::size_t s_ReapCount = 0U;
297
298static sync::Semaphore *s_ReapSem = nullptr;
299static struct {
300 alignas(sync::Semaphore) uint8_t data[sizeof(sync::Semaphore)];
302static stk_task_t *s_ReaperTask = nullptr;
304
305static void ReleaseThreadResources(ThreadCtrl *ctrl);
306static void WaitForTaskExit(ThreadCtrl *ctrl);
307static void ReaperEntry(void *arg);
308
309static void EnsureReaper()
310{
311 if (s_ReaperTask == nullptr)
312 {
313 STK_C_ASSERT(s_BoundKernel != nullptr);
314
315 if (s_ReapSem == nullptr)
316 {
318 }
319
322 if (s_ReaperTask != nullptr)
323 {
324 stk_task_set_name(s_ReaperTask, "pthread_reaper");
326 }
327 }
328}
329
330static void EnqueueReap(ThreadCtrl *ctrl)
331{
332 STK_C_ASSERT(s_ReaperTask != nullptr);
333
334 const CsLock cs_;
335
338 {
339 const std::size_t tail = (s_ReapHead + s_ReapCount) % STK_C_PTHREAD_MAX_THREADS;
340 s_ReapQueue[tail] = ctrl;
341 ++s_ReapCount;
342 }
343}
344
346{
347 ThreadCtrl *result = nullptr;
348
349 {
350 const CsLock cs_;
351
352 if (s_ReapCount > 0U)
353 {
354 result = s_ReapQueue[s_ReapHead];
356 --s_ReapCount;
357 }
358 }
359
360 return result;
361}
362
363static void ReaperEntry(void * /*arg*/)
364{
365 for (;;)
366 {
367 STK_C_ASSERT(s_ReapSem != nullptr);
368 (void)s_ReapSem->Wait(); // WAIT_INFINITE
369
370 ThreadCtrl *const ctrl = DequeueReap();
371 if (ctrl != nullptr)
372 {
373 WaitForTaskExit(ctrl);
375 }
376 }
377}
378
379// Exactly-once trigger: called both when a detached thread finishes and when an
380// already-running thread is detached. Whichever observes "finished && detached"
381// first hands the thread off to the reaper.
382static void MaybeReap(ThreadCtrl *ctrl)
383{
384 bool do_reap = false;
385
386 {
387 const CsLock cs_;
388
389 if (ctrl->finished && ctrl->detached && !ctrl->reclaimed)
390 {
391 ctrl->reclaimed = true;
392 do_reap = true;
393 }
394 }
395
396 if (do_reap)
397 {
398 EnqueueReap(ctrl);
399 s_ReapSem->Signal();
400 }
401}
402
403// -----------------------------------------------------------------------------
404// Slot management
405// -----------------------------------------------------------------------------
407{
408 ThreadCtrl *result = nullptr;
409
410 {
411 const CsLock cs_;
412
413 for (std::size_t i = 0U; (result == nullptr) && (i < STK_C_PTHREAD_MAX_THREADS); ++i)
414 {
415 if (!s_Threads[i].busy)
416 {
417 s_Threads[i].Reset();
418 s_Threads[i].busy = true;
419 result = &s_Threads[i];
420 }
421 }
422 }
423
424 return result;
425}
426
427// Wait until the kernel has invoked PthreadTask::OnExit() for this thread, i.e. the task
428// has been fully torn down and its stack is no longer in use, so it is safe to free
429// the stack and recycle the control block. The flag is exact: no task-list scan, no
430// large on-stack buffer, and no ABA hazard from recycled task slots.
431//
432// Waiting is a 1 ms sleep-poll on purpose: OnExit() runs in kernel context where
433// blocking/signalling primitives must not be used, and the wait is normally a few ticks.
434static void WaitForTaskExit(ThreadCtrl *ctrl)
435{
436 bool exited = false;
437
438 while (!exited)
439 {
440 {
441 const CsLock cs_;
442
443 exited = ctrl->exited;
444 }
445
446 if (!exited)
447 {
448 SleepMs(1);
449 }
450 }
451}
452
454{
455 if (ctrl->done_event != nullptr)
456 {
457 ctrl->done_event->~Event();
458 ctrl->done_event = nullptr;
459 }
460
461 if (ctrl->stack_owned && (ctrl->stack != nullptr))
462 {
463 if (ctrl->stack_from_pool)
464 {
466 }
467 else
468 {
469 free(ctrl->stack);
470 }
471 }
472 ctrl->stack = nullptr;
473
474 {
475 const CsLock cs_;
476
477 ctrl->busy = false;
478 }
479}
480
481// Provides the thread stack: either the caller-supplied one, a pool block (default size),
482// or a malloc() block. Returns true if ctrl->stack is valid afterwards.
483static bool AllocateStack(ThreadCtrl *ctrl, const pthread_attr_t *attr, std::size_t stack_bytes)
484{
485 if ((attr != nullptr) && (attr->__ext_stack != nullptr))
486 {
487 ctrl->stack = attr->__ext_stack;
488 ctrl->stack_owned = false;
489 ctrl->stack_from_pool = false;
490 }
491 else
492 {
493 void *blk = nullptr;
494
495 if (stack_bytes == kDefaultStackBytes)
496 {
497 stk_blockpool_t *const pool = EnsureStackPool();
498 if (pool != nullptr)
499 {
500 blk = stk_blockpool_try_alloc(pool);
501 }
502 }
503
504 if (blk != nullptr)
505 {
506 ctrl->stack = static_cast<stk_word_t *>(blk);
507 ctrl->stack_owned = true;
508 ctrl->stack_from_pool = true;
509 }
510 else
511 {
512 ctrl->stack = static_cast<stk_word_t *>(malloc(stack_bytes));
513 ctrl->stack_owned = true;
514 ctrl->stack_from_pool = false;
515 }
516 }
517
518 return (ctrl->stack != nullptr);
519}
520
521// -----------------------------------------------------------------------------
522// Thread trampoline (PthreadTask::Run) and exit notification (PthreadTask::OnExit)
523// -----------------------------------------------------------------------------
525{
526 ThreadCtrl *const ctrl = m_ctrl;
527
528 hw::SetTlsPtr(static_cast<void *>(ctrl));
529
530 void *const result = ctrl->start_routine(ctrl->arg);
531
532 RunKeyDestructors(ctrl);
533
534 {
535 const CsLock cs_;
536
537 ctrl->retval = result;
538 ctrl->finished = true;
539 }
540
541 (void)ctrl->done_event->Set();
542 MaybeReap(ctrl);
543
544 // Natural return: STK tears the task down and calls OnExit(). A joiner (or the
545 // reaper, for detached threads) reclaims *our* resources (stack, control block)
546 // only after OnExit() has set ctrl->exited.
547}
548
550{
551 // Kernel context: no blocking calls. This is the last time the kernel touches
552 // this task object; after the flag is set the control block may be recycled.
553 const CsLock cs_;
554 m_ctrl->exited = true;
555}
556
557// -----------------------------------------------------------------------------
558// Absolute-deadline -> relative-timeout helper
559//
560// STK has no wall clock; abstime is interpreted as a point on the same timeline as
561// stk::GetTimeNowMs() (see the "timedwait limitation" note in stk_c_pthread.h).
562// -----------------------------------------------------------------------------
563static Timeout TimespecToRelativeTimeout(const struct timespec *abstime)
564{
565 const stk_time_t now_ms = static_cast<stk_time_t>(GetTimeNowMs());
566 const stk_time_t target_ms = (static_cast<stk_time_t>(abstime->tv_sec) * 1000LL) +
567 (static_cast<stk_time_t>(abstime->tv_nsec) / 1000000LL);
568
569 stk_time_t rel_ms = target_ms - now_ms;
570 if (rel_ms < 0LL)
571 {
572 rel_ms = 0LL;
573 }
574 else
575 if (rel_ms > static_cast<stk_time_t>(INT32_MAX))
576 {
577 rel_ms = static_cast<stk_time_t>(INT32_MAX);
578 }
579
580 return GetTicksFromMsClampedToTimeout(static_cast<Timeout>(rel_ms));
581}
582
587
588static inline bool IsMutexOwner(const sync::Mutex *m)
589{
590 return (m->GetOwner() == IKernelService::GetInstance()->GetTid());
591}
592
593static inline bool IsValidMutexType(int type)
594{
595 return (type == PTHREAD_MUTEX_NORMAL) ||
596 (type == PTHREAD_MUTEX_ERRORCHECK) ||
597 (type == PTHREAD_MUTEX_RECURSIVE);
598}
599
600// Checks that must run before locking a mutex the caller may already own. Safe without a
601// critical section: only the calling thread can make "owner == self" true or false, and
602// the recursion counter is stable while the caller owns the lock.
603// Returns 0 if the lock attempt may proceed, otherwise the errno to return;
604// errorcheck_code is the error for relocking an ERRORCHECK mutex (EDEADLK or EBUSY).
605static int PreLockCheck(const pthread_mutex_t *pm, const sync::Mutex *m, int errorcheck_code)
606{
607 int result = 0;
608
609 if (IsMutexOwner(m))
610 {
612 {
613 result = errorcheck_code;
614 }
615 else
616 {
617 result = (m->GetRecursionCount() >= sync::Mutex::RECURSION_MAX) ? EAGAIN : 0;
618 }
619 }
620
621 return result;
622}
623
628
633
638
639// -----------------------------------------------------------------------------
640// Current-thread identity (TLS), including adoption of foreign tasks
641// -----------------------------------------------------------------------------
642
643// Control block of the calling task if it already has one (pthread_create()'d, or
644// previously adopted); NULL otherwise. Never allocates.
646{
647 return hw::GetTlsPtr<ThreadCtrl>();
648}
649
650// Gives the calling task a pthread identity on first use. This is what makes
651// pthread_self()/pthread_setspecific() work from tasks the shim did not create
652// (typically the application's bootstrap task). The block comes from the same
653// s_Threads[] pool, so each adopted task permanently consumes one
654// STK_C_PTHREAD_MAX_THREADS slot. An adopted thread is never joinable (it is marked
655// detached), is never reaped, and its TSD destructors only run if it calls
656// pthread_exit(). Only the calling task touches its own TLS slot, so no locking is needed.
658{
659 ThreadCtrl *ctrl = CurrentCtrl();
660 if (ctrl == nullptr)
661 {
662 ctrl = AcquireThreadSlot();
663 if (ctrl != nullptr)
664 {
665 ctrl->adopted = true;
666 ctrl->detached = true; // not joinable, never handed to the reaper
667 hw::SetTlsPtr(static_cast<void *>(ctrl));
668 }
669 }
670
671 return ctrl;
672}
673
674// -----------------------------------------------------------------------------
675// Thread-specific data (keys)
676// -----------------------------------------------------------------------------
678{
679 bool used;
680 void (*destructor)(void *);
681};
682
684
685// True if 'key' is in range and currently allocated. The range check short-circuits
686// the array access.
687static inline bool IsKeyUsed(pthread_key_t key)
688{
689 return (key < STK_C_PTHREAD_KEYS_MAX) && s_Keys[key].used;
690}
691
692// Runs at thread termination (natural return or pthread_exit()) for a thread that
693// was created via pthread_create() - see the "thread-specific data limitation"
694// note in stk_c_pthread.h. Mirrors POSIX: each pass calls the destructor for every
695// key whose value is currently non-NULL (clearing the value first, so a
696// destructor that reads it back via pthread_getspecific() sees NULL), and repeats
697// while any destructor call left a *new* non-NULL value, up to
698// PTHREAD_DESTRUCTOR_ITERATIONS passes.
700{
701 bool more = true;
702
703 for (int iter = 0; more && (iter < PTHREAD_DESTRUCTOR_ITERATIONS); ++iter)
704 {
705 bool any_ran = false;
706
707 for (std::size_t i = 0U; i < STK_C_PTHREAD_KEYS_MAX; ++i)
708 {
709 void *const value = ctrl->tsd[i];
710
711 if (value != nullptr)
712 {
713 void (*destructor)(void *) = nullptr;
714 {
715 const CsLock cs_;
716
717 if (s_Keys[i].used)
718 {
719 destructor = s_Keys[i].destructor;
720 }
721 }
722
723 ctrl->tsd[i] = nullptr; // clear before calling, per POSIX
724 if (destructor != nullptr)
725 {
726 destructor(value);
727 any_ran = true;
728 }
729 }
730 }
731
732 more = any_ran;
733 }
734}
735
736} // namespace pthread
737} // namespace stk
738
739using namespace stk::pthread;
740
741// =============================================================================
742// C-interface
743// =============================================================================
744extern "C" {
745
746// -----------------------------------------------------------------------------
747// Bootstrap
748// -----------------------------------------------------------------------------
750{
751 STK_C_ASSERT(kernel != nullptr);
752
753 s_BoundKernel = kernel;
754
755 EnsureReaper();
756}
757
758// -----------------------------------------------------------------------------
759// Thread attributes
760// -----------------------------------------------------------------------------
762{
763 int result = EINVAL;
764
765 if (attr != nullptr)
766 {
767 attr->__stack_bytes = 0U;
768 attr->__ext_stack = nullptr;
771 result = 0;
772 }
773
774 return result;
775}
776
778{
779 return (attr != nullptr) ? 0 : EINVAL;
780}
781
782int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
783{
784 int result = EINVAL;
785
786 if ((attr != nullptr) && (stacksize != 0U))
787 {
788 attr->__stack_bytes = stacksize;
789 result = 0;
790 }
791
792 return result;
793}
794
795int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
796{
797 int result = EINVAL;
798
799 if ((attr != nullptr) && (stacksize != nullptr))
800 {
801 *stacksize = (attr->__stack_bytes != 0U) ? attr->__stack_bytes : kDefaultStackBytes;
802 result = 0;
803 }
804
805 return result;
806}
807
808int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)
809{
810 int result = EINVAL;
811
812 if ((attr != nullptr) && (stackaddr != nullptr) && (stacksize >= sizeof(stk_word_t)))
813 {
814 if ((reinterpret_cast<uintptr_t>(stackaddr) & STK_ALIGN_MASK) == 0U)
815 {
816 attr->__ext_stack = static_cast<stk_word_t *>(stackaddr);
817 attr->__stack_bytes = stacksize;
818 result = 0;
819 }
820 }
821
822 return result;
823}
824
825int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize)
826{
827 int result = EINVAL;
828
829 if ((attr != nullptr) && (stackaddr != nullptr) && (stacksize != nullptr))
830 {
831 *stackaddr = attr->__ext_stack;
832 *stacksize = attr->__stack_bytes;
833 result = 0;
834 }
835
836 return result;
837}
838
840{
841 int result = EINVAL;
842
843 if ((attr != nullptr) &&
844 ((detachstate == PTHREAD_CREATE_JOINABLE) || (detachstate == PTHREAD_CREATE_DETACHED)))
845 {
846 attr->__detachstate = detachstate;
847 result = 0;
848 }
849
850 return result;
851}
852
853int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)
854{
855 int result = EINVAL;
856
857 if ((attr != nullptr) && (detachstate != nullptr))
858 {
859 *detachstate = attr->__detachstate;
860 result = 0;
861 }
862
863 return result;
864}
865
867{
868 int result = EINVAL;
869
870 if ((attr != nullptr) &&
872 {
873 attr->__privileged = mode;
874 result = 0;
875 }
876
877 return result;
878}
879
881{
882 int result = EINVAL;
883
884 if ((attr != nullptr) && (mode != nullptr))
885 {
886 *mode = attr->__privileged;
887 result = 0;
888 }
889
890 return result;
891}
892
893// -----------------------------------------------------------------------------
894// Thread lifecycle
895// -----------------------------------------------------------------------------
896int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
897 void *(*start_routine)(void *), void *arg)
898{
899 STK_C_ASSERT(s_BoundKernel != nullptr);
900 STK_C_ASSERT(thread != nullptr);
901 STK_C_ASSERT(start_routine != nullptr);
902
903 int result = 0;
904
905 if ((s_BoundKernel == nullptr) || (thread == nullptr) || (start_routine == nullptr))
906 {
907 result = EINVAL;
908 }
909 else
910 {
912
913 if (ctrl == nullptr)
914 {
915 result = EAGAIN;
916 }
917 else
918 {
919 const size_t stack_bytes = ((attr != nullptr) && (attr->__stack_bytes != 0U)) ?
921 const uint32_t stack_words =
922 static_cast<uint32_t>((stack_bytes + sizeof(stk_word_t) - 1U) / sizeof(stk_word_t));
923
924 if (!AllocateStack(ctrl, attr, stack_bytes))
925 {
926 {
927 const CsLock cs_;
928
929 ctrl->busy = false;
930 }
931 result = EAGAIN;
932 }
933 else
934 {
935 ctrl->start_routine = start_routine;
936 ctrl->arg = arg;
937 ctrl->retval = nullptr;
938 ctrl->detached = ((attr != nullptr) && (attr->__detachstate == PTHREAD_CREATE_DETACHED));
939 ctrl->done_event = ConstructIn<sync::Event>(ctrl->done_event_buf, true /* manual_reset */);
940
941 const bool privileged = (attr != nullptr) && (attr->__privileged == PTHREAD_CREATE_PRIVILEGED_NP);
942
943 ctrl->task.Initialize(ctrl, ctrl->stack, stack_words,
945
947
948 *thread = ctrl;
949 }
950 }
951 }
952
953 return result;
954}
955
956int pthread_join(pthread_t thread, void **retval)
957{
958 pthread_stk_ctrl_t *const ctrl = thread;
959 int result = 0;
960
961 if (ctrl == nullptr)
962 {
963 result = EINVAL;
964 }
965 else if (ctrl == CurrentCtrl())
966 {
967 result = EDEADLK; // joining yourself would wait forever
968 }
969 else
970 {
971 bool ok = false;
972 {
973 const CsLock cs_;
974
975 if (!ctrl->detached && !ctrl->joined)
976 {
977 ctrl->joined = true;
978 ok = true;
979 }
980 }
981
982 if (!ok)
983 {
984 result = EINVAL;
985 }
986 else
987 {
988 (void)ctrl->done_event->Wait(); // WAIT_INFINITE
989
990 if (retval != nullptr)
991 {
992 *retval = ctrl->retval;
993 }
994
995 WaitForTaskExit(ctrl);
997 }
998 }
999
1000 return result;
1001}
1002
1004{
1005 pthread_stk_ctrl_t *const ctrl = thread;
1006 int result = 0;
1007
1008 if (ctrl == nullptr)
1009 {
1010 result = EINVAL;
1011 }
1012 else
1013 {
1014 bool ok = false;
1015 {
1016 const CsLock cs_;
1017
1018 if (!ctrl->joined && !ctrl->detached)
1019 {
1020 ctrl->detached = true;
1021 ok = true;
1022 }
1023 }
1024
1025 if (!ok)
1026 {
1027 result = EINVAL;
1028 }
1029 else
1030 {
1031 MaybeReap(ctrl);
1032 }
1033 }
1034
1035 return result;
1036}
1037
1038void pthread_exit(void *retval)
1039{
1040 pthread_stk_ctrl_t *const ctrl = CurrentCtrl();
1041
1042 if (ctrl != nullptr)
1043 {
1044 RunKeyDestructors(ctrl);
1045
1046 // An adopted (foreign) task has no ITask we could ask the kernel to remove, so for
1047 // it pthread_exit() only runs the destructors and then parks (see below).
1048 if (!ctrl->adopted)
1049 {
1050 {
1051 const CsLock cs_;
1052
1053 ctrl->retval = retval;
1054 ctrl->finished = true;
1055 }
1056
1057 (void)ctrl->done_event->Set();
1058 MaybeReap(ctrl); // reaper/joiner will wait for ctrl->exited before freeing anything
1059
1060 STK_C_ASSERT(s_BoundKernel != nullptr);
1062 }
1063 }
1064
1065 // Park here instead of falling back into caller code that isn't expecting to
1066 // regain control (the removal above takes effect on the next scheduling round).
1067 for (;;)
1068 {
1070 }
1071}
1072
1074{
1075 return static_cast<pthread_t>(CurrentCtrlOrAdopt()); // NULL only if the slot pool is exhausted
1076}
1077
1079{
1080 return (t1 == t2) ? 1 : 0;
1081}
1082
1084{
1085 stk::Yield();
1086 return 0;
1087}
1088
1089// -----------------------------------------------------------------------------
1090// Mutex
1091// -----------------------------------------------------------------------------
1093{
1094 int result = EINVAL;
1095
1096 if (attr != nullptr)
1097 {
1099 result = 0;
1100 }
1101
1102 return result;
1103}
1104
1106{
1107 return (attr != nullptr) ? 0 : EINVAL;
1108}
1109
1111{
1112 int result = EINVAL;
1113
1114 if ((attr != nullptr) && IsValidMutexType(type))
1115 {
1116 attr->__type = type;
1117 result = 0;
1118 }
1119
1120 return result;
1121}
1122
1124{
1125 int result = EINVAL;
1126
1127 if ((attr != nullptr) && (type != nullptr))
1128 {
1129 *type = attr->__type;
1130 result = 0;
1131 }
1132
1133 return result;
1134}
1135
1137{
1138 int result = EINVAL;
1139
1140 if (mutex != nullptr)
1141 {
1142 const int type = (attr != nullptr) ? attr->__type : PTHREAD_MUTEX_DEFAULT;
1143
1144 if (IsValidMutexType(type))
1145 {
1146 mutex->__type = type;
1148 result = 0;
1149 }
1150 }
1151
1152 return result;
1153}
1154
1156{
1157 int result = EINVAL;
1158
1159 if (mutex != nullptr)
1160 {
1161 if (mutex->__handle != nullptr)
1162 {
1163 HandleOf(mutex)->~Mutex();
1164 mutex->__handle = nullptr;
1165 }
1166 result = 0;
1167 }
1168
1169 return result;
1170}
1171
1173{
1174 int result = EINVAL;
1175
1176 if (mutex != nullptr)
1177 {
1178 sync::Mutex *const m = EnsureMutex(mutex);
1179
1180 result = PreLockCheck(mutex, m, EDEADLK);
1181 if (result == 0)
1182 {
1183 m->Lock();
1184 }
1185 }
1186
1187 return result;
1188}
1189
1191{
1192 int result = EINVAL;
1193
1194 if (mutex != nullptr)
1195 {
1196 sync::Mutex *const m = EnsureMutex(mutex);
1197
1198 result = PreLockCheck(mutex, m, EBUSY);
1199 if (result == 0)
1200 {
1201 result = m->TryLock() ? 0 : EBUSY;
1202 }
1203 }
1204
1205 return result;
1206}
1207
1209{
1210 int result = EINVAL;
1211
1212 if ((mutex != nullptr) && (mutex->__handle != nullptr))
1213 {
1214 sync::Mutex *const m = HandleOf(mutex);
1215
1216 if (IsMutexOwner(m))
1217 {
1218 m->Unlock();
1219 result = 0;
1220 }
1221 else
1222 {
1223 result = EPERM;
1224 }
1225 }
1226
1227 return result;
1228}
1229
1230int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime)
1231{
1232 int result = EINVAL;
1233
1234 if ((mutex != nullptr) && (abstime != nullptr))
1235 {
1236 sync::Mutex *const m = EnsureMutex(mutex);
1237
1238 result = PreLockCheck(mutex, m, EDEADLK);
1239 if (result == 0)
1240 {
1241 result = m->TimedLock(TimespecToRelativeTimeout(abstime)) ? 0 : ETIMEDOUT;
1242 }
1243 }
1244
1245 return result;
1246}
1247
1248// -----------------------------------------------------------------------------
1249// Condition variable
1250// -----------------------------------------------------------------------------
1252{
1253 int result = EINVAL;
1254
1255 if (attr != nullptr)
1256 {
1257 attr->__reserved = 0;
1258 result = 0;
1259 }
1260
1261 return result;
1262}
1263
1265{
1266 return (attr != nullptr) ? 0 : EINVAL;
1267}
1268
1270{
1271 int result = EINVAL;
1272
1273 if (cond != nullptr)
1274 {
1276 result = 0;
1277 }
1278
1279 return result;
1280}
1281
1283{
1284 int result = EINVAL;
1285
1286 if (cond != nullptr)
1287 {
1288 if (cond->__handle != nullptr)
1289 {
1290 HandleOf(cond)->~ConditionVariable();
1291 cond->__handle = nullptr;
1292 }
1293 result = 0;
1294 }
1295
1296 return result;
1297}
1298
1300{
1301 int result = 0;
1302
1303 if ((cond == nullptr) || (mutex == nullptr) || (mutex->__handle == nullptr))
1304 {
1305 result = EINVAL;
1306 }
1307 else if ((mutex->__type == PTHREAD_MUTEX_ERRORCHECK) && !IsMutexOwner(HandleOf(mutex)))
1308 {
1309 result = EPERM;
1310 }
1311 else
1312 {
1313 (void)EnsureCond(cond)->Wait(*HandleOf(mutex)); // WAIT_INFINITE
1314 }
1315
1316 return result;
1317}
1318
1319int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
1320{
1321 int result = 0;
1322
1323 if ((cond == nullptr) || (mutex == nullptr) || (mutex->__handle == nullptr) || (abstime == nullptr))
1324 {
1325 result = EINVAL;
1326 }
1327 else if ((mutex->__type == PTHREAD_MUTEX_ERRORCHECK) && !IsMutexOwner(HandleOf(mutex)))
1328 {
1329 result = EPERM;
1330 }
1331 else
1332 {
1333 const bool signaled = EnsureCond(cond)->Wait(*HandleOf(mutex), TimespecToRelativeTimeout(abstime));
1334 result = signaled ? 0 : ETIMEDOUT;
1335 }
1336
1337 return result;
1338}
1339
1341{
1342 int result = EINVAL;
1343
1344 if (cond != nullptr)
1345 {
1346 EnsureCond(cond)->NotifyOne();
1347 result = 0;
1348 }
1349
1350 return result;
1351}
1352
1354{
1355 int result = EINVAL;
1356
1357 if (cond != nullptr)
1358 {
1359 EnsureCond(cond)->NotifyAll();
1360 result = 0;
1361 }
1362
1363 return result;
1364}
1365
1366// -----------------------------------------------------------------------------
1367// Read-write lock
1368// -----------------------------------------------------------------------------
1370{
1371 int result = EINVAL;
1372
1373 if (attr != nullptr)
1374 {
1375 attr->__reserved = 0;
1376 result = 0;
1377 }
1378
1379 return result;
1380}
1381
1383{
1384 return (attr != nullptr) ? 0 : EINVAL;
1385}
1386
1388{
1389 int result = EINVAL;
1390
1391 if (rwlock != nullptr)
1392 {
1394 rwlock->__wrlocked = false;
1395 result = 0;
1396 }
1397
1398 return result;
1399}
1400
1402{
1403 int result = EINVAL;
1404
1405 if (rwlock != nullptr)
1406 {
1407 if (rwlock->__handle != nullptr)
1408 {
1409 HandleOf(rwlock)->~RWMutex();
1410 rwlock->__handle = nullptr;
1411 }
1412 result = 0;
1413 }
1414
1415 return result;
1416}
1417
1419{
1420 int result = EINVAL;
1421
1422 if (rwlock != nullptr)
1423 {
1424 EnsureRWLock(rwlock)->ReadLock();
1425 result = 0;
1426 }
1427
1428 return result;
1429}
1430
1432{
1433 int result = EINVAL;
1434
1435 if (rwlock != nullptr)
1436 {
1437 result = EnsureRWLock(rwlock)->TryReadLock() ? 0 : EBUSY;
1438 }
1439
1440 return result;
1441}
1442
1443int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
1444{
1445 int result = EINVAL;
1446
1447 if ((rwlock != nullptr) && (abstime != nullptr))
1448 {
1449 const bool locked = EnsureRWLock(rwlock)->TimedReadLock(TimespecToRelativeTimeout(abstime));
1450 result = locked ? 0 : ETIMEDOUT;
1451 }
1452
1453 return result;
1454}
1455
1457{
1458 int result = EINVAL;
1459
1460 if (rwlock != nullptr)
1461 {
1462 EnsureRWLock(rwlock)->Lock();
1463 rwlock->__wrlocked = true;
1464 result = 0;
1465 }
1466
1467 return result;
1468}
1469
1471{
1472 int result = EINVAL;
1473
1474 if (rwlock != nullptr)
1475 {
1476 if (EnsureRWLock(rwlock)->TryLock())
1477 {
1478 rwlock->__wrlocked = true;
1479 result = 0;
1480 }
1481 else
1482 {
1483 result = EBUSY;
1484 }
1485 }
1486
1487 return result;
1488}
1489
1490int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
1491{
1492 int result = EINVAL;
1493
1494 if ((rwlock != nullptr) && (abstime != nullptr))
1495 {
1496 if (EnsureRWLock(rwlock)->TimedLock(TimespecToRelativeTimeout(abstime)))
1497 {
1498 rwlock->__wrlocked = true;
1499 result = 0;
1500 }
1501 else
1502 {
1503 result = ETIMEDOUT;
1504 }
1505 }
1506
1507 return result;
1508}
1509
1511{
1512 int result = EINVAL;
1513
1514 if ((rwlock != nullptr) && (rwlock->__handle != nullptr))
1515 {
1516 // See "pthread_rwlock_unlock() disambiguation" in stk_c_pthread.h: a write
1517 // hold is always exclusive, so __wrlocked unambiguously identifies which
1518 // underlying call (Unlock() vs ReadUnlock()) the current holder must have made. Clear it before
1519 // releasing so a racing new writer's own acquisition can't be clobbered.
1520 if (rwlock->__wrlocked)
1521 {
1522 rwlock->__wrlocked = false;
1523 HandleOf(rwlock)->Unlock();
1524 }
1525 else
1526 {
1527 HandleOf(rwlock)->ReadUnlock();
1528 }
1529 result = 0;
1530 }
1531
1532 return result;
1533}
1534
1535// -----------------------------------------------------------------------------
1536// Spin lock
1537// -----------------------------------------------------------------------------
1539{
1540 int result = EINVAL;
1541
1542 if (lock != nullptr)
1543 {
1544 if (pshared != PTHREAD_PROCESS_PRIVATE)
1545 {
1546 result = ENOTSUP;
1547 }
1548 else
1549 {
1551 result = 0;
1552 }
1553 }
1554
1555 return result;
1556}
1557
1559{
1560 int result = EINVAL;
1561
1562 if (lock != nullptr)
1563 {
1564 if (lock->__handle != nullptr)
1565 {
1566 HandleOf(lock)->~SpinLock();
1567 lock->__handle = nullptr;
1568 }
1569 result = 0;
1570 }
1571
1572 return result;
1573}
1574
1576{
1577 int result = EINVAL;
1578
1579 if ((lock != nullptr) && (lock->__handle != nullptr))
1580 {
1581 HandleOf(lock)->Lock();
1582 result = 0;
1583 }
1584
1585 return result;
1586}
1587
1589{
1590 int result = EINVAL;
1591
1592 if ((lock != nullptr) && (lock->__handle != nullptr))
1593 {
1594 result = HandleOf(lock)->TryLock() ? 0 : EBUSY;
1595 }
1596
1597 return result;
1598}
1599
1601{
1602 int result = EINVAL;
1603
1604 if ((lock != nullptr) && (lock->__handle != nullptr))
1605 {
1606 HandleOf(lock)->Unlock();
1607 result = 0;
1608 }
1609
1610 return result;
1611}
1612
1613// -----------------------------------------------------------------------------
1614// Barrier
1615// -----------------------------------------------------------------------------
1617{
1618 int result = EINVAL;
1619
1620 if (attr != nullptr)
1621 {
1622 attr->__reserved = 0;
1623 result = 0;
1624 }
1625
1626 return result;
1627}
1628
1630{
1631 return (attr != nullptr) ? 0 : EINVAL;
1632}
1633
1635 unsigned int count)
1636{
1637 int result = EINVAL;
1638
1639 if ((barrier != nullptr) && (count != 0U))
1640 {
1641 barrier->__handle = ConstructIn<stk::sync::Barrier>(barrier->__mem, static_cast<uint32_t>(count));
1642 result = 0;
1643 }
1644
1645 return result;
1646}
1647
1649{
1650 int result = EINVAL;
1651
1652 if (barrier != nullptr)
1653 {
1654 if (barrier->__handle != nullptr)
1655 {
1656 HandleOf(barrier)->~Barrier();
1657 barrier->__handle = nullptr;
1658 }
1659 result = 0;
1660 }
1661
1662 return result;
1663}
1664
1666{
1667 int result = EINVAL;
1668
1669 if ((barrier != nullptr) && (barrier->__handle != nullptr))
1670 {
1671 result = HandleOf(barrier)->Wait() ? PTHREAD_BARRIER_SERIAL_THREAD : 0;
1672 }
1673
1674 return result;
1675}
1676
1677// -----------------------------------------------------------------------------
1678// Once
1679// -----------------------------------------------------------------------------
1680int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
1681{
1682 int result = 0;
1683
1684 if ((once_control == nullptr) || (init_routine == nullptr))
1685 {
1686 result = EINVAL;
1687 }
1688 else if (LoadAcquire(&once_control->__state) != kOnceDone) // fast path: already done, no lock needed
1689 {
1690 stk::sync::Mutex *const guard = EnsureOnceMutex(once_control);
1691
1692 // The guard is held only while init_routine() runs, so "owned by the caller" means
1693 // init_routine() re-entered pthread_once() on the same control object. The guard is a
1694 // recursive Mutex, so Lock() would succeed and init_routine() would recurse until the
1695 // stack overflows; report the misuse instead (POSIX leaves it undefined).
1696 if (IsMutexOwner(guard))
1697 {
1698 result = EDEADLK;
1699 }
1700 else
1701 {
1702 guard->Lock();
1703
1704 // Re-check under the lock: another thread may have finished init_routine()
1705 // (or be running it right now, in which case this lock() call already blocked
1706 // until it was done) between our unlocked fast-path check above and here.
1707 if (once_control->__state != kOnceDone)
1708 {
1709 once_control->__state = kOnceRunning;
1710 init_routine();
1711 StoreRelease(&once_control->__state, kOnceDone); // release: publish init_routine()'s effects
1712 }
1713
1714 guard->Unlock();
1715 }
1716 }
1717 else
1718 {
1719 // already initialized: nothing to do
1720 }
1721
1722 return result;
1723}
1724
1725// -----------------------------------------------------------------------------
1726// Thread-specific data (keys)
1727// -----------------------------------------------------------------------------
1728int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
1729{
1730 int result = EINVAL;
1731
1732 if (key != nullptr)
1733 {
1734 result = EAGAIN;
1735
1736 const CsLock cs_;
1737
1738 for (std::size_t i = 0U; (result != 0) && (i < STK_C_PTHREAD_KEYS_MAX); ++i)
1739 {
1740 if (!s_Keys[i].used)
1741 {
1742 s_Keys[i].used = true;
1743 s_Keys[i].destructor = destructor;
1744 *key = static_cast<pthread_key_t>(i);
1745 result = 0;
1746 }
1747 }
1748 }
1749
1750 return result;
1751}
1752
1754{
1755 int result = EINVAL;
1756
1757 if (key < STK_C_PTHREAD_KEYS_MAX)
1758 {
1759 const CsLock cs_;
1760
1761 if (s_Keys[key].used)
1762 {
1763 s_Keys[key].used = false;
1764 s_Keys[key].destructor = nullptr;
1765 result = 0;
1766 }
1767 }
1768
1769 return result;
1770}
1771
1772int pthread_setspecific(pthread_key_t key, const void *value)
1773{
1774 int result = EINVAL;
1775
1776 if (IsKeyUsed(key))
1777 {
1778 ThreadCtrl *const ctrl = CurrentCtrlOrAdopt(); // foreign tasks are adopted on first use
1779
1780 if (ctrl == nullptr)
1781 {
1782 result = ENOMEM; // pthread slot pool exhausted
1783 }
1784 else
1785 {
1786 ctrl->tsd[key] = const_cast<void *>(value);
1787 result = 0;
1788 }
1789 }
1790
1791 return result;
1792}
1793
1795{
1796 void *result = nullptr;
1797
1798 if (IsKeyUsed(key))
1799 {
1800 ThreadCtrl *const ctrl = CurrentCtrl(); // no adoption: a task that never called setspecific() has no values
1801
1802 if (ctrl != nullptr)
1803 {
1804 result = ctrl->tsd[key];
1805 }
1806 }
1807
1808 return result;
1809}
1810
1811// =============================================================================
1812} // extern "C"
1813// =============================================================================
static void __stk_dmb()
Hardware memory barrier: ensures visibility across cores and bus masters.
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:261
Collection of synchronization primitives (stk::sync namespace).
C language binding/interface for SuperTinyKernel RTOS.
C language binding for stk::memory::BlockMemoryPool.
void * malloc(size_t size)
void free(void *ptr)
A minimal, POSIX-named pthreads-style API for STK. The public interface needs only stk_c....
stk::hw::CriticalSection::ScopedLock CsLock
void * malloc(std::size_t size)
void free(void *ptr)
stk_task_t * stk_task_create_user(stk_task_entry_t entry, void *arg, stk_word_t *stack, uint32_t stack_size)
Create user-mode task.
Definition stk_c.cpp:567
void stk_task_set_name(stk_task_t *tsk, const char *tname)
Assign human-readable task name (for tracing/debugging).
Definition stk_c.cpp:595
struct stk_kernel_t stk_kernel_t
Opaque handle to a kernel instance.
Definition stk_c.h:131
#define STK_ALIGN_MASK
Alignment mask.
Definition stk_c.h:164
void stk_kernel_add_task(stk_kernel_t *k, stk_task_t *tsk)
Add task to non-HRT kernel (static or dynamic).
Definition stk_c.cpp:428
uintptr_t stk_word_t
CPU register type.
Definition stk_c.h:99
#define STK_C_ASSERT(e)
Assertion macro used inside STK C bindings.
Definition stk_c.h:80
int64_t stk_time_t
Time value.
Definition stk_c.h:113
bool stk_blockpool_free(stk_blockpool_t *pool, void *ptr)
Return a previously allocated block to the pool.
void * stk_blockpool_try_alloc(stk_blockpool_t *pool)
Non-blocking allocation attempt.
stk_blockpool_t * stk_blockpool_create_static(size_t capacity, size_t raw_block_size, uint8_t *storage, size_t storage_size, const char *name)
Create a block pool backed by caller-supplied (external) storage.
#define STK_BLOCKPOOL_STORAGE_DECL(name, capacity, raw_block_size)
Declare a correctly sized and aligned external storage array.
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
Try to acquire the write lock without blocking.
int pthread_condattr_destroy(pthread_condattr_t *attr)
Destroy a condition variable attributes object (no-op).
int pthread_attr_init(pthread_attr_t *attr)
Initialize a thread attributes object with default values (default stack size, no external stack,...
int pthread_mutex_lock(pthread_mutex_t *mutex)
Lock a mutex, blocking until it becomes available.
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *)
Initialize a read-write lock.
int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
Acquire the write lock with an absolute deadline.
int pthread_once(pthread_once_t *once_control, void(*init_routine)(void))
Call init_routine exactly once for a given once_control, no matter how many threads call pthread_once...
#define PTHREAD_DESTRUCTOR_ITERATIONS
Maximum number of passes over a finishing thread's keys made while destructors keep setting new non-N...
int pthread_cond_signal(pthread_cond_t *cond)
Wake one thread waiting on cond (no effect if none is waiting).
int pthread_setspecific(pthread_key_t key, const void *value)
Set the calling thread's value for key.
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
Destroy a mutex attributes object (no-op; no owned resources).
void pthread_exit(void *retval)
Terminate the calling thread.
int pthread_mutex_trylock(pthread_mutex_t *mutex)
Try to lock a mutex without blocking.
int pthread_join(pthread_t thread, void **retval)
Block until the given joinable thread finishes, then reclaim its resources.
#define PTHREAD_MUTEX_DEFAULT
#define PTHREAD_CREATE_PRIVILEGED_NP
int pthread_condattr_init(pthread_condattr_t *attr)
Initialize a condition variable attributes object (no settable properties).
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)
Get the current detach-state setting.
#define PTHREAD_CREATE_DETACHED
int pthread_spin_unlock(pthread_spinlock_t *lock)
Release a spinlock held by the calling thread.
int pthread_mutex_destroy(pthread_mutex_t *mutex)
Destroy a mutex, releasing the underlying STK mutex object.
#define STK_C_PTHREAD_DEFAULT_STACK_WORDS
Default per-thread stack size in stk_word_t units, used when pthread_attr_t does not specify a stack ...
#define PTHREAD_PROCESS_PRIVATE
#define STK_C_PTHREAD_KEYS_MAX
Maximum number of concurrently-alive pthread_key_t's (default: 8).
int pthread_attr_destroy(pthread_attr_t *attr)
Destroy a thread attributes object (no-op; no owned resources).
pthread_t pthread_self(void)
Return the calling thread's own handle.
int pthread_spin_lock(pthread_spinlock_t *lock)
Acquire the spinlock, spinning until available.
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
Acquire the lock for exclusive writing. Blocks until available.
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime)
Lock with an absolute deadline.
#define PTHREAD_MUTEX_RECURSIVE
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
Initialize a mutex.
#define STK_C_PTHREAD_REAPER_STACK_WORDS
Stack size (in stk_word_t units) for the internal reaper task that reclaims detached-thread resources...
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
Destroy a read-write lock attributes object (no-op).
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
Destroy a read-write lock, releasing the underlying STK object.
int pthread_detach(pthread_t thread)
Mark a thread as detached.
unsigned int pthread_key_t
A thread-specific data key.
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
Release a read or write hold, whichever the calling thread holds.
int pthread_attr_setprivileged_np(pthread_attr_t *attr, int mode)
Non-portable: select the STK access mode of threads created with attr.
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type)
Get the mutex type stored in attr.
struct pthread_stk_ctrl_t * pthread_t
Opaque thread handle.
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
Set the requested stack size in bytes.
#define PTHREAD_BARRIER_SERIAL_THREAD
Returned by pthread_barrier_wait() to exactly one arbitrary caller per round; all others receive 0....
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)
Set the mutex type.
int pthread_barrier_wait(pthread_barrier_t *barrier)
Block until count threads have called this function, then release them all together; the barrier rese...
int pthread_attr_getprivileged_np(const pthread_attr_t *attr, int *mode)
Non-portable: get the access mode set in attr.
int pthread_spin_destroy(pthread_spinlock_t *lock)
Destroy a spinlock.
void stk_pthread_bind_kernel(stk_kernel_t *kernel)
Bind the STK kernel instance that pthread_create() will add new threads to.
int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *, unsigned int count)
Initialize a barrier for count participating threads.
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
Atomically unlock mutex and wait for a signal; re-locks mutex before returning.
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)
Supply an external, caller-owned stack buffer for the thread.
int pthread_mutex_unlock(pthread_mutex_t *mutex)
Unlock a mutex held by the calling thread.
int pthread_equal(pthread_t t1, pthread_t t2)
Compare two thread handles for equality.
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
Initialize a read-write lock attributes object (no settable properties).
int pthread_barrierattr_init(pthread_barrierattr_t *attr)
Initialize a barrier attributes object (no settable properties).
int pthread_cond_destroy(pthread_cond_t *cond)
Destroy a condition variable, releasing the underlying STK object.
int pthread_spin_init(pthread_spinlock_t *lock, int pshared)
Initialize a spinlock.
#define PTHREAD_MUTEX_NORMAL
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
Try to acquire the read lock without blocking.
#define PTHREAD_MUTEX_ERRORCHECK
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
As pthread_cond_wait(), with an absolute deadline.
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr)
Destroy a barrier attributes object (no-op).
#define STK_C_PTHREAD_MAX_THREADS
Maximum number of concurrently-alive pthread_t's (default: 8).
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
Acquire the lock for shared reading. Blocks until available.
int pthread_spin_trylock(pthread_spinlock_t *lock)
Try to acquire the spinlock without blocking.
void * pthread_getspecific(pthread_key_t key)
Get the calling thread's value for key.
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *)
Initialize a condition variable.
int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
Acquire the read lock with an absolute deadline.
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
Get the stack size in bytes that threads created with attr will use.
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
Create and start a new thread.
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
Set PTHREAD_CREATE_JOINABLE or PTHREAD_CREATE_DETACHED.
int pthread_barrier_destroy(pthread_barrier_t *barrier)
Destroy a barrier, releasing the underlying STK barrier object.
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize)
Get the previously-set external stack (stackaddr is NULL if none set).
#define PTHREAD_CREATE_USER_NP
int pthread_key_delete(pthread_key_t key)
Free a thread-specific data key.
int pthread_yield(void)
Voluntarily give up the CPU to another ready task (cooperative yield), then resume once rescheduled.
int pthread_key_create(pthread_key_t *key, void(*destructor)(void *))
Allocate a new thread-specific data key.
int pthread_mutexattr_init(pthread_mutexattr_t *attr)
Initialize a mutex attributes object with default values (PTHREAD_MUTEX_DEFAULT).
#define PTHREAD_CREATE_JOINABLE
int pthread_cond_broadcast(pthread_cond_t *cond)
Wake all threads waiting on cond (no effect if none is waiting).
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:144
static void Yield()
Notify scheduler to switch to the next runnable task.
Definition stk_helper.h:536
static T * ConstructIn(Mem &mem, Args &&... args)
Construct object T in-place inside a C memory container.
Definition stk_c.h:2198
static void Sleep(Timeout tick_count)
Put calling process into a sleep state.
Definition stk_helper.h:479
static void SleepMs(Timeout ms)
Put calling process into a sleep state.
Definition stk_helper.h:492
EAccessMode
Hardware access modes by the task.
Definition stk_common.h:36
@ ACCESS_USER
Unprivileged access mode (access to some hardware is restricted, see CPU manual for details)....
Definition stk_common.h:37
@ ACCESS_PRIVILEGED
Privileged access mode (access to hardware is fully unrestricted).
Definition stk_common.h:38
static Time GetTimeNowMs()
Get current time in milliseconds since kernel start.
Definition stk_helper.h:445
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:154
static constexpr Weight DEFAULT_WEIGHT
Weight value: default weight of value (1) (see SwitchStrategySmoothWeightedRoundRobin).
Definition stk_common.h:228
static constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:212
static Timeout GetTicksFromMsClampedToTimeout(Timeout ms)
Convert milliseconds to ticks and clamp the result to a Timeout type.
Definition stk_helper.h:419
static KeyEntry s_Keys[STK_C_PTHREAD_KEYS_MAX]
static void RunKeyDestructors(ThreadCtrl *ctrl)
static T LoadAcquire(T *p)
static void ReleaseThreadResources(ThreadCtrl *ctrl)
static void MaybeReap(ThreadCtrl *ctrl)
static sync::ConditionVariable * EnsureCond(pthread_cond_t *c)
static ThreadCtrl s_Threads[STK_C_PTHREAD_MAX_THREADS]
static ThreadCtrl * CurrentCtrlOrAdopt()
static sync::Mutex * EnsureMutex(pthread_mutex_t *m)
static ThreadCtrl * AcquireThreadSlot()
static stk_word_t s_ReaperStack[STK_C_PTHREAD_REAPER_STACK_WORDS]
static stk_kernel_t * s_BoundKernel
static bool IsMutexOwner(const sync::Mutex *m)
static void EnsureReaper()
static std::size_t s_ReapHead
static sync::Mutex * HandleOf(pthread_mutex_t *o)
static stk_blockpool_t * EnsureStackPool()
static ThreadCtrl * CurrentCtrl()
static sync::Semaphore * s_ReapSem
constexpr int kOnceRunning
pthread_stk_ctrl_t ThreadCtrl
static ThreadCtrl * s_ReapQueue[STK_C_PTHREAD_MAX_THREADS]
uint8_t data[sizeof(sync::Semaphore)]
static void ReaperEntry(void *arg)
static T * EnsureObject(void **slot, Mem &mem)
static Timeout TimespecToRelativeTimeout(const struct timespec *abstime)
static void WaitForTaskExit(ThreadCtrl *ctrl)
static void EnqueueReap(ThreadCtrl *ctrl)
constexpr int kOnceDone
static void StoreRelease(T *p, T v)
static std::size_t s_ReapCount
static int PreLockCheck(const pthread_mutex_t *pm, const sync::Mutex *m, int errorcheck_code)
constexpr std::size_t kDefaultStackBytes
static bool AllocateStack(ThreadCtrl *ctrl, const pthread_attr_t *attr, std::size_t stack_bytes)
static sync::Mutex * EnsureOnceMutex(pthread_once_t *o)
static bool IsValidMutexType(int type)
static stk_task_t * s_ReaperTask
static struct stk::pthread::@154260026337266142101324335010026147170101120162 s_ReapSemBuf
static ThreadCtrl * DequeueReap()
static sync::RWMutex * EnsureRWLock(pthread_rwlock_t *rw)
static stk_blockpool_t * s_StackPool
static IKernel * ToKernel(stk_kernel_t *k)
static bool IsKeyUsed(pthread_key_t key)
RAII instance that enters the critical section on construction and exits it on destruction.
Definition stk_arch.h:494
Atomic busy-wait lock used as the global cross-core synchronisation primitive inside CriticalSection.
Definition stk_arch.h:570
Interface for a user task.
Definition stk_common.h:772
Interface for the implementation of the kernel of the scheduler. It supports Soft and Hard Real-Time ...
virtual void AddTask(ITask *user_task)=0
Add user task.
virtual void ScheduleTaskRemoval(ITask *user_task)=0
Schedule task removal from scheduling (exit).
static IKernelService * GetInstance()
Get CPU-local instance of the kernel service.
Cyclic barrier that blocks a fixed-size group of tasks until all of them have arrived.
Condition Variable primitive for signaling between tasks based on specific predicates.
Definition stk_sync_cv.h:68
void NotifyOne()
Wake one waiting task.
bool Wait(IMutex &mutex, Timeout timeout_ticks=WAIT_INFINITE)
Wait for a signal.
void NotifyAll()
Wake all waiting tasks.
Binary synchronization event (signaled / non-signaled) primitive.
~Event()
Destructor.
bool Set()
Set event to signaled state.
bool Wait(Timeout timeout_ticks=WAIT_INFINITE)
Wait until event becomes signaled or the timeout expires.
Recursive mutex primitive that allows the same thread to acquire the lock multiple times.
bool TimedLock(Timeout timeout_ticks)
Acquire lock.
~Mutex()
Destructor.
bool TryLock()
Acquire the lock.
TId GetOwner() const
Get owner of the mutex.
void Unlock() override
Release lock.
void Lock() override
Acquire lock.
static const uint16_t RECURSION_MAX
maximum nesting depth
uint16_t GetRecursionCount() const
Get current recursion depth.
Reader-Writer Lock synchronization primitive for non-recursive shared and exclusive access.
void Lock() override
Acquire the lock for exclusive writing (IMutex interface).
bool TimedReadLock(Timeout timeout_ticks)
Acquire the lock for shared reading with a timeout.
void ReadLock()
Acquire the lock for shared reading.
bool TryReadLock()
Attempt to acquire the lock for shared reading without blocking.
Counting semaphore primitive for resource management and signaling.
Thread creation attributes.
size_t __stack_bytes
0 = use STK_C_PTHREAD_DEFAULT_STACK_WORDS.
int __detachstate
PTHREAD_CREATE_JOINABLE / _DETACHED.
stk_word_t * __ext_stack
Caller-supplied stack, or NULL.
int __privileged
PTHREAD_CREATE_USER_NP (default) / _PRIVILEGED_NP.
Mutex attributes.
int __type
PTHREAD_MUTEX_NORMAL / DEFAULT / RECURSIVE / ERRORCHECK.
A pthread mutex.
int __type
PTHREAD_MUTEX_* type; 0 (NORMAL) when statically initialized.
stk_mutex_mem_t __mem
In-place storage for the STK mutex object.
void * __handle
Object constructed in __mem, or NULL if not yet constructed.
Condition variable attributes (currently no settable properties).
int __reserved
Unused (no settable properties); reserved for future use.
A pthread condition variable.
void * __handle
Object constructed in __mem, or NULL if not yet constructed.
stk_cv_mem_t __mem
In-place storage for the STK condition variable object.
Read-write lock attributes (currently no settable properties).
int __reserved
Unused (no settable properties); reserved for future use.
A pthread read-write lock.
volatile bool __wrlocked
True while held for writing; selects Unlock() vs ReadUnlock() on release.
void * __handle
Object constructed in __mem, or NULL if not yet constructed.
stk_rwmutex_mem_t __mem
In-place storage for the STK rwmutex object.
A pthread spinlock.
struct pthread_spinlock_t::@072117035232205037233337242056154122155161307376 __mem
In-place storage for the STK spinlock object.
void * __handle
Object constructed in __mem, or NULL if not yet constructed.
Barrier attributes (currently no settable properties).
int __reserved
Unused (no settable properties); reserved for future use.
A pthread barrier.
stk_barrier_mem_t __mem
In-place storage for the STK barrier object.
void * __handle
Object constructed in __mem, or NULL if not yet constructed.
A pthread_once() control object.
stk_mutex_mem_t __mem
In-place storage for the guard mutex.
void * __handle
Guard mutex constructed in __mem, or NULL if not yet constructed.
int __state
0 = not started, 1 = running, 2 = done.
const Word * GetStack() const override
Get pointer to the stack memory.
EAccessMode GetAccessMode() const override
Get pointer to the stack memory.
void Run() override
Entry point of the user task.
size_t GetStackSize() const override
Get number of elements of the stack memory array.
void Initialize(pthread_stk_ctrl_t *ctrl, stk_word_t *stack, size_t stack_size, EAccessMode mode)
int32_t GetWeight() const override
Get static base weight of the task.
pthread_stk_ctrl_t * m_ctrl
STK_NONCOPYABLE_CLASS(PthreadTask)
const char * GetTraceName() const override
Get task trace name set by application.
STK_VIRT_DTOR ~PthreadTask()=default
Destructor.
void OnExit() override
Called by the kernel before removal from the scheduling (see stk::KERNEL_DYNAMIC).
struct pthread_stk_ctrl_t::@024110347346021317207143067073371172206260032255 done_event_buf
uint8_t data[sizeof(stk::sync::Event)]
void * tsd[STK_C_PTHREAD_KEYS_MAX]
void *(* start_routine)(void *)
stk::sync::Event * done_event
stk::pthread::PthreadTask task
void(* destructor)(void *)