1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
|
#include "XenomaiLock.h"
#include <pthread.h>
#include <error.h>
#include <string.h>
#include <cobalt/sys/cobalt.h>
#include <xenomai/init.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <unistd.h>
#include <sys/syscall.h>
#ifndef __COBALT__
# error This should be compiled with __COBALT__ and the appropriate Xenomai headers in the #include path
#endif // __COBALT__
// All __wrap_* calls below are Xenomai libcobalt calls
//#define PRINT_XENO_LOCK
#ifdef PRINT_XENO_LOCK
# define xprintf printf
# define xfprintf fprintf
#else // PRINT_XENO_LOCK
# define xprintf(...)
# define xfprintf(...)
#endif // PRINT_XENO_LOCK
// Standard Linux `gettid(2)` not available on Bela
static inline pid_t getTid() {
pid_t tid = syscall(SYS_gettid);
return tid;
}
// throughout, we use heuristics to check whether Xenomai needs to be
// initialised and whether the current thread is a Xenomai thread.
// See https://www.xenomai.org/pipermail/xenomai/2019-January/040203.html
static void initializeXenomai() {
xprintf("initializeXenomai\n");
int argc = 2;
char blankOpt[] = "";
#ifdef PRINT_XENO_LOCK
char traceOpt[] = "--trace";
#else // PRINT_XENO_LOCK
char traceOpt[] = "";
#endif // PRINT_XENO_LOCK
char* const argv[argc] = { blankOpt, traceOpt };
char* const* argvPtrs[argc] = { &argv[0], &argv[1] };
xenomai_init(&argc, argvPtrs);
}
static bool turnIntoCobaltThread(bool recurred = false) {
struct sched_param param;
memset(¶m, 0, sizeof(param));
int policy;
// Guaranteed to succeed as pthread_self() cannot fail and pthread_getschedparam()'s only error condition is when
// the given thread does not exist.
pthread_getschedparam(pthread_self(), &policy, ¶m);
pid_t tid = getTid();
if (int ret = __wrap_sched_setscheduler(tid, policy, ¶m)) {
fprintf(stderr, "Warning: unable to turn current thread into a Xenomai thread : (%d) %s\n", -ret,
strerror(-ret));
initializeXenomai();
if (!recurred)
return turnIntoCobaltThread(true);
else
return false;
}
xprintf("Turned thread %d into a Cobalt thread %s\n", tid, recurred ? "with recursion" : "");
return true;
}
XenomaiInitializer::XenomaiInitializer() { initializeXenomai(); }
XenomaiMutex::XenomaiMutex() {
xprintf("Construct mutex\n");
if (int ret = __wrap_pthread_mutex_init(&m_mutex, NULL)) {
if (EPERM != ret) {
xprintf("__wrap_thread_mutex_init failed with %d %s\n", ret, strerror(ret));
return;
} else {
xprintf("mutex init returned EPERM\n");
initializeXenomai();
if (int ret = __wrap_pthread_mutex_init(&m_mutex, NULL)) {
fprintf(stderr, "Error: unable to initialize mutex : (%d) %s\n", ret, strerror(-ret));
return;
}
}
}
m_enabled = true;
}
XenomaiMutex::~XenomaiMutex() {
xprintf("Destroy mutex %p\n", &m_mutex);
if (m_enabled)
__wrap_pthread_mutex_destroy(&m_mutex);
}
// a helper function to try
// - call func()
// - if it fails, try to turn the current thread into a cobalt thread and call func() again
// - if it fails again, just fail
// - return true if it succeeds, or false if it fails
// id and name are just for debugging purposes, while m_enabled is there because it saves duplicating some lines
template <typename F, typename T> static bool tryOrRetryImpl(F&& func, bool m_enabled, T* id, const char* name) {
xprintf("tid: %d ", getTid());
if (!m_enabled) {
xfprintf(stderr, "%s disabled %p\n", name, id);
return false;
}
xprintf("%s %p\n", name, id);
int ret = func();
// 0 is "success" (or at least meaningful failure)
if (0 == ret) {
return true;
} else if (EPERM != ret) {
return false;
} else {
// if we got EPERM, we are not a Xenomai thread
if (!turnIntoCobaltThread()) {
xfprintf(stderr, "%s %p could not turn into cobalt\n", name, id);
return false;
}
}
// retry after becoming a cobalt thread
ret = func();
if (0 == ret) {
return true;
} else {
xfprintf(stderr, "%s %p failed after having turned into cobalt: %d\n", name, id, ret);
return false;
}
}
// Helper macro to insert this-ptr and function name
#define tryOrRetry(_func_, _enabled_) tryOrRetryImpl(_func_, _enabled_, this, __func__)
// condition resource_deadlock_would_occur instead of deadlocking. https://en.cppreference.com/w/cpp/thread/mutex/lock
bool XenomaiMutex::try_lock() {
return tryOrRetry([this]() { return __wrap_pthread_mutex_trylock(&this->m_mutex); }, m_enabled);
// TODO: An implementation that can detect the invalid usage is encouraged to throw a std::system_error with error
// condition resource_deadlock_would_occur instead of deadlocking.
}
void XenomaiMutex::lock() {
tryOrRetry([this]() { return __wrap_pthread_mutex_lock(&this->m_mutex); }, m_enabled);
}
void XenomaiMutex::unlock() {
tryOrRetry([this]() { return __wrap_pthread_mutex_unlock(&this->m_mutex); }, m_enabled);
}
XenomaiConditionVariable::XenomaiConditionVariable() {
xprintf("Construct CondictionVariable\n");
if (int ret = __wrap_pthread_cond_init(&m_cond, NULL)) {
if (EPERM != ret) {
xprintf("__wrap_thread_cond_init failed with %d %s\n", ret, strerror(ret));
return;
} else {
xprintf("mutex init returned EPERM\n");
initializeXenomai();
if (int ret = __wrap_pthread_cond_init(&m_cond, NULL)) {
fprintf(stderr, "Error: unable to create condition variable : (%d) %s\n", ret, strerror(ret));
return;
}
}
}
m_enabled = true;
}
XenomaiConditionVariable::~XenomaiConditionVariable() {
if (m_enabled) {
notify_all();
__wrap_pthread_cond_destroy(&m_cond);
}
}
void XenomaiConditionVariable::wait(std::unique_lock<XenomaiMutex>& lck) {
// If any parameter has a value that is not valid for this function (such as if lck's mutex object is not locked by
// the calling thread), it causes undefined behavior.
// Otherwise, if an exception is thrown, both the condition_variable_any object and the arguments are in a valid
// state (basic guarantee). Additionally, on exception, the state of lck is attempted to be restored before exiting
// the function scope (by calling lck.lock()).
// It may throw system_error in case of failure (transmitting any error condition from the respective call to lock
// or unlock). The predicate version (2) may also throw exceptions thrown by pred.
tryOrRetry(([this, &lck]() { return __wrap_pthread_cond_wait(&this->m_cond, &lck.mutex()->m_mutex); }), m_enabled);
}
void XenomaiConditionVariable::notify_one() noexcept {
tryOrRetry([this]() { return __wrap_pthread_cond_signal(&this->m_cond); }, m_enabled);
}
void XenomaiConditionVariable::notify_all() noexcept {
tryOrRetry([this]() { return __wrap_pthread_cond_broadcast(&this->m_cond); }, m_enabled);
}
|