/* * M*LIB - Thin Mutex & Thread wrapper * * Copyright (c) 2017-2023, Patrick Pelissier * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * + Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MSTARLIB_MUTEX_H #define MSTARLIB_MUTEX_H /* Auto-detect the thread backend to use if the user has not override it */ #ifndef M_USE_THREAD_BACKEND # if defined(INC_FREERTOS_H) # define M_USE_THREAD_BACKEND 4 # elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \ && !defined(__STDC_NO_THREADS__) # define M_USE_THREAD_BACKEND 1 # elif defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) # define M_USE_THREAD_BACKEND 2 # else # define M_USE_THREAD_BACKEND 3 # endif #endif /****************************** C11 version ********************************/ #if M_USE_THREAD_BACKEND == 1 #include #include #include #include "m-core.h" M_BEGIN_PROTECTED_CODE /* Define a mutex type based on C11 definition */ typedef mtx_t m_mutex_t[1]; /* Define a condition variable type based on C11 definition */ typedef cnd_t m_cond_t[1]; /* Define a thread type based on C11 definition */ typedef thrd_t m_thread_t[1]; /* Initialize the mutex (constructor) */ M_INLINE void m_mutex_init(m_mutex_t m) { int rc = mtx_init(m, mtx_plain); // Abort program in case of initialization failure // There is really nothing else to do if a mutex cannot be constructed M_ASSERT_INIT (rc == thrd_success, "mutex"); } /* Clear the mutex (destructor) */ M_INLINE void m_mutex_clear(m_mutex_t m) { mtx_destroy(m); } /* Lock the mutex */ M_INLINE void m_mutex_lock(m_mutex_t m) { mtx_lock(m); } /* Unlock the mutex */ M_INLINE void m_mutex_unlock(m_mutex_t m) { mtx_unlock(m); } /* Initialize the condition variable (constructor) */ M_INLINE void m_cond_init(m_cond_t c) { int rc = cnd_init(c); // Abort program in case of initialization failure // There is really nothing else to do if the object cannot be constructed M_ASSERT_INIT (rc == thrd_success, "conditional variable"); } /* Clear the condition variable (destructor) */ M_INLINE void m_cond_clear(m_cond_t c) { cnd_destroy(c); } /* Signal the condition variable to at least one waiting thread */ M_INLINE void m_cond_signal(m_cond_t c) { cnd_signal(c); } /* Signal the condition variable to all waiting threads */ M_INLINE void m_cond_broadcast(m_cond_t c) { cnd_broadcast(c); } /* Wait for signaling the condition variable by another thread */ M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m) { cnd_wait(c, m); } /* Create the thread (constructor) and start it */ M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void* arg) { int rc = thrd_create(t, (int(*)(void*))(void(*)(void))func, arg); // Abort program in case of initialization failure M_ASSERT_INIT (rc == thrd_success, "thread"); } /* Wait for the thread to terminate and destroy it (destructor) */ M_INLINE void m_thread_join(m_thread_t t) { int rc = thrd_join(*t, NULL); M_ASSERT (rc == thrd_success); // Avoid warning about variable unused. (void) rc; } /* The thread has nothing meaningfull to do. Inform the OS to let other threads be scheduled */ M_INLINE void m_thread_yield(void) { thrd_yield(); } /* Sleep the thread for at least usec microseconds. Return true if the sleep was successful (or we cannot know) */ M_INLINE bool m_thread_sleep(unsigned long long usec) { struct timespec tv; tv.tv_sec = (long) (usec / 1000000ULL); tv.tv_nsec = (long) ((usec % 1000000ULL) * 1000UL); int retval = thrd_sleep(&tv, NULL); return retval == 0; } // a helper structure for m_once_call typedef once_flag m_once_t[1]; // Initial value for m_once_t #define M_ONCE_INIT_VALUE { ONCE_FLAG_INIT } // Call the function exactly once M_INLINE void m_once_call(m_once_t o, void (*func)(void)) { call_once(o,func); } // Attribute to use to allocate a global variable to a thread. #define M_THREAD_ATTR _Thread_local M_END_PROTECTED_CODE /****************************** WIN32 version ******************************/ #elif M_USE_THREAD_BACKEND == 2 /* CLANG provides some useless and wrong warnings: * - _WIN32_WINNT starts with '_' which is reserved by the standard * as per the MSVC compiler, it is needed to be defined by the user * to define which version of windows it want to be compatible with. * - windows.h may be different than the case used by the file sytem * there is however no normalized case. * * So, theses warnings have to be ignored and are disabled. * * We cannot add theses warnings in M_BEGIN_PROTECTED_CODE * as they need to be disabled **BEFORE** including any system header * and m-core includes some system headers. * So we need to disable them explictly here. */ #if defined(__clang__) && __clang_major__ >= 4 _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wreserved-id-macro\"") _Pragma("clang diagnostic ignored \"-Wnonportable-system-include-path\"") #endif /* CriticalSection & ConditionVariable are available from Windows Vista */ #ifndef WINVER #define WINVER _WIN32_WINNT_VISTA #endif #ifndef _WIN32_WINNT #define _WIN32_WINNT _WIN32_WINNT_VISTA #endif /* Include system headers */ #include #include #include #include "m-core.h" #if defined(__clang__) && __clang_major__ >= 4 _Pragma("clang diagnostic pop") #endif M_BEGIN_PROTECTED_CODE /* Define a thread type based on WINDOWS definition */ typedef HANDLE m_thread_t[1]; /* Define a mutex type based on WINDOWS definition */ typedef CRITICAL_SECTION m_mutex_t[1]; /* Define a condition variable type based on WINDOWS definition */ typedef CONDITION_VARIABLE m_cond_t[1]; /* Initialize a mutex (Constructor)*/ M_INLINE void m_mutex_init(m_mutex_t m) { InitializeCriticalSection(m); } /* Clear a mutex (destructor) */ M_INLINE void m_mutex_clear(m_mutex_t m) { DeleteCriticalSection(m); } /* Lock a mutex */ M_INLINE void m_mutex_lock(m_mutex_t m) { EnterCriticalSection(m); } /* Unlock a mutex */ M_INLINE void m_mutex_unlock(m_mutex_t m) { LeaveCriticalSection(m); } /* Initialize a condition variable (constructor) */ M_INLINE void m_cond_init(m_cond_t c) { InitializeConditionVariable(c); } /* Clear a condition variable (destructor) */ M_INLINE void m_cond_clear(m_cond_t c) { (void) c; // There is no destructor for this object. } /* Signal a condition variable to at least one waiting thread */ M_INLINE void m_cond_signal(m_cond_t c) { WakeConditionVariable(c); } /* Signal a condition variable to all waiting threads */ M_INLINE void m_cond_broadcast(m_cond_t c) { WakeAllConditionVariable(c); } /* Wait for a condition variable */ M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m) { SleepConditionVariableCS(c, m, INFINITE); } /* Create a thread (constructor) and start it */ M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void *arg) { *t = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) (uintptr_t) func, arg, 0, NULL); M_ASSERT_INIT (*t != NULL, "thread"); } /* Wait for the thread to terminate and destroy it (destructor) */ M_INLINE void m_thread_join(m_thread_t t) { DWORD dwWaitResult = WaitForSingleObject(*t, INFINITE); (void) dwWaitResult; M_ASSERT (dwWaitResult == WAIT_OBJECT_0); CloseHandle(*t); } /* The thread has nothing meaningfull to do. Inform the OS to let other threads be scheduled */ M_INLINE void m_thread_yield(void) { Sleep(0); } /* Sleep the thread for at least usec microseconds Return true if the sleep was successful */ M_INLINE bool m_thread_sleep(unsigned long long usec) { LARGE_INTEGER ft; M_ASSERT (usec <= LLONG_MAX); ft.QuadPart = -(10LL*(long long) usec); HANDLE hd = CreateWaitableTimer(NULL, TRUE, NULL); M_ASSERT_INIT (hd != NULL, "timer"); SetWaitableTimer(hd, &ft, 0, NULL, NULL, 0); DWORD dwWaitResult = WaitForSingleObject(hd, INFINITE); CloseHandle(hd); return dwWaitResult == WAIT_OBJECT_0; } typedef INIT_ONCE m_once_t[1]; #define M_ONCE_INIT_VALUE { INIT_ONCE_STATIC_INIT } M_INLINE BOOL CALLBACK m_once_callback( PINIT_ONCE InitOnce, PVOID Parameter, PVOID *lpContext) { void (*func)(void); (void) InitOnce; (void) lpContext; func = (void (*)(void))(uintptr_t) Parameter; (*func)(); return TRUE; } M_INLINE void m_once_call(m_once_t o, void (*func)(void)) { InitOnceExecuteOnce(o, m_once_callback, (void*)(intptr_t)func, NULL); } #if defined(_MSC_VER) // Attribute to use to allocate a global variable to a thread (MSVC def). # define M_THREAD_ATTR __declspec( thread ) #else // Attribute to use to allocate a global variable to a thread (GCC def). # define M_THREAD_ATTR __thread #endif M_END_PROTECTED_CODE /**************************** PTHREAD version ******************************/ #elif M_USE_THREAD_BACKEND == 3 #include #ifdef _POSIX_PRIORITY_SCHEDULING #include #endif #include #include #include #include #include #include "m-core.h" M_BEGIN_PROTECTED_CODE /* Define a mutex type based on PTHREAD definition */ typedef pthread_mutex_t m_mutex_t[1]; /* Define a condition variable type based on PTHREAD definition */ typedef pthread_cond_t m_cond_t[1]; /* Define a thread type based on PTHREAD definition */ typedef pthread_t m_thread_t[1]; /* Initialize the mutex (constructor) */ M_INLINE void m_mutex_init(m_mutex_t m) { int _rc = pthread_mutex_init(m, NULL); // Abort program in case of initialization failure // There is really nothing else to do if a mutex cannot be constructed M_ASSERT_INIT (_rc == 0, "mutex"); } /* Clear the mutex (destructor) */ M_INLINE void m_mutex_clear(m_mutex_t m) { pthread_mutex_destroy(m); } /* Lock the mutex */ M_INLINE void m_mutex_lock(m_mutex_t m) { pthread_mutex_lock(m); } /* Unlock the mutex */ M_INLINE void m_mutex_unlock(m_mutex_t m) { pthread_mutex_unlock(m); } /* Lazy lock initialization */ #define M_MUTEXI_INIT_VALUE { PTHREAD_MUTEX_INITIALIZER } /* Internal function compatible with lazy lock */ M_INLINE void m_mutexi_lazy_lock(m_mutex_t m) { pthread_mutex_lock(m); } /* Initialize the condition variable (constructor) */ M_INLINE void m_cond_init(m_cond_t c) { int _rc = pthread_cond_init(c, NULL); // Abort program in case of initialization failure // There is really nothing else to do if a mutex cannot be constructed M_ASSERT_INIT (_rc == 0, "conditional variable"); } /* Clear the condition variable (destructor) */ M_INLINE void m_cond_clear(m_cond_t c) { pthread_cond_destroy(c); } /* Signal a condition variable to at least a waiting thread */ M_INLINE void m_cond_signal(m_cond_t c) { pthread_cond_signal(c); } /* Signal a condition variable to all waiting threads */ M_INLINE void m_cond_broadcast(m_cond_t c) { pthread_cond_broadcast(c); } /* Waiting for a condition variable */ M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m) { pthread_cond_wait(c, m); } /* Create a thread (constructor) and start it */ M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void *arg) { int _rc = pthread_create(t, NULL, (void*(*)(void*))(void(*)(void))func, arg); M_ASSERT_INIT (_rc == 0, "thread"); } /* Wait for the thread to terminate and destroy it (destructor) */ M_INLINE void m_thread_join(m_thread_t t) { int _rc = pthread_join(*t, NULL); (void)_rc; // Avoid warning about variable unused. M_ASSERT (_rc == 0); } /* The thread has nothing meaningfull to do. Inform the OS to let other threads be scheduled */ M_INLINE void m_thread_yield(void) { #ifdef _POSIX_PRIORITY_SCHEDULING sched_yield(); #endif } /* Sleep for at least usec microseconds Return true if the sleep was successful */ M_INLINE bool m_thread_sleep(unsigned long long usec) { struct timeval tv; /* We don't want to use usleep or nanosleep so that we remain compatible with strict C99 build */ tv.tv_sec = (time_t) (usec / 1000000ULL); tv.tv_usec = (suseconds_t) (usec % 1000000ULL); int retval = select(1, NULL, NULL, NULL, &tv); return retval == 0; } typedef pthread_once_t m_once_t[1]; #define M_ONCE_INIT_VALUE { PTHREAD_ONCE_INIT } M_INLINE void m_once_call(m_once_t o, void (*func)(void)) { pthread_once(o,func); } #if defined(__GNUC__) # define M_THREAD_ATTR __thread #else # define M_THREAD_ATTR /* Not supported */ #endif M_END_PROTECTED_CODE /****************************** FreeRTOS version ********************************/ #elif M_USE_THREAD_BACKEND == 4 #include #include #include #include "m-core.h" M_BEGIN_PROTECTED_CODE /* Default value for the stack */ #ifndef M_USE_TASK_STACK_SIZE #define M_USE_TASK_STACK_SIZE configMINIMAL_STACK_SIZE #endif /* Default value for the priority tasks */ #ifndef M_USE_TASK_PRIORITY #define M_USE_TASK_PRIORITY ( tskIDLE_PRIORITY ) #endif /* Define a mutex type based on FreeRTOS definition */ typedef struct m_mutex_s { SemaphoreHandle_t handle; StaticSemaphore_t MutexBuffer; } m_mutex_t[1]; /* Define a thread type based on FreeRTOS definition */ typedef struct m_cond_s { SemaphoreHandle_t handle; StaticSemaphore_t SemBuffer; unsigned int NumThreadWaiting; } m_cond_t[1]; /* Define a thread type based on FreeRTOS definition */ typedef struct m_thread_s { SemaphoreHandle_t SemHandle; StaticSemaphore_t SemBuffer; TaskHandle_t TaskHandle; StaticTask_t TaskBuffer; void (*EntryPoint)(void *); void* ArgsEntryPoint; StackType_t* StackBuffer; } m_thread_t[1]; /* Initialize the mutex (constructor) */ M_INLINE void m_mutex_init(m_mutex_t m) { /* Create a mutex semaphore without using any dynamic allocation */ m->handle = xSemaphoreCreateMutexStatic(&m->MutexBuffer); // It cannot fail, so we won't use M_ASSERT_INIT M_ASSERT(m->handle); } /* Clear the mutex (destructor) */ M_INLINE void m_mutex_clear(m_mutex_t m) { vSemaphoreDelete(m->handle); } /* Lock the mutex */ M_INLINE void m_mutex_lock(m_mutex_t m) { xSemaphoreTake(m->handle, portMAX_DELAY); } /* Unlock the mutex */ M_INLINE void m_mutex_unlock(m_mutex_t m) { xSemaphoreGive(m->handle); } /* Initialize the condition variable (constructor) */ M_INLINE void m_cond_init(m_cond_t c) { c->NumThreadWaiting = 0; // Create a semaphore to implement the conditional variable // Initial value is 0 and valid range is <= 0 c->handle = xSemaphoreCreateCountingStatic( INT_MAX, 0, &c->SemBuffer ); // It cannot fail, so we won't use M_ASSERT_INIT M_ASSERT(c->handle); } /* Clear the condition variable (destructor) */ M_INLINE void m_cond_clear(m_cond_t c) { vSemaphoreDelete(c->handle); } /* Signal the condition variable to at least one waiting thread */ M_INLINE void m_cond_signal(m_cond_t c) { // This function is called within the mutex lock // NumThreadWaiting doesn't need to be atomic if (c->NumThreadWaiting > 0) { // Wakeup one thread by posting on the semaphore xSemaphoreGive(c->handle); } // Otherwise there is no waiting thread, so nothing to signal } /* Signal the condition variable to all waiting threads */ M_INLINE void m_cond_broadcast(m_cond_t c) { // This function is called within the mutex lock // NumThreadWaiting doesn't need to be atomic if (c->NumThreadWaiting > 0) { // Wakeup all thread by posting on the semaphore // as many times as there are waiting threads for(unsigned i = 0; i < c->NumThreadWaiting; i++) { xSemaphoreGive(c->handle); } } // Otherwise there is no waiting thread, so nothing to signal } /* Wait for signaling the condition variable by another thread */ M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m) { // This function is called within the mutex lock // Increment the number of waiting thread c->NumThreadWaiting ++; m_mutex_unlock(m); // Wait for post in the semaphore xSemaphoreTake(c->handle, portMAX_DELAY); m_mutex_lock(m); c->NumThreadWaiting --; } M_INLINE void m_thr3ad_wrapper( void *args) { struct m_thread_s *thread_ptr = args; thread_ptr->EntryPoint(thread_ptr->ArgsEntryPoint); // Give back the semaphore. xSemaphoreGive(thread_ptr->SemHandle); // Wait for destruction while (true) { vTaskSuspend(NULL); } } /* Create the thread (constructor) and start it */ M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void* arg) { // Create a semaphore to implement the final wait t->SemHandle = xSemaphoreCreateCountingStatic( 1, 0, &t->SemBuffer ); M_ASSERT(t->SemHandle); // Save the argument to the thread t->EntryPoint = func; t->ArgsEntryPoint = arg; // Allocate the stack t->StackBuffer = pvPortMalloc( sizeof (StackType_t) * M_USE_TASK_STACK_SIZE); M_ASSERT_INIT(t->StackBuffer, "STACK"); // Create the task without using any dynamic allocation t->TaskHandle = xTaskCreateStatic(m_thr3ad_wrapper, "M*LIB", M_USE_TASK_STACK_SIZE, (void*) t, M_USE_TASK_PRIORITY, t->StackBuffer, &t->TaskBuffer); // It cannot fail, so we won't use M_ASSERT_INIT M_ASSERT(t->TaskHandle); } /* Wait for the thread to terminate and destroy it (destructor) */ M_INLINE void m_thread_join(m_thread_t t) { xSemaphoreTake(t->SemHandle, portMAX_DELAY); vTaskDelete(t->TaskHandle); vPortFree(t->StackBuffer); vSemaphoreDelete(t->SemHandle); t->TaskHandle = 0; t->StackBuffer = 0; t->SemHandle = 0; } /* The thread has nothing meaningfull to do. Inform the OS to let other threads be scheduled */ M_INLINE void m_thread_yield(void) { taskYIELD(); } /* Sleep the thread for at least usec microseconds. Return true if the sleep was successful */ M_INLINE bool m_thread_sleep(unsigned long long usec) { TickType_t delay = (TickType_t) (usec / portTICK_PERIOD_MS / 1000ULL); vTaskDelay(delay); return true; } // a helper structure for m_once_call typedef struct { atomic_int count; } m_once_t[1]; // Initial value for m_once_t #define M_ONCE_INIT_VALUE { { M_ATOMIC_VAR_INIT(0) } } // Call the function exactly once M_INLINE void m_once_call(m_once_t o, void (*func)(void)) { if (atomic_load(&o->count) != 2) { int n = 0; if (atomic_compare_exchange_strong( &o->count, &n, 1)) { // First thread success func(); atomic_store(&o->count, 2); } // Wait for function call (FIXME: priority inversion possible?) while (atomic_load(&o->count) != 2) { m_thread_yield(); } } // Already called. Nothing to do } // Attribute to use to allocate a global variable to a thread. #define M_THREAD_ATTR __thread M_END_PROTECTED_CODE /******************************** INVALID VALUE **********************************/ #else # error Value of M_USE_THREAD_BACKEND is incorrect. Please see the documentation for valid usage. #endif // TODO: Obsolete M_LOCK macro. /* M_LOCK macro. Allow simple locking encapsulation. USAGE: static M_LOCK_DECL(name); int f(int n) { M_LOCK(name) { // Exclusive access } } */ /* NOTE: Either using direct support by the OS (WIN32/PTHREAD) or using C11's ONCE mechanism */ #ifdef M_MUTEXI_INIT_VALUE # define M_LOCK_DECL(name) m_mutex_t name = M_MUTEXI_INIT_VALUE # define M_LOCK(name) \ M_LOCKI_DO(name, M_C(local_cont_, __LINE__), m_mutexi_lazy_lock, m_mutex_unlock) #else # define M_LOCK_DECL(name) \ m_mutex_t name; \ static void M_C(m_mutex_init_, name)(void) { \ m_mutex_init(name); \ } \ m_once_t M_C(m_once_, name) = M_ONCE_INIT_VALUE # define M_LOCKI_BY_ONCE(name) \ (m_once_call(M_C(m_once_, name), M_C(m_mutex_init_, name)), \ m_mutex_lock(name), (void) 0 ) # define M_LOCK(name) \ M_LOCKI_DO(name, M_C(local_cont_, __LINE__), M_LOCKI_BY_ONCE, m_mutex_unlock) #endif #define M_LOCKI_DO(name, cont, lock_func, unlock_func) \ for(bool cont = true \ ; cont && (lock_func (name), true); \ (unlock_func (name), cont = false)) #endif