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
13#include "stk_c.h"
14#include "stk_c_memory.h"
15#include "stk_c_pthread.h"
16
17// Private malloc/free declarations (mirrors stk_c_memory.cpp): overcomes absence of
18// declarations under a -ffreestanding compiler flag. Only used for the "unusual"
19// stack-size fallback path; the common-size path is served entirely from a static
20// BlockMemoryPool with zero heap use.
21extern "C" void *malloc(std::size_t size);
22extern "C" void free(void *ptr);
23
24// =============================================================================
25// Internal state
26// =============================================================================
27
28// Definition of the opaque type forward-declared in stk_c_pthread.h as
29// `typedef struct pthread_stk_ctrl_t *pthread_t;`. Must live at global scope
30// (matching the header's forward declaration) rather than inside the anonymous
31// namespace below, so that pthread_t in caller code and this definition refer to
32// the exact same type.
34{
35 bool busy;
36 bool finished; // start_routine returned / pthread_exit() called
38 bool joined;
39 bool reclaimed; // resources already freed or handed to the reaper
40
42 void *(*start_routine)(void *);
43 void *arg;
44 void *retval;
45
47 bool stack_owned; // false => caller-supplied via pthread_attr_setstack()
48 bool stack_from_pool; // true => came from s_StackPool, else malloc()
49
52
53 void *tsd[STK_C_PTHREAD_KEYS_MAX]; // pthread_setspecific()/getspecific() storage
54};
55
56namespace {
57
59
60constexpr std::size_t kDefaultStackBytes =
61 static_cast<std::size_t>(STK_C_PTHREAD_DEFAULT_STACK_WORDS) * sizeof(stk_word_t);
62
64
65// -----------------------------------------------------------------------------
66// Bound kernel
67// -----------------------------------------------------------------------------
69
70// -----------------------------------------------------------------------------
71// Static stack pool (zero heap) for default-size stacks
72// -----------------------------------------------------------------------------
75
77{
78 if (s_StackPool == nullptr)
79 {
81 if (s_StackPool == nullptr)
82 {
86 reinterpret_cast<uint8_t *>(s_PthreadStackStorage),
87 sizeof(s_PthreadStackStorage),
88 "pthread_stacks");
89 }
91 }
92
93 return s_StackPool;
94}
95
96// -----------------------------------------------------------------------------
97// Reaper task: reclaims detached-thread resources once they finish.
98// -----------------------------------------------------------------------------
100std::size_t s_ReapHead = 0U;
101std::size_t s_ReapCount = 0U;
102
107
109void WaitForTaskGone(stk_task_t *task);
110void ReaperEntry(void *arg);
111
113{
114 if (s_ReaperTask == nullptr)
115 {
117 if (s_ReaperTask == nullptr)
118 {
119 STK_C_ASSERT(s_BoundKernel != nullptr);
120
121 if (s_ReapSem == nullptr)
122 {
124 }
125
128 if (s_ReaperTask != nullptr)
129 {
130 stk_task_set_name(s_ReaperTask, "pthread_reaper");
132 }
133 }
135 }
136}
137
150
152{
153 ThreadCtrl *result = nullptr;
154
156 if (s_ReapCount > 0U)
157 {
158 result = s_ReapQueue[s_ReapHead];
160 --s_ReapCount;
161 }
163
164 return result;
165}
166
167void ReaperEntry(void * /*arg*/)
168{
169 for (;;)
170 {
171 STK_C_ASSERT(s_ReapSem != nullptr);
173
174 ThreadCtrl *ctrl = DequeueReap();
175 if (ctrl != nullptr)
176 {
177 WaitForTaskGone(ctrl->task);
179 }
180 }
181}
182
183// Exactly-once trigger: called both when a detached thread finishes and when an
184// already-running thread is detached. Whichever observes "finished && detached"
185// first hands the thread off to the reaper.
187{
188 bool do_reap = false;
189
191 if (ctrl->finished && ctrl->detached && !ctrl->reclaimed)
192 {
193 ctrl->reclaimed = true;
194 do_reap = true;
195 }
197
198 if (do_reap)
199 {
200 EnsureReaper();
201 EnqueueReap(ctrl);
203 }
204}
205
206// -----------------------------------------------------------------------------
207// Slot management
208// -----------------------------------------------------------------------------
210{
211 ThreadCtrl *result = nullptr;
212
214 for (std::size_t i = 0U; i < STK_C_PTHREAD_MAX_THREADS; ++i)
215 {
216 if (!s_Threads[i].busy)
217 {
218 s_Threads[i] = ThreadCtrl();
219 s_Threads[i].busy = true;
220 result = &s_Threads[i];
221 break;
222 }
223 }
225
226 return result;
227}
228
229// Poll until 'task' no longer appears in the kernel's active task list, i.e. it has
230// been fully torn down (its ITask/OnExit teardown has completed) and it is safe to
231// free resources - most importantly, the stack it was running on.
233{
235
236 for (;;)
237 {
239
240 bool found = false;
241 for (std::size_t i = 0U; i < n; ++i)
242 {
243 if (buf[i] == task)
244 {
245 found = true;
246 break;
247 }
248 }
249
250 if (!found)
251 {
252 break;
253 }
254
255 stk_sleep_ms(1);
256 }
257}
258
260{
261 if (ctrl->done_event != nullptr)
262 {
264 ctrl->done_event = nullptr;
265 }
266
267 if (ctrl->stack_owned && (ctrl->stack != nullptr))
268 {
269 if (ctrl->stack_from_pool)
270 {
272 }
273 else
274 {
275 free(ctrl->stack);
276 }
277 }
278 ctrl->stack = nullptr;
279
281 ctrl->busy = false;
283}
284
285// -----------------------------------------------------------------------------
286// Trampoline
287// -----------------------------------------------------------------------------
288void PthreadTrampoline(void *arg)
289{
290 ThreadCtrl *const ctrl = static_cast<ThreadCtrl *>(arg);
291
292 stk_tls_set(ctrl);
293
294 void *const result = ctrl->start_routine(ctrl->arg);
295
296 RunKeyDestructors(ctrl);
297
299 ctrl->retval = result;
300 ctrl->finished = true;
302
304 MaybeReap(ctrl);
305
306 // Natural return: STK finishes/frees the underlying dynamic task automatically.
307 // A joiner (or the reaper, for detached threads) reclaims *our* resources
308 // (stack, control block) only after confirming via WaitForTaskGone() that this
309 // task has fully left the kernel's task list.
310}
311
312// -----------------------------------------------------------------------------
313// Absolute-deadline -> relative-timeout helper
314//
315// STK has no wall clock; abstime is interpreted as a point on the same timeline as
316// stk_time_now_ms() (see the "timedwait limitation" note in stk_c_pthread.h).
317// -----------------------------------------------------------------------------
318stk_timeout_t TimespecToRelativeTimeout(const struct timespec *abstime)
319{
320 const stk_time_t now_ms = stk_time_now_ms();
321 const stk_time_t target_ms = (static_cast<stk_time_t>(abstime->tv_sec) * 1000LL) +
322 (static_cast<stk_time_t>(abstime->tv_nsec) / 1000000LL);
323
324 stk_time_t rel_ms = target_ms - now_ms;
325 if (rel_ms < 0LL)
326 {
327 rel_ms = 0LL;
328 }
329 if (rel_ms > static_cast<stk_time_t>(INT32_MAX))
330 {
331 rel_ms = static_cast<stk_time_t>(INT32_MAX);
332 }
333
334 return stk_ticks_from_ms_clamped_to_timeout(static_cast<stk_timeout_t>(rel_ms));
335}
336
338{
339 if (m->__handle == nullptr)
340 {
342 if (m->__handle == nullptr)
343 {
344 m->__handle = stk_mutex_create(&m->__mem, sizeof(m->__mem));
345 }
347 }
348
349 return m->__handle;
350}
351
353{
354 if (c->__handle == nullptr)
355 {
357 if (c->__handle == nullptr)
358 {
359 c->__handle = stk_cv_create(&c->__mem, sizeof(c->__mem));
360 }
362 }
363
364 return c->__handle;
365}
366
368{
369 if (rw->__handle == nullptr)
370 {
372 if (rw->__handle == nullptr)
373 {
374 rw->__handle = stk_rwmutex_create(&rw->__mem, sizeof(rw->__mem));
375 }
377 }
378
379 return rw->__handle;
380}
381
383{
384 if (o->__handle == nullptr)
385 {
387 if (o->__handle == nullptr)
388 {
389 o->__handle = stk_mutex_create(&o->__mem, sizeof(o->__mem));
390 }
392 }
393
394 return o->__handle;
395}
396
397// -----------------------------------------------------------------------------
398// Thread-specific data (keys)
399// -----------------------------------------------------------------------------
401{
402 bool used;
403 void (*destructor)(void *);
404};
405
407
408// Runs at thread termination (natural return or pthread_exit()) for a thread that
409// was created via pthread_create() - see the "thread-specific data limitation"
410// note in stk_c_pthread.h. Mirrors POSIX: each pass calls the destructor for every
411// key whose value is currently non-NULL (clearing the value first, so a
412// destructor that reads it back via pthread_getspecific() sees NULL), and repeats
413// while any destructor call left a *new* non-NULL value, up to
414// PTHREAD_DESTRUCTOR_ITERATIONS passes.
416{
417 for (int iter = 0; iter < PTHREAD_DESTRUCTOR_ITERATIONS; ++iter)
418 {
419 bool any_ran = false;
420
421 for (std::size_t i = 0U; i < STK_C_PTHREAD_KEYS_MAX; ++i)
422 {
423 void *const value = ctrl->tsd[i];
424 if (value == nullptr) { continue; }
425
426 void (*destructor)(void *) = nullptr;
428 if (s_Keys[i].used) { destructor = s_Keys[i].destructor; }
430
431 ctrl->tsd[i] = nullptr; // clear before calling, per POSIX
432 if (destructor != nullptr)
433 {
434 destructor(value);
435 any_ran = true;
436 }
437 }
438
439 if (!any_ran) { break; }
440 }
441}
442
443} // anonymous namespace
444
445// =============================================================================
446// C-interface
447// =============================================================================
448extern "C" {
449
450// -----------------------------------------------------------------------------
451// Bootstrap
452// -----------------------------------------------------------------------------
454{
455 STK_C_ASSERT(kernel != nullptr);
456
457 s_BoundKernel = kernel;
458}
459
460// -----------------------------------------------------------------------------
461// Thread attributes
462// -----------------------------------------------------------------------------
464{
465 if (attr == nullptr) { return EINVAL; }
466
467 attr->__stack_bytes = 0U;
468 attr->__ext_stack = nullptr;
470 return 0;
471}
472
474{
475 if (attr == nullptr) { return EINVAL; }
476 return 0;
477}
478
479int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
480{
481 if ((attr == nullptr) || (stacksize == 0U)) { return EINVAL; }
482
483 attr->__stack_bytes = stacksize;
484 return 0;
485}
486
487int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
488{
489 if ((attr == nullptr) || (stacksize == nullptr)) { return EINVAL; }
490
491 *stacksize = (attr->__stack_bytes != 0U) ? attr->__stack_bytes : kDefaultStackBytes;
492 return 0;
493}
494
495int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)
496{
497 if ((attr == nullptr) || (stackaddr == nullptr) || (stacksize < sizeof(stk_word_t))) { return EINVAL; }
498 if ((reinterpret_cast<uintptr_t>(stackaddr) & STK_ALIGN_MASK) != 0U) { return EINVAL; }
499
500 attr->__ext_stack = static_cast<stk_word_t *>(stackaddr);
501 attr->__stack_bytes = stacksize;
502 return 0;
503}
504
505int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize)
506{
507 if ((attr == nullptr) || (stackaddr == nullptr) || (stacksize == nullptr)) { return EINVAL; }
508
509 *stackaddr = attr->__ext_stack;
510 *stacksize = attr->__stack_bytes;
511 return 0;
512}
513
515{
516 if ((attr == nullptr) ||
517 ((detachstate != PTHREAD_CREATE_JOINABLE) && (detachstate != PTHREAD_CREATE_DETACHED)))
518 {
519 return EINVAL;
520 }
521
522 attr->__detachstate = detachstate;
523 return 0;
524}
525
526int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)
527{
528 if ((attr == nullptr) || (detachstate == nullptr)) { return EINVAL; }
529
530 *detachstate = attr->__detachstate;
531 return 0;
532}
533
534// -----------------------------------------------------------------------------
535// Thread lifecycle
536// -----------------------------------------------------------------------------
537int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
538 void *(*start_routine)(void *), void *arg)
539{
540 STK_C_ASSERT(s_BoundKernel != nullptr);
541 STK_C_ASSERT(thread != nullptr);
542 STK_C_ASSERT(start_routine != nullptr);
543
544 if ((s_BoundKernel == nullptr) || (thread == nullptr) || (start_routine == nullptr))
545 {
546 return EINVAL;
547 }
548
550 if (ctrl == nullptr)
551 {
552 return EAGAIN;
553 }
554
555 const size_t stack_bytes = ((attr != nullptr) && (attr->__stack_bytes != 0U)) ?
557 const uint32_t stack_words =
558 static_cast<uint32_t>((stack_bytes + sizeof(stk_word_t) - 1U) / sizeof(stk_word_t));
559
560 if ((attr != nullptr) && (attr->__ext_stack != nullptr))
561 {
562 ctrl->stack = attr->__ext_stack;
563 ctrl->stack_owned = false;
564 ctrl->stack_from_pool = false;
565 }
566 else if (stack_bytes == kDefaultStackBytes)
567 {
568 stk_blockpool_t *const pool = EnsureStackPool();
569 void *const blk = (pool != nullptr) ? stk_blockpool_try_alloc(pool) : nullptr;
570 if (blk != nullptr)
571 {
572 ctrl->stack = static_cast<stk_word_t *>(blk);
573 ctrl->stack_owned = true;
574 ctrl->stack_from_pool = true;
575 }
576 else
577 {
578 ctrl->stack = static_cast<stk_word_t *>(malloc(stack_bytes));
579 ctrl->stack_owned = true;
580 ctrl->stack_from_pool = false;
581 }
582 }
583 else
584 {
585 ctrl->stack = static_cast<stk_word_t *>(malloc(stack_bytes));
586 ctrl->stack_owned = true;
587 ctrl->stack_from_pool = false;
588 }
589
590 if (ctrl->stack == nullptr)
591 {
593 ctrl->busy = false;
595 return EAGAIN;
596 }
597
598 ctrl->start_routine = start_routine;
599 ctrl->arg = arg;
600 ctrl->retval = nullptr;
601 ctrl->detached = ((attr != nullptr) && (attr->__detachstate == PTHREAD_CREATE_DETACHED));
602
603 ctrl->done_event = stk_event_create(&ctrl->done_event_mem, sizeof(ctrl->done_event_mem),
604 true /* manual_reset */);
605
606 ctrl->task = stk_task_create_user(PthreadTrampoline, ctrl, ctrl->stack, stack_words);
607 if (ctrl->task == nullptr)
608 {
609 if (ctrl->done_event != nullptr) { stk_event_destroy(ctrl->done_event); }
610 if (ctrl->stack_owned)
611 {
612 if (ctrl->stack_from_pool) { (void)stk_blockpool_free(s_StackPool, ctrl->stack); }
613 else { free(ctrl->stack); }
614 }
616 ctrl->busy = false;
618 return EAGAIN;
619 }
620
621 EnsureReaper();
622
624
625 *thread = ctrl;
626 return 0;
627}
628
629int pthread_join(pthread_t thread, void **retval)
630{
631 pthread_stk_ctrl_t *const ctrl = thread;
632 if (ctrl == nullptr) { return EINVAL; }
633
634 bool ok = false;
636 if (!ctrl->detached && !ctrl->joined)
637 {
638 ctrl->joined = true;
639 ok = true;
640 }
642
643 if (!ok) { return EINVAL; }
644
646
647 if (retval != nullptr)
648 {
649 *retval = ctrl->retval;
650 }
651
652 WaitForTaskGone(ctrl->task);
654 return 0;
655}
656
658{
659 pthread_stk_ctrl_t *const ctrl = thread;
660 if (ctrl == nullptr) { return EINVAL; }
661
662 bool ok = false;
664 if (!ctrl->joined && !ctrl->detached)
665 {
666 ctrl->detached = true;
667 ok = true;
668 }
670
671 if (!ok) { return EINVAL; }
672
673 MaybeReap(ctrl);
674 return 0;
675}
676
677void pthread_exit(void *retval)
678{
679 pthread_stk_ctrl_t *const ctrl = static_cast<pthread_stk_ctrl_t *>(stk_tls_get());
680
681 if (ctrl != nullptr)
682 {
683 RunKeyDestructors(ctrl);
684
686 ctrl->retval = retval;
687 ctrl->finished = true;
689
691 MaybeReap(ctrl);
692
693 STK_C_ASSERT(s_BoundKernel != nullptr);
695 }
696
697 // Safety net: if the task is somehow still running past the removal request
698 // (or ctrl was NULL, i.e. this wasn't called from a pthread_create()'d thread),
699 // park here instead of falling back into caller code that isn't expecting to
700 // regain control.
701 for (;;)
702 {
704 }
705}
706
708{
709 return static_cast<pthread_t>(stk_tls_get());
710}
711
713{
714 return (t1 == t2) ? 1 : 0;
715}
716
718{
719 stk_yield();
720 return 0;
721}
722
723// -----------------------------------------------------------------------------
724// Mutex
725// -----------------------------------------------------------------------------
727{
728 if (attr == nullptr) { return EINVAL; }
729
731 return 0;
732}
733
735{
736 if (attr == nullptr) { return EINVAL; }
737 return 0;
738}
739
741{
742 if (attr == nullptr) { return EINVAL; }
743 if (type != PTHREAD_MUTEX_NORMAL) { return ENOTSUP; }
744
745 attr->__type = type;
746 return 0;
747}
748
750{
751 if ((attr == nullptr) || (type == nullptr)) { return EINVAL; }
752
753 *type = attr->__type;
754 return 0;
755}
756
758{
759 if (mutex == nullptr) { return EINVAL; }
760 if ((attr != nullptr) && (attr->__type != PTHREAD_MUTEX_NORMAL)) { return ENOTSUP; }
761
762 mutex->__handle = stk_mutex_create(&mutex->__mem, sizeof(mutex->__mem));
763 return (mutex->__handle != nullptr) ? 0 : EAGAIN;
764}
765
767{
768 if (mutex == nullptr) { return EINVAL; }
769
770 if (mutex->__handle != nullptr)
771 {
773 mutex->__handle = nullptr;
774 }
775 return 0;
776}
777
779{
780 if (mutex == nullptr) { return EINVAL; }
781
783 return 0;
784}
785
787{
788 if (mutex == nullptr) { return EINVAL; }
789
790 return stk_mutex_trylock(EnsureMutex(mutex)) ? 0 : EBUSY;
791}
792
794{
795 if (mutex == nullptr) { return EINVAL; }
796 if (mutex->__handle == nullptr) { return EINVAL; }
797
799 return 0;
800}
801
802int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime)
803{
804 if ((mutex == nullptr) || (abstime == nullptr)) { return EINVAL; }
805
806 const bool locked = stk_mutex_timed_lock(EnsureMutex(mutex), TimespecToRelativeTimeout(abstime));
807 return locked ? 0 : ETIMEDOUT;
808}
809
810// -----------------------------------------------------------------------------
811// Condition variable
812// -----------------------------------------------------------------------------
814{
815 if (attr == nullptr) { return EINVAL; }
816
817 attr->__reserved = 0;
818 return 0;
819}
820
822{
823 if (attr == nullptr) { return EINVAL; }
824 return 0;
825}
826
828{
829 if (cond == nullptr) { return EINVAL; }
830
831 cond->__handle = stk_cv_create(&cond->__mem, sizeof(cond->__mem));
832 return (cond->__handle != nullptr) ? 0 : EAGAIN;
833}
834
836{
837 if (cond == nullptr) { return EINVAL; }
838
839 if (cond->__handle != nullptr)
840 {
842 cond->__handle = nullptr;
843 }
844 return 0;
845}
846
848{
849 if ((cond == nullptr) || (mutex == nullptr) || (mutex->__handle == nullptr)) { return EINVAL; }
850
851 (void)stk_cv_wait(EnsureCond(cond), mutex->__handle, STK_WAIT_INFINITE);
852 return 0;
853}
854
855int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
856{
857 if ((cond == nullptr) || (mutex == nullptr) || (mutex->__handle == nullptr) || (abstime == nullptr))
858 {
859 return EINVAL;
860 }
861
862 const bool signaled = stk_cv_wait(EnsureCond(cond), mutex->__handle, TimespecToRelativeTimeout(abstime));
863 return signaled ? 0 : ETIMEDOUT;
864}
865
867{
868 if (cond == nullptr) { return EINVAL; }
869
871 return 0;
872}
873
875{
876 if (cond == nullptr) { return EINVAL; }
877
879 return 0;
880}
881
882// -----------------------------------------------------------------------------
883// Read-write lock
884// -----------------------------------------------------------------------------
886{
887 if (attr == nullptr) { return EINVAL; }
888
889 attr->__reserved = 0;
890 return 0;
891}
892
894{
895 if (attr == nullptr) { return EINVAL; }
896 return 0;
897}
898
900{
901 if (rwlock == nullptr) { return EINVAL; }
902
903 rwlock->__handle = stk_rwmutex_create(&rwlock->__mem, sizeof(rwlock->__mem));
904 rwlock->__wrlocked = false;
905 return (rwlock->__handle != nullptr) ? 0 : EAGAIN;
906}
907
909{
910 if (rwlock == nullptr) { return EINVAL; }
911
912 if (rwlock->__handle != nullptr)
913 {
915 rwlock->__handle = nullptr;
916 }
917 return 0;
918}
919
921{
922 if (rwlock == nullptr) { return EINVAL; }
923
925 return 0;
926}
927
929{
930 if (rwlock == nullptr) { return EINVAL; }
931
932 return stk_rwmutex_try_read_lock(EnsureRWLock(rwlock)) ? 0 : EBUSY;
933}
934
935int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
936{
937 if ((rwlock == nullptr) || (abstime == nullptr)) { return EINVAL; }
938
939 const bool locked = stk_rwmutex_timed_read_lock(EnsureRWLock(rwlock), TimespecToRelativeTimeout(abstime));
940 return locked ? 0 : ETIMEDOUT;
941}
942
944{
945 if (rwlock == nullptr) { return EINVAL; }
946
948 rwlock->__wrlocked = true;
949 return 0;
950}
951
953{
954 if (rwlock == nullptr) { return EINVAL; }
955
956 if (!stk_rwmutex_trylock(EnsureRWLock(rwlock))) { return EBUSY; }
957
958 rwlock->__wrlocked = true;
959 return 0;
960}
961
962int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
963{
964 if ((rwlock == nullptr) || (abstime == nullptr)) { return EINVAL; }
965
966 if (!stk_rwmutex_timed_lock(EnsureRWLock(rwlock), TimespecToRelativeTimeout(abstime))) { return ETIMEDOUT; }
967
968 rwlock->__wrlocked = true;
969 return 0;
970}
971
973{
974 if (rwlock == nullptr) { return EINVAL; }
975 if (rwlock->__handle == nullptr) { return EINVAL; }
976
977 // See "pthread_rwlock_unlock() disambiguation" in stk_c_pthread.h: a write
978 // hold is always exclusive, so __wrlocked unambiguously identifies which
979 // underlying call the current holder must have made. Clear it before
980 // releasing so a racing new writer's own acquisition can't be clobbered.
981 if (rwlock->__wrlocked)
982 {
983 rwlock->__wrlocked = false;
985 }
986 else
987 {
989 }
990 return 0;
991}
992
993// -----------------------------------------------------------------------------
994// Spin lock
995// -----------------------------------------------------------------------------
997{
998 if (lock == nullptr) { return EINVAL; }
999 if (pshared != PTHREAD_PROCESS_PRIVATE) { return ENOTSUP; }
1000
1001 lock->__handle = stk_spinlock_create(&lock->__mem, sizeof(lock->__mem));
1002 return (lock->__handle != nullptr) ? 0 : EAGAIN;
1003}
1004
1006{
1007 if (lock == nullptr) { return EINVAL; }
1008
1009 if (lock->__handle != nullptr)
1010 {
1012 lock->__handle = nullptr;
1013 }
1014 return 0;
1015}
1016
1018{
1019 if ((lock == nullptr) || (lock->__handle == nullptr)) { return EINVAL; }
1020
1022 return 0;
1023}
1024
1026{
1027 if ((lock == nullptr) || (lock->__handle == nullptr)) { return EINVAL; }
1028
1029 return stk_spinlock_trylock(lock->__handle) ? 0 : EBUSY;
1030}
1031
1033{
1034 if ((lock == nullptr) || (lock->__handle == nullptr)) { return EINVAL; }
1035
1037 return 0;
1038}
1039
1040// -----------------------------------------------------------------------------
1041// Barrier
1042// -----------------------------------------------------------------------------
1044{
1045 if (attr == nullptr) { return EINVAL; }
1046
1047 attr->__reserved = 0;
1048 return 0;
1049}
1050
1052{
1053 if (attr == nullptr) { return EINVAL; }
1054 return 0;
1055}
1056
1058 unsigned int count)
1059{
1060 if (barrier == nullptr) { return EINVAL; }
1061 if (count == 0U) { return EINVAL; }
1062
1063 barrier->__handle = stk_barrier_create(&barrier->__mem, sizeof(barrier->__mem),
1064 static_cast<uint32_t>(count));
1065 return (barrier->__handle != nullptr) ? 0 : EAGAIN;
1066}
1067
1069{
1070 if (barrier == nullptr) { return EINVAL; }
1071
1072 if (barrier->__handle != nullptr)
1073 {
1074 stk_barrier_destroy(barrier->__handle);
1075 barrier->__handle = nullptr;
1076 }
1077 return 0;
1078}
1079
1081{
1082 if ((barrier == nullptr) || (barrier->__handle == nullptr)) { return EINVAL; }
1083
1085}
1086
1087// -----------------------------------------------------------------------------
1088// Once
1089// -----------------------------------------------------------------------------
1090int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
1091{
1092 if ((once_control == nullptr) || (init_routine == nullptr)) { return EINVAL; }
1093
1094 if (once_control->__state == 2) { return 0; } // fast path: already done, no lock needed
1095
1096 stk_mutex_t *const guard = EnsureOnceMutex(once_control);
1097 stk_mutex_lock(guard);
1098
1099 // Re-check under the lock: another thread may have finished init_routine()
1100 // (or be running it right now, in which case this lock() call already blocked
1101 // until it was done) between our unlocked fast-path check above and here.
1102 if (once_control->__state != 2)
1103 {
1104 once_control->__state = 1;
1105 init_routine();
1106 once_control->__state = 2;
1107 }
1108
1109 stk_mutex_unlock(guard);
1110 return 0;
1111}
1112
1113// -----------------------------------------------------------------------------
1114// Thread-specific data (keys)
1115// -----------------------------------------------------------------------------
1116int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
1117{
1118 if (key == nullptr) { return EINVAL; }
1119
1120 int result = EAGAIN;
1121
1123 for (std::size_t i = 0U; i < STK_C_PTHREAD_KEYS_MAX; ++i)
1124 {
1125 if (!s_Keys[i].used)
1126 {
1127 s_Keys[i].used = true;
1128 s_Keys[i].destructor = destructor;
1129 *key = static_cast<pthread_key_t>(i);
1130 result = 0;
1131 break;
1132 }
1133 }
1135
1136 return result;
1137}
1138
1140{
1141 if (key >= STK_C_PTHREAD_KEYS_MAX) { return EINVAL; }
1142
1143 int result = EINVAL;
1144
1146 if (s_Keys[key].used)
1147 {
1148 s_Keys[key].used = false;
1149 s_Keys[key].destructor = nullptr;
1150 result = 0;
1151 }
1153
1154 return result;
1155}
1156
1157int pthread_setspecific(pthread_key_t key, const void *value)
1158{
1159 if ((key >= STK_C_PTHREAD_KEYS_MAX) || !s_Keys[key].used) { return EINVAL; }
1160
1161 ThreadCtrl *const ctrl = static_cast<ThreadCtrl *>(stk_tls_get());
1162 if (ctrl == nullptr) { return EINVAL; } // not a pthread_create()'d task; see file docs
1163
1164 ctrl->tsd[key] = const_cast<void *>(value);
1165 return 0;
1166}
1167
1169{
1170 if ((key >= STK_C_PTHREAD_KEYS_MAX) || !s_Keys[key].used) { return nullptr; }
1171
1172 ThreadCtrl *const ctrl = static_cast<ThreadCtrl *>(stk_tls_get());
1173 if (ctrl == nullptr) { return nullptr; } // not a pthread_create()'d task; see file docs
1174
1175 return ctrl->tsd[key];
1176}
1177
1178// =============================================================================
1179} // extern "C"
1180// =============================================================================
C language binding/interface for SuperTinyKernel RTOS.
C language binding for stk::memory::BlockMemoryPool.
void * malloc(std::size_t size)
void free(void *ptr)
A minimal, POSIX-named pthreads-style API implemented on top of the STK C bindings (stk_c....
stk_rwmutex_t * stk_rwmutex_create(stk_rwmutex_mem_t *const membuf, uint32_t membuf_size)
Create an RWMutex (using provided memory).
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:568
void stk_task_set_name(stk_task_t *tsk, const char *tname)
Assign human-readable task name (for tracing/debugging).
Definition stk_c.cpp:596
void stk_barrier_destroy(stk_barrier_t *barrier)
Destroy a Barrier.
void stk_sem_signal(stk_sem_t *sem)
Signal/Release a semaphore resource.
#define STK_WAIT_INFINITE
Infinite timeout constant.
Definition stk_c.h:147
bool stk_event_set(stk_event_t *ev)
Set the event to signaled state.
void stk_mutex_destroy(stk_mutex_t *mtx)
Destroy a Mutex.
stk_barrier_t * stk_barrier_create(stk_barrier_mem_t *const membuf, uint32_t membuf_size, uint32_t count)
Create a Barrier (using provided memory).
bool stk_rwmutex_try_read_lock(stk_rwmutex_t *rw)
Try to acquire the read lock without blocking.
stk_sem_t * stk_sem_create(stk_sem_mem_t *const membuf, uint32_t membuf_size, uint32_t initial_count, uint32_t max_count)
Create a Semaphore (using provided memory).
void * stk_tls_get(void)
Get thread-local pointer (platform-specific slot).
bool stk_event_wait(stk_event_t *ev, stk_timeout_t timeout)
Wait for the event to become signaled.
bool stk_rwmutex_trylock(stk_rwmutex_t *rw)
Try to acquire the write lock without blocking.
bool stk_cv_wait(stk_cv_t *cv, stk_mutex_t *mtx, stk_timeout_t timeout)
Wait for a signal on the condition variable.
void stk_spinlock_destroy(stk_spinlock_t *slock)
Destroy the SpinLock.
void stk_cv_notify_one(stk_cv_t *cv)
Wake one task waiting on the condition variable.
bool stk_spinlock_trylock(stk_spinlock_t *slock)
Attempt to acquire the SpinLock immediately.
struct stk_kernel_t stk_kernel_t
Opaque handle to a kernel instance.
Definition stk_c.h:126
void stk_event_destroy(stk_event_t *ev)
Destroy an Event.
stk_event_t * stk_event_create(stk_event_mem_t *const membuf, uint32_t membuf_size, bool manual_reset)
Create an Event (using provided memory).
void stk_yield(void)
Voluntarily give up CPU to another ready task (cooperative yield).
Definition stk_c.cpp:651
bool stk_mutex_timed_lock(stk_mutex_t *mtx, stk_timeout_t timeout)
Try to lock the mutex with a timeout.
void stk_mutex_lock(stk_mutex_t *mtx)
Lock the mutex. Blocks until available.
void stk_rwmutex_lock(stk_rwmutex_t *rw)
Acquire the lock for exclusive writing. Blocks until available.
void stk_cv_destroy(stk_cv_t *cv)
Destroy a Condition Variable.
#define STK_C_KERNEL_MAX_TASKS
Maximum number of tasks per kernel instance (default: 4).
Definition stk_c.h:52
void stk_sleep(stk_timeout_t ticks)
Put current task to sleep (non-HRT kernels only).
Definition stk_c.cpp:646
bool stk_mutex_trylock(stk_mutex_t *mtx)
Try locking the mutex. Does not block if already locked.
void stk_rwmutex_read_lock(stk_rwmutex_t *rw)
Acquire the lock for shared reading. Blocks until available.
void stk_mutex_unlock(stk_mutex_t *mtx)
Unlock the mutex.
stk_timeout_t stk_ticks_from_ms_clamped_to_timeout(stk_timeout_t ms)
Get ticks from milliseconds using current kernel tick resolution, clamped to the maximum value repres...
Definition stk_c.cpp:639
#define STK_ALIGN_MASK
Alignment mask.
Definition stk_c.h:159
bool stk_sem_wait(stk_sem_t *sem, stk_timeout_t timeout)
Wait for a semaphore resource.
void stk_kernel_schedule_task_removal(stk_kernel_t *k, stk_task_t *task)
Schedule removal of a running task from the kernel on the next tick.
Definition stk_c.cpp:453
void stk_sleep_ms(stk_timeout_t ms)
Put current task to sleep (non-HRT kernels only).
Definition stk_c.cpp:648
size_t stk_kernel_enumerate_tasks(stk_kernel_t *k, stk_task_t **tasks, size_t max_count)
Enumerate all currently active tasks.
Definition stk_c.cpp:478
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:429
void stk_spinlock_lock(stk_spinlock_t *slock)
Acquire the SpinLock (recursive).
void stk_spinlock_unlock(stk_spinlock_t *slock)
Release the SpinLock.
void stk_tls_set(void *ptr)
Set thread-local pointer.
void stk_rwmutex_read_unlock(stk_rwmutex_t *rw)
Release the shared reader lock.
stk_spinlock_t * stk_spinlock_create(stk_spinlock_mem_t *const membuf, uint32_t membuf_size)
Create a recursive SpinLock.
void stk_critical_section_enter()
Enter global critical section - disable context switches on current core.
Definition stk_c.cpp:682
uintptr_t stk_word_t
CPU register type.
Definition stk_c.h:94
#define STK_C_ASSERT(e)
Assertion macro used inside STK C bindings.
Definition stk_c.h:75
stk_cv_t * stk_cv_create(stk_cv_mem_t *const membuf, uint32_t membuf_size)
Create a Condition Variable (using provided memory).
stk_mutex_t * stk_mutex_create(stk_mutex_mem_t *const membuf, uint32_t membuf_size)
Create a Mutex (using provided memory).
int64_t stk_time_t
Time value.
Definition stk_c.h:108
stk_time_t stk_time_now_ms(void)
Returns current time in milliseconds since kernel start.
Definition stk_c.cpp:637
bool stk_rwmutex_timed_lock(stk_rwmutex_t *rw, stk_timeout_t timeout)
Try to acquire the write lock with a timeout.
void stk_rwmutex_destroy(stk_rwmutex_t *rw)
Destroy an RWMutex.
bool stk_rwmutex_timed_read_lock(stk_rwmutex_t *rw, stk_timeout_t timeout)
Try to acquire the read lock with a timeout.
int32_t stk_timeout_t
Timeout value.
Definition stk_c.h:113
void stk_critical_section_exit()
Leave global critical section - re-enable context switches.
Definition stk_c.cpp:687
void stk_cv_notify_all(stk_cv_t *cv)
Wake all tasks waiting on the condition variable.
void stk_rwmutex_unlock(stk_rwmutex_t *rw)
Release the exclusive writer lock.
bool stk_barrier_wait(stk_barrier_t *barrier)
Block the calling task until count tasks have called stk_barrier_wait().
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)
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)
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)
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)
void pthread_exit(void *retval)
Terminate the calling thread.
int pthread_mutex_trylock(pthread_mutex_t *mutex)
int pthread_join(pthread_t thread, void **retval)
Block until the given joinable thread finishes, then reclaim its resources.
#define PTHREAD_MUTEX_DEFAULT
int pthread_condattr_init(pthread_condattr_t *attr)
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)
int pthread_mutex_destroy(pthread_mutex_t *mutex)
#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.
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)
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
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_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type)
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_spin_destroy(pthread_spinlock_t *lock)
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)
int pthread_equal(pthread_t t1, pthread_t t2)
Compare two thread handles for equality.
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
int pthread_barrierattr_init(pthread_barrierattr_t *attr)
int pthread_cond_destroy(pthread_cond_t *cond)
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.
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)
#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 *)
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 currently requested stack size in bytes (0 = default).
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)
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize)
Get the previously-set external stack (NULL/0 if none set).
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)
#define PTHREAD_CREATE_JOINABLE
int pthread_cond_broadcast(pthread_cond_t *cond)
stk_timeout_t TimespecToRelativeTimeout(const struct timespec *abstime)
stk_mutex_t * EnsureOnceMutex(pthread_once_t *o)
stk_mutex_t * EnsureMutex(pthread_mutex_t *m)
stk_cv_t * EnsureCond(pthread_cond_t *c)
void ReleaseThreadResources(ThreadCtrl *ctrl)
stk_rwmutex_t * EnsureRWLock(pthread_rwlock_t *rw)
Opaque memory container for an Event instance.
Definition stk_c.h:1095
Opaque memory container for a Semaphore instance.
Definition stk_c.h:1172
stk_event_mem_t done_event_mem
stk_event_t * done_event
void *(* start_routine)(void *)
Thread creation attributes.
stk_word_t * __ext_stack
Mutex attributes.
A pthread mutex.
stk_mutex_mem_t __mem
stk_mutex_t * __handle
Condition variable attributes (currently no settable properties).
A pthread condition variable.
stk_cv_t * __handle
stk_cv_mem_t __mem
Read-write lock attributes (currently no settable properties).
A pthread read-write lock.
stk_rwmutex_t * __handle
stk_rwmutex_mem_t __mem
A pthread spinlock.
stk_spinlock_mem_t __mem
stk_spinlock_t * __handle
Barrier attributes (currently no settable properties).
A pthread barrier.
stk_barrier_mem_t __mem
stk_barrier_t * __handle
A pthread_once() control object.
stk_mutex_mem_t __mem
stk_mutex_t * __handle