diff --git a/Makefile.dep b/Makefile.dep index 58642b8219..f41be51907 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -254,3 +254,9 @@ endif ifneq (,$(filter log_%,$(USEMODULE))) USEMODULE += log endif + +ifneq (,$(filter cpp11-compat,$(USEMODULE))) + USEMODULE += vtimer + USEMODULE += timex + FEATURES_REQUIRED += cpp +endif diff --git a/core/include/native_sched.h b/core/include/native_sched.h new file mode 100644 index 0000000000..d337bc1c05 --- /dev/null +++ b/core/include/native_sched.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup core_sched Scheduler + * @ingroup core + * @brief The RIOT scheduler + * @details + * + * @{ + * + * @file native_sched.h + * @brief Add definitions required on the native board + * + * @author Raphael Hiesgen + * @} + */ + +#ifndef _NATIVE_SCHEDULER_H +#define _NATIVE_SCHEDULER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef BOARD_NATIVE +#include +/* + * Required to use some C++11 headers with g++ on the native board. + */ +#define __CPU_SETSIZE 1024 +#define __NCPUBITS (8* sizeof(__cpu_mask)) +typedef unsigned long int __cpu_mask; +typedef struct { + __cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS]; +} cpu_set_t; + +/** + * @brief In all test the function has never been called, hence it is empty for now. + */ +inline int sched_yield(void) +{ + puts("[ERROR] sched_yield called (defined in sched.h)\n"); + return 0; +} +#endif // BOARD_NATIVE + +#ifdef __cplusplus +} +#endif + +#endif // _NATIVE_SCHEDULER_H diff --git a/core/include/sched.h b/core/include/sched.h index b045ce1ad1..c925644adb 100644 --- a/core/include/sched.h +++ b/core/include/sched.h @@ -85,6 +85,7 @@ #include "tcb.h" #include "attributes.h" #include "kernel_types.h" +#include "native_sched.h" #ifdef __cplusplus extern "C" { diff --git a/cpu/stm32f4/startup.c b/cpu/stm32f4/startup.c index 36c62e9fb1..84e151f2a5 100644 --- a/cpu/stm32f4/startup.c +++ b/cpu/stm32f4/startup.c @@ -40,6 +40,11 @@ extern void board_init(void); extern void kernel_init(void); extern void __libc_init_array(void); +/** + * Required by g++ cross compiler + */ +void *__dso_handle; + /** * @brief This function is the entry point after a system reset * diff --git a/sys/Makefile b/sys/Makefile index 49919cef4b..567fca59a1 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -17,8 +17,8 @@ ifneq (,$(filter nomac,$(USEMODULE))) DIRS += net/link_layer/nomac endif ifneq (,$(filter transport_layer,$(USEMODULE))) - USEMODULE += udp - USEMODULE += tcp + USEMODULE += udp + USEMODULE += tcp endif ifneq (,$(filter socket_base,$(USEMODULE))) DIRS += net/transport_layer/socket_base @@ -45,7 +45,7 @@ ifneq (,$(filter rpl,$(USEMODULE))) DIRS += net/routing/rpl endif ifneq (,$(filter routing,$(USEMODULE))) - DIRS += net/routing + DIRS += net/routing endif ifneq (,$(filter aodvv2,$(USEMODULE))) DIRS += net/routing/aodvv2 @@ -146,6 +146,9 @@ endif ifneq (,$(filter log_%,$(USEMODULE))) DIRS += log endif +ifneq (,$(filter cpp11-compat,$(USEMODULE))) + DIRS += cpp11-compat +endif DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE}))) diff --git a/sys/Makefile.include b/sys/Makefile.include index 68137f6cfb..6783844dfe 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -81,6 +81,10 @@ ifneq (,$(filter fib,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/sys/include/net endif +ifneq (,$(filter cpp11-compat,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/sys/cpp11-compat/include +endif + ifneq (,$(filter embunit,$(USEMODULE))) ifeq ($(OUTPUT),XML) CFLAGS += -DOUTPUT=OUTPUT_XML diff --git a/sys/cpp11-compat/Makefile b/sys/cpp11-compat/Makefile new file mode 100644 index 0000000000..9e24ecf137 --- /dev/null +++ b/sys/cpp11-compat/Makefile @@ -0,0 +1,4 @@ +# This module requires cpp 11 +CXXEXFLAGS += -std=c++11 + +include $(RIOTBASE)/Makefile.base diff --git a/sys/cpp11-compat/condition_variable.cpp b/sys/cpp11-compat/condition_variable.cpp new file mode 100644 index 0000000000..d660874fab --- /dev/null +++ b/sys/cpp11-compat/condition_variable.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file condition_variable.cpp + * @brief C++11 condition variable drop in replacement + * + * @author Raphael Hiesgen + * + * @} + */ + +#include +#include +#include + +#include "irq.h" +#include "sched.h" +#include "vtimer.h" +#include "priority_queue.h" + +#include "riot/condition_variable.hpp" + +using namespace std::chrono; + +namespace riot { + +condition_variable::~condition_variable() { m_queue.first = NULL; } + +void condition_variable::notify_one() noexcept { + unsigned old_state = disableIRQ(); + priority_queue_node_t* head = priority_queue_remove_head(&m_queue); + int other_prio = -1; + if (head != NULL) { + tcb_t* other_thread = (tcb_t*)sched_threads[head->data]; + if (other_thread) { + other_prio = other_thread->priority; + sched_set_status(other_thread, STATUS_PENDING); + } + head->data = -1u; + } + restoreIRQ(old_state); + if (other_prio >= 0) { + sched_switch(other_prio); + } +} + +void condition_variable::notify_all() noexcept { + unsigned old_state = disableIRQ(); + int other_prio = -1; + while (true) { + priority_queue_node_t* head = priority_queue_remove_head(&m_queue); + if (head == NULL) { + break; + } + tcb_t* other_thread = (tcb_t*)sched_threads[head->data]; + if (other_thread) { + auto max_prio + = [](int a, int b) { return (a < 0) ? b : ((a < b) ? a : b); }; + other_prio = max_prio(other_prio, other_thread->priority); + sched_set_status(other_thread, STATUS_PENDING); + } + head->data = -1u; + } + restoreIRQ(old_state); + if (other_prio >= 0) { + sched_switch(other_prio); + } +} + +void condition_variable::wait(unique_lock& lock) noexcept { + if (!lock.owns_lock()) { + throw std::system_error( + std::make_error_code(std::errc::operation_not_permitted), + "Mutex not locked."); + } + priority_queue_node_t n; + n.priority = sched_active_thread->priority; + n.data = sched_active_pid; + n.next = NULL; + // the signaling thread may not hold the mutex, the queue is not thread safe + unsigned old_state = disableIRQ(); + priority_queue_add(&m_queue, &n); + restoreIRQ(old_state); + mutex_unlock_and_sleep(lock.mutex()->native_handle()); + if (n.data != -1u) { + // on signaling n.data is set to -1u + // if it isn't set, then the wakeup is either spurious or a timer wakeup + old_state = disableIRQ(); + priority_queue_remove(&m_queue, &n); + restoreIRQ(old_state); + } + mutex_lock(lock.mutex()->native_handle()); +} + +cv_status condition_variable::wait_until(unique_lock& lock, + const time_point& timeout_time) { + vtimer_t timer; + // todo: use function to wait for absolute timepoint once available + timex_t before; + vtimer_now(&before); + auto diff = timex_sub(timeout_time.native_handle(), before); + vtimer_set_wakeup(&timer, diff, sched_active_pid); + wait(lock); + timex_t after; + vtimer_now(&after); + vtimer_remove(&timer); + auto cmp = timex_cmp(after, timeout_time.native_handle()); + return cmp < 1 ? cv_status::no_timeout : cv_status::timeout; +} + +} // namespace riot diff --git a/sys/cpp11-compat/doc.txt b/sys/cpp11-compat/doc.txt new file mode 100644 index 0000000000..2a17c155e7 --- /dev/null +++ b/sys/cpp11-compat/doc.txt @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup cpp11-compat C++11 wrapper for RIOT + * @brief drop in replacement to enable C++11-like thread, mutex and condition_variable + * @ingroup sys + */ diff --git a/sys/cpp11-compat/include/riot/chrono.hpp b/sys/cpp11-compat/include/riot/chrono.hpp new file mode 100644 index 0000000000..531e8a4ad8 --- /dev/null +++ b/sys/cpp11-compat/include/riot/chrono.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file chrono.hpp + * @brief C++11 chrono drop in replacement that adds the function now based on + * vtimer/timex + * @see + * std::thread, defined in header + * + * + * @author Raphael Hiesgen + * + * @} + */ + +#ifndef RIOT_CHRONO_HPP +#define RIOT_CHRONO_HPP + +#include +#include +#include + +#include "time.h" +#include "vtimer.h" + +namespace riot { + +namespace { +constexpr uint32_t microsecs_in_sec = 1000000; +} // namespace anaonymous + +/** + * @brief time point to use for timed wait, as stdlib clocks are not available + */ +class time_point { + using native_handle_type = timex_t; + + public: + /** + * @brief create a time point with seconds and microseconds set to 0 + */ + inline time_point() : m_handle{0, 0} {} + /** + * @brief create time point from timex_t struct + */ + inline time_point(timex_t&& tp) : m_handle(tp) {} + constexpr time_point(const time_point& tp) = default; + constexpr time_point(time_point&& tp) = default; + + /** + * @brief get access to the handle used to store the time information + */ + inline native_handle_type native_handle() const { return m_handle; } + + /** + * @brief add a stdlib chrono::duration to this time point + */ + template + inline time_point& operator+=(const std::chrono::duration& d) { + auto s = std::chrono::duration_cast(d); + auto m = (std::chrono::duration_cast(d) - s); + m_handle.seconds += s.count(); + m_handle.microseconds += m.count(); + adjust_overhead(); + return *this; + } + + /** + * @brief returns seconds member as uint32_t + */ + inline uint32_t seconds() const { return m_handle.seconds; } + + /** + * @brief returns microseconds member as uint32_t + */ + inline uint32_t microseconds() const { return m_handle.microseconds; } + + private: + timex_t m_handle; + void inline adjust_overhead() { + auto secs = m_handle.microseconds / microsecs_in_sec; + m_handle.seconds += secs; + m_handle.microseconds -= (secs * microsecs_in_sec); + } +}; + +/** + * @brief get the current time saved in a time point + * + * @return time_point containing the current time + */ +inline time_point now() { + timex_t tp; + vtimer_now(&tp); + return time_point(std::move(tp)); +} + +/** + * @brief compare two timepoints + */ +inline bool operator<(const time_point& lhs, const time_point& rhs) { + return lhs.seconds() < rhs.seconds() + || (lhs.seconds() == rhs.seconds() && lhs.microseconds() + < rhs.microseconds()); +} + +/** + * @brief compare two timepoints + */ +inline bool operator>(const time_point& lhs, const time_point& rhs) { + return rhs < lhs; +} + +/** + * @brief compare two timepoints + */ +inline bool operator<=(const time_point& lhs, const time_point& rhs) { + return !(rhs < lhs); +} + +/** + * @brief compare two timepoints + */ +inline bool operator>=(const time_point& lhs, const time_point& rhs) { + return !(lhs < rhs); +} + +} // namespace riot + +#endif // RIOT_CHRONO_HPP diff --git a/sys/cpp11-compat/include/riot/condition_variable.hpp b/sys/cpp11-compat/include/riot/condition_variable.hpp new file mode 100644 index 0000000000..7f30875040 --- /dev/null +++ b/sys/cpp11-compat/include/riot/condition_variable.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file condition_variable.hpp + * @brief C++11 condition variable drop in replacement + * @see + * std::condition_variable + * + * + * @author Raphael Hiesgen + * + * @} + */ + +#ifndef RIOT_CONDITION_VARIABLE_HPP +#define RIOT_CONDITION_VARIABLE_HPP + +#include "sched.h" +#include "vtimer.h" + +#include "riot/mutex.hpp" +#include "riot/chrono.hpp" + +namespace riot { + +enum class cv_status { + no_timeout, + timeout +}; + +/** + * @brief C++11 complient implementation of condition variable, uses the time + * point implemented in our chrono replacement instead of the + * specified one + * @see + * std::condition_variable + * + */ +class condition_variable { + public: + using native_handle_type = priority_queue_t*; + + inline condition_variable() { m_queue.first = NULL; } + ~condition_variable(); + + void notify_one() noexcept; + void notify_all() noexcept; + + void wait(unique_lock& lock) noexcept; + template + void wait(unique_lock& lock, Predicate pred); + cv_status wait_until(unique_lock& lock, + const time_point& timeout_time); + template + bool wait_until(unique_lock& lock, const time_point& timeout_time, + Predicate pred); + + template + cv_status wait_for(unique_lock& lock, + const std::chrono::duration& rel_time); + template + bool wait_for(unique_lock& lock, + const std::chrono::duration& rel_time, + Predicate pred); + + inline native_handle_type native_handle() { return &m_queue; } + + private: + condition_variable(const condition_variable&); + condition_variable& operator=(const condition_variable&); + + priority_queue_t m_queue; +}; + +template +void condition_variable::wait(unique_lock& lock, Predicate pred) { + while (!pred()) { + wait(lock); + } +} + +template +bool condition_variable::wait_until(unique_lock& lock, + const time_point& timeout_time, + Predicate pred) { + while (!pred()) { + if (wait_until(lock, timeout_time) == cv_status::timeout) { + return pred(); + } + } + return true; +} + +template +cv_status condition_variable::wait_for(unique_lock& lock, + const std::chrono::duration + & timeout_duration) { + using namespace std::chrono; + using std::chrono::duration; + if (timeout_duration <= timeout_duration.zero()) { + return cv_status::timeout; + } + timex_t timeout, before, after; + auto s = duration_cast(timeout_duration); + timeout.seconds = s.count(); + timeout.microseconds + = (duration_cast(timeout_duration - s)).count(); + vtimer_now(&before); + vtimer_t timer; + vtimer_set_wakeup(&timer, timeout, sched_active_pid); + wait(lock); + vtimer_now(&after); + vtimer_remove(&timer); + auto passed = timex_sub(after, before); + auto cmp = timex_cmp(passed, timeout); + return cmp < 1 ? cv_status::no_timeout : cv_status::timeout; +} + +template +inline bool condition_variable::wait_for(unique_lock& lock, + const std::chrono::duration + & timeout_duration, + Predicate pred) { + return wait_until(lock, std::chrono::steady_clock::now() + timeout_duration, + std::move(pred)); +} + +} // namespace riot + +#endif // RIOT_CONDITION_VARIABLE_HPP diff --git a/sys/cpp11-compat/include/riot/detail/thread_util.hpp b/sys/cpp11-compat/include/riot/detail/thread_util.hpp new file mode 100644 index 0000000000..2ac1a5d078 --- /dev/null +++ b/sys/cpp11-compat/include/riot/detail/thread_util.hpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file thread_util.hpp + * @brief utility functions + * + * @author Dominik Charousset + * @author Raphael Hiesgen + * + * @} + */ + +#ifndef RIOT_THREAD_UTILS_HPP +#define RIOT_THREAD_UTILS_HPP + +#include +#include + +namespace riot { +namespace detail { + +/** + * A list of integers (wraps a long... template parameter pack). + */ +template +struct int_list {}; + +/** + * Creates indices for from `Pos` to `Max`. + */ +template > +struct il_indices; + +template +struct il_indices> { + using type = int_list; +}; + +template +struct il_indices> { + using type = typename il_indices>::type; +}; + +template +typename il_indices::type get_indices() { + return {}; +} + +/** + * apply arguments to function + */ +template +inline auto apply_args(F& f, detail::int_list, Tuple&& tup) + -> decltype(f(std::get(tup)...)) { + return f(std::get(tup)...); +} + +template +inline auto apply_args_prefixed(F& f, detail::int_list<>, Tuple&, Ts&&... args) + -> decltype(f(std::forward(args)...)) { + return f(std::forward(args)...); +} + +template +inline auto apply_args_prefixed(F& f, detail::int_list, Tuple& tup, + Ts&&... args) + -> decltype(f(std::forward(args)..., std::get(tup)...)) { + return f(std::forward(args)..., std::get(tup)...); +} + +template +inline auto apply_args_suffxied(F& f, detail::int_list, Tuple& tup, + Ts&&... args) + -> decltype(f(std::get(tup)..., std::forward(args)...)) { + return f(std::get(tup)..., std::forward(args)...); +} + +} // namespace detail +} // namespace riot + +#endif // RIOT_THREAD_UTILS_HPP diff --git a/sys/cpp11-compat/include/riot/mutex.hpp b/sys/cpp11-compat/include/riot/mutex.hpp new file mode 100644 index 0000000000..5c95c4c79b --- /dev/null +++ b/sys/cpp11-compat/include/riot/mutex.hpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file mutex.hpp + * @brief C++11 mutex drop in replacement + * @see + * std::mutex, std::lock_guard and std::unique_lock + * + * + * @author Raphael Hiesgen + * + * @} + */ + +#ifndef RIOT_MUTEX_HPP +#define RIOT_MUTEX_HPP + +#include "mutex.h" + +#include +#include +#include + +namespace riot { + +/** + * @brief C++11 complient implementation of mutex, uses the time point + * implemented in our chrono replacement instead of the specified + * one + * @see + * std::mutex + * + */ +class mutex { + public: + using native_handle_type = mutex_t*; + + inline constexpr mutex() noexcept : m_mtx{0, PRIORITY_QUEUE_INIT} {} + ~mutex(); + + void lock(); + bool try_lock() noexcept; + void unlock() noexcept; + + inline native_handle_type native_handle() { return &m_mtx; } + + private: + mutex(const mutex&); + mutex& operator=(const mutex&); + + mutex_t m_mtx; +}; + +struct defer_lock_t {}; +struct try_to_lock_t {}; +struct adopt_lock_t {}; + +constexpr defer_lock_t defer_lock = defer_lock_t(); +constexpr try_to_lock_t try_to_lock = try_to_lock_t(); +constexpr adopt_lock_t adopt_lock = adopt_lock_t(); + +/** + * @brief C++11 complient implementation of unique lock + * @see + * std::lock_guard + * + */ +template +class lock_guard { + + public: + using mutex_type = Mutex; + + inline explicit lock_guard(mutex_type& mtx) : m_mtx(mtx) { m_mtx.lock(); } + inline lock_guard(mutex_type& mtx, adopt_lock_t) : m_mtx{mtx} {} + inline ~lock_guard() { m_mtx.unlock(); } + + private: + mutex_type& m_mtx; +}; + +/** + * @brief C++11 complient implementation of unique lock + * @see + * std::unique_lock + * + */ +template +class unique_lock { + + public: + using mutex_type = Mutex; + + inline unique_lock() noexcept : m_mtx{nullptr}, m_owns{false} {} + inline explicit unique_lock(mutex_type& mtx) : m_mtx{&mtx}, m_owns{true} { + m_mtx->lock(); + } + inline unique_lock(mutex_type& mtx, defer_lock_t) noexcept : m_mtx{&mtx}, + m_owns{false} {} + inline unique_lock(mutex_type& mtx, try_to_lock_t) + : m_mtx{&mtx}, m_owns{mtx.try_lock()} {} + inline unique_lock(mutex_type& mtx, adopt_lock_t) + : m_mtx{&mtx}, m_owns{true} {} + inline ~unique_lock() { + if (m_owns) { + m_mtx->unlock(); + } + } + inline unique_lock(unique_lock&& lock) noexcept : m_mtx{lock.m_mtx}, + m_owns{lock.m_owns} { + lock.m_mtx = nullptr; + lock.m_owns = false; + } + inline unique_lock& operator=(unique_lock&& lock) noexcept { + if (m_owns) { + m_mtx->unlock(); + } + m_mtx = lock.m_mtx; + m_owns = lock.m_owns; + lock.m_mtx = nullptr; + lock.m_owns = false; + return *this; + } + + void lock(); + bool try_lock(); + + void unlock(); + + inline void swap(unique_lock& lock) noexcept { + std::swap(m_mtx, lock.m_mtx); + std::swap(m_owns, lock.m_owns); + } + + inline mutex_type* release() noexcept { + mutex_type* mtx = m_mtx; + m_mtx = nullptr; + m_owns = false; + return mtx; + } + + inline bool owns_lock() const noexcept { return m_owns; } + inline explicit operator bool() const noexcept { return m_owns; } + inline mutex_type* mutex() const noexcept { return m_mtx; } + + private: + unique_lock(unique_lock const&); + unique_lock& operator=(unique_lock const&); + + mutex_type* m_mtx; + bool m_owns; +}; + +template +void unique_lock::lock() { + if (m_mtx == nullptr) { + throw std::system_error( + std::make_error_code(std::errc::operation_not_permitted), + "References null mutex."); + } + if (m_owns) { + throw std::system_error( + std::make_error_code(std::errc::resource_deadlock_would_occur), + "Already locked."); + } + m_mtx->lock(); + m_owns = true; +} + +template +bool unique_lock::try_lock() { + if (m_mtx == nullptr) { + throw std::system_error( + std::make_error_code(std::errc::operation_not_permitted), + "References null mutex."); + } + if (m_owns) { + throw std::system_error( + std::make_error_code(std::errc::resource_deadlock_would_occur), + "Already locked."); + } + m_owns = m_mtx->try_lock(); + return m_owns; +} + +template +void unique_lock::unlock() { + if (!m_owns) { + throw std::system_error( + std::make_error_code(std::errc::operation_not_permitted), + "Mutex not locked."); + } + m_mtx->unlock(); + m_owns = false; +} + +template +inline void swap(unique_lock& lhs, unique_lock& rhs) noexcept { + lhs.swap(rhs); +} + +} // namespace riot + +#endif // RIOT_MUTEX_HPP diff --git a/sys/cpp11-compat/include/riot/thread.hpp b/sys/cpp11-compat/include/riot/thread.hpp new file mode 100644 index 0000000000..05a9ffae9f --- /dev/null +++ b/sys/cpp11-compat/include/riot/thread.hpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file thread.hpp + * @brief C++11 thread drop in replacement + * @see + * std::thread, std::this_thread + * + * + * @author Raphael Hiesgen + * + * @} + */ + +#ifndef RIOT_THREAD_HPP +#define RIOT_THREAD_HPP + +#include "time.h" +#include "thread.h" +#include "kernel_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "riot/mutex.hpp" +#include "riot/chrono.hpp" +#include "riot/condition_variable.hpp" + +#include "riot/detail/thread_util.hpp" + +namespace riot { + +namespace { +constexpr kernel_pid_t thread_uninitialized = -1; +constexpr size_t stack_size = KERNEL_CONF_STACKSIZE_MAIN; +} + +struct thread_data { + thread_data() : ref_count{2}, joining_thread{thread_uninitialized} { + // nop + } + std::atomic ref_count; + kernel_pid_t joining_thread; + char stack[stack_size]; +}; + +/** + * This deleter prevents our thread data from being destroyed if the thread + * object is destroyed before the thread had a chance to run + */ +struct thread_data_deleter { + void operator()(thread_data* ptr) { + if (--ptr->ref_count == 0) { + delete ptr; + } + } +}; + +/** + * @brief implementation of thread::id + * @see + * thread::id + * + */ +class thread_id { + template + friend std::basic_ostream& operator<<(std::basic_ostream + & out, + thread_id id); + friend class thread; + + public: + inline thread_id() noexcept : m_handle{thread_uninitialized} {} + inline thread_id(kernel_pid_t handle) : m_handle{handle} {} + + inline bool operator==(thread_id other) noexcept { + return m_handle == other.m_handle; + } + inline bool operator!=(thread_id other) noexcept { + return !(m_handle == other.m_handle); + } + inline bool operator<(thread_id other) noexcept { + return m_handle < other.m_handle; + } + inline bool operator<=(thread_id other) noexcept { + return !(m_handle > other.m_handle); + } + inline bool operator>(thread_id other) noexcept { + return m_handle > other.m_handle; + } + inline bool operator>=(thread_id other) noexcept { + return !(m_handle < other.m_handle); + } + + private: + kernel_pid_t m_handle; +}; + +template +inline std::basic_ostream& operator<<(std::basic_ostream + & out, + thread_id id) { + return out << id.m_handle; +} + +namespace this_thread { + +inline thread_id get_id() noexcept { return thread_getpid(); } +inline void yield() noexcept { thread_yield(); } +void sleep_for(const std::chrono::nanoseconds& ns); +template +void sleep_for(const std::chrono::duration& sleep_duration) { + using namespace std::chrono; + if (sleep_duration > std::chrono::duration::zero()) { + constexpr std::chrono::duration max = nanoseconds::max(); + nanoseconds ns; + if (sleep_duration < max) { + ns = duration_cast(sleep_duration); + if (ns < sleep_duration) { + ++ns; + } + } else { + ns = nanoseconds::max(); + } + sleep_for(ns); + } +} +inline void sleep_until(const riot::time_point& sleep_time) { + mutex mtx; + condition_variable cv; + unique_lock lk(mtx); + while (riot::now() < sleep_time) { + cv.wait_until(lk, sleep_time); + } +} +} // namespace this_thread + +/* + * @brief C++11 compliant implementation of thread, however uses the time + * point from out chrono header instead of the specified one + * @see + * std::thread + * + */ +class thread { + public: + using id = thread_id; + using native_handle_type = kernel_pid_t; + + inline thread() noexcept : m_handle{thread_uninitialized} {} + template + explicit thread(F&& f, Args&&... args); + ~thread(); + + thread(const thread&) = delete; + inline thread(thread&& t) noexcept : m_handle{t.m_handle} { + t.m_handle = thread_uninitialized; + std::swap(m_data, t.m_data); + } + thread& operator=(const thread&) = delete; + thread& operator=(thread&&) noexcept; + + void swap(thread& t) noexcept { + std::swap(m_data, t.m_data); + std::swap(m_handle, t.m_handle); + } + + inline bool joinable() const noexcept { + return m_handle != thread_uninitialized; + } + void join(); + void detach(); + inline id get_id() const noexcept { return m_handle; } + inline native_handle_type native_handle() noexcept { return m_handle; } + + static unsigned hardware_concurrency() noexcept; + + kernel_pid_t m_handle; + std::unique_ptr m_data; +}; + +void swap(thread& lhs, thread& rhs) noexcept; + +template +void* thread_proxy(void* vp) { + { // without this scope, the objects here are not cleaned up corrctly + std::unique_ptr p(static_cast(vp)); + auto tmp = std::get<0>(*p); + std::unique_ptr data{tmp}; + // create indices for the arguments, 0 is thread_data and 1 is the function + auto indices = detail::get_indices::value, 2>(); + try { + detail::apply_args(std::get<1>(*p), indices, *p); + } + catch (...) { + // nop + } + if (data->joining_thread != thread_uninitialized) { + thread_wakeup(data->joining_thread); + } + } + // some riot cleanup code + sched_task_exit(); + return nullptr; +} + +template +thread::thread(F&& f, Args&&... args) + : m_data{new thread_data} { + using namespace std; + using func_and_args = tuple + ::type, typename decay::type...>; + std::unique_ptr p( + new func_and_args(m_data.get(), forward(f), forward(args)...)); + m_handle = thread_create( + m_data->stack, stack_size, PRIORITY_MAIN - 1, 0, // CREATE_WOUT_YIELD + &thread_proxy, p.get(), "riot_cpp_thread"); + if (m_handle >= 0) { + p.release(); + } else { + throw std::system_error( + std::make_error_code(std::errc::resource_unavailable_try_again), + "Failed to create thread."); + } +} + +inline thread& thread::operator=(thread&& other) noexcept { + if (m_handle != thread_uninitialized) { + std::terminate(); + } + m_handle = other.m_handle; + other.m_handle = thread_uninitialized; + std::swap(m_data, other.m_data); + return *this; +} + +inline void swap(thread& lhs, thread& rhs) noexcept { lhs.swap(rhs); } + +} // namespace riot + +#endif // RIOT_THREAD_HPP diff --git a/sys/cpp11-compat/mutex.cpp b/sys/cpp11-compat/mutex.cpp new file mode 100644 index 0000000000..7abaed0aa9 --- /dev/null +++ b/sys/cpp11-compat/mutex.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file mutex.cpp + * @brief C++11 mutex drop in replacement + * + * @author Raphael Hiesgen + * + * @} + */ + +#include "riot/mutex.hpp" + +namespace riot { + +mutex::~mutex() { + // nop +} + +void mutex::lock() { mutex_lock(&m_mtx); } + +bool mutex::try_lock() noexcept { return (1 == mutex_trylock(&m_mtx)); } + +void mutex::unlock() noexcept { mutex_unlock(&m_mtx); } + +} // namespace riot diff --git a/sys/cpp11-compat/thread.cpp b/sys/cpp11-compat/thread.cpp new file mode 100644 index 0000000000..3cebaf7657 --- /dev/null +++ b/sys/cpp11-compat/thread.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpp11-compat + * @{ + * + * @file thread.cpp + * @brief C++11 thread drop in replacement + * + * @author Raphael Hiesgen + * + * @} + */ + +#include "vtimer.h" + +#include +#include + +#include "riot/thread.hpp" + +using namespace std; + +namespace riot { + +thread::~thread() { + if (joinable()) { + terminate(); + } +} + +void thread::join() { + if (this->get_id() == this_thread::get_id()) { + throw system_error(make_error_code(errc::resource_deadlock_would_occur), + "Joining this leads to a deadlock."); + } + if (joinable()) { + auto status = thread_getstatus(m_handle); + if (status != STATUS_NOT_FOUND && status != STATUS_STOPPED) { + m_data->joining_thread = sched_active_pid; + thread_sleep(); + } + m_handle = thread_uninitialized; + } else { + throw system_error(make_error_code(errc::invalid_argument), + "Can not join an unjoinable thread."); + } + // missing: no_such_process system error +} + +void thread::detach() { + if (joinable()) { + m_handle = thread_uninitialized; + } else { + throw system_error(make_error_code(errc::invalid_argument), + "Can not detach an unjoinable thread."); + } +} + +unsigned thread::hardware_concurrency() noexcept { + // there is currently no API for this + return 1; +} + +namespace this_thread { + +void sleep_for(const chrono::nanoseconds& ns) { + using namespace chrono; + if (ns > nanoseconds::zero()) { + seconds s = duration_cast(ns); + timespec ts; + using ts_sec = decltype(ts.tv_sec); + constexpr ts_sec ts_sec_max = numeric_limits::max(); + if (s.count() < ts_sec_max) { + ts.tv_sec = static_cast(s.count()); + ts.tv_nsec = static_cast((ns - s).count()); + } else { + ts.tv_sec = ts_sec_max; + ts.tv_nsec = giga::num - 1; + } + timex_t reltime; + reltime.seconds = ts.tv_sec; + reltime.microseconds = ts.tv_nsec / 1000u; + vtimer_t timer; + vtimer_set_wakeup(&timer, reltime, sched_active_pid); + thread_sleep(); + } +} + +} // namespace this_thread + +} // namespace riot diff --git a/tests/cpp11_condition_variable/Makefile b/tests/cpp11_condition_variable/Makefile new file mode 100644 index 0000000000..31f4446f71 --- /dev/null +++ b/tests/cpp11_condition_variable/Makefile @@ -0,0 +1,28 @@ +# name of your application +APPLICATION = cpp11_condition_variable + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP -Wno-deprecated + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +BOARD_WHITELIST := stm32f4discovery native + +# If you want to add some extra flags when compile c++ files, add these flags +# to CXXEXFLAGS variable +CXXEXFLAGS += -std=c++11 -g -O0 -Wno-deprecated + +USEMODULE += cpp11-compat +USEMODULE += vtimer +USEMODULE += timex + +include $(RIOTBASE)/Makefile.include diff --git a/tests/cpp11_condition_variable/main.cpp b/tests/cpp11_condition_variable/main.cpp new file mode 100644 index 0000000000..41c225a7ff --- /dev/null +++ b/tests/cpp11_condition_variable/main.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief test condition variable replacement header + * + * @author Raphael Hiesgen + * + * @} + */ + +#include +#include +#include +#include + +#include "riot/mutex.hpp" +#include "riot/chrono.hpp" +#include "riot/thread.hpp" +#include "riot/condition_variable.hpp" + +using namespace std; +using namespace riot; + +/* http://en.cppreference.com/w/cpp/thread/condition_variable */ +int main() { + puts("\n************ C++ condition_variable test ***********"); + + puts("Wait with predicate and notify one ... "); + { + mutex m; + condition_variable cv; + string data; + bool ready = false; + bool processed = false; + thread worker([&] { + unique_lock lk(m); + cv.wait(lk, [&ready] { return ready; }); + data += " after processing"; + processed = true; + cv.notify_one(); + }); + data = "Example data"; + { + lock_guard lk(m); + // reason: variable is read in the thread created above + /* cppcheck-suppress unreadVariable */ + ready = true; + cv.notify_one(); + } + { + unique_lock lk(m); + cv.wait(lk, [&processed] { return processed; }); + } + string expected = "Example data after processing"; + assert(data == expected); + worker.join(); + } + puts("Done\n"); + + puts("Wait and notify all ..."); + { + mutex m; + condition_variable cv; + auto waits = [&m, &cv] { + unique_lock lk(m); + cv.wait(lk); + }; + thread t1(waits); + thread t2(waits); + thread t3(waits); + thread t4(waits); + thread([&m, &cv] { + unique_lock lk(m); + cv.notify_all(); + }).detach(); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + } + puts("Done\n"); + + puts("Wait for ..."); + { + using chrono::system_clock; + constexpr unsigned timeout = 1; + mutex m; + condition_variable cv; + timex_t before, after; + unique_lock lk(m); + vtimer_now(&before); + cv.wait_for(lk, chrono::seconds(timeout)); + vtimer_now(&after); + auto diff = timex_sub(after, before); + assert(diff.seconds >= timeout); + } + puts("Done\n"); + + puts("Wait until ..."); + { + using chrono::system_clock; + constexpr unsigned timeout = 1; + mutex m; + condition_variable cv; + timex_t before, after; + unique_lock lk(m); + vtimer_now(&before); + auto time = riot::now() += chrono::seconds(timeout); + cv.wait_until(lk, time); + vtimer_now(&after); + auto diff = timex_sub(after, before); + assert(diff.seconds >= timeout); + } + puts("Done\n"); + + puts("Bye, bye. "); + puts("******************************************************\n"); + + return 0; +} diff --git a/tests/cpp11_mutex/Makefile b/tests/cpp11_mutex/Makefile new file mode 100644 index 0000000000..e247ae456a --- /dev/null +++ b/tests/cpp11_mutex/Makefile @@ -0,0 +1,28 @@ +# name of your application +APPLICATION = cpp11_mutex + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP -Wno-deprecated + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +BOARD_WHITELIST := stm32f4discovery native + +# If you want to add some extra flags when compile c++ files, add these flags +# to CXXEXFLAGS variable +CXXEXFLAGS += -std=c++11 -g -O0 -Wno-deprecated + +USEMODULE += cpp11-compat +USEMODULE += vtimer +USEMODULE += timex + +include $(RIOTBASE)/Makefile.include diff --git a/tests/cpp11_mutex/main.cpp b/tests/cpp11_mutex/main.cpp new file mode 100644 index 0000000000..795a22a23e --- /dev/null +++ b/tests/cpp11_mutex/main.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief test mutex replacement header + * + * @author Raphael Hiesgen + * + * @} + */ +#include +#include +#include +#include + +#include "riot/mutex.hpp" +#include "riot/chrono.hpp" +#include "riot/thread.hpp" +#include "riot/condition_variable.hpp" + +using namespace std; +using namespace riot; + +/* http://en.cppreference.com/w/cpp/thread/mutex */ +int main() { + puts("\n************ C++ mutex test ***********"); + + puts("Lock and unlock ... "); + { + mutex m; + int resource = 0; + auto f = [&m, &resource] { + for (int i = 0; i < 3; ++i) { + m.lock(); + ++resource; + this_thread::sleep_for(chrono::milliseconds(100)); + m.unlock(); + } + }; + assert(resource == 0); + auto start = std::chrono::system_clock::now(); + thread t1(f); + thread t2(f); + t1.join(); + t2.join(); + assert(resource == 6); + auto duration = std::chrono::duration_cast + (std::chrono::system_clock::now() - start); + assert(duration.count() >= 600); + } + puts("Done\n"); + + puts("Try_lock ..."); + { + mutex m; + m.lock(); + thread([&m] { + auto res = m.try_lock(); + assert(res == false); + }).detach(); + m.unlock(); + } + + { + mutex m; + thread([&m] { + auto res = m.try_lock(); + assert(res == true); + m.unlock(); + }).detach(); + } + puts("Done\n"); + + puts("Bye, bye."); + puts("*****************************************\n"); + + return 0; +} diff --git a/tests/cpp11_thread/Makefile b/tests/cpp11_thread/Makefile new file mode 100644 index 0000000000..49afd6a6be --- /dev/null +++ b/tests/cpp11_thread/Makefile @@ -0,0 +1,28 @@ +# name of your application +APPLICATION = cpp11_thread + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP -Wno-deprecated + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +BOARD_WHITELIST := stm32f4discovery native + +# If you want to add some extra flags when compile c++ files, add these flags +# to CXXEXFLAGS variable +CXXEXFLAGS += -std=c++11 -g -O0 -Wno-deprecated + +USEMODULE += cpp11-compat +USEMODULE += vtimer +USEMODULE += timex + +include $(RIOTBASE)/Makefile.include diff --git a/tests/cpp11_thread/main.cpp b/tests/cpp11_thread/main.cpp new file mode 100644 index 0000000000..8536a598f2 --- /dev/null +++ b/tests/cpp11_thread/main.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2015 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief test thread replacement header + * + * @author Raphael Hiesgen + * + * @} + */ + +#include +#include +#include +#include + +#include "riot/mutex.hpp" +#include "riot/chrono.hpp" +#include "riot/thread.hpp" +#include "riot/condition_variable.hpp" + +using namespace std; +using namespace riot; + +/* http://en.cppreference.com/w/cpp/thread/thread */ +int main() { + puts("\n************ C++ thread test ***********"); + + assert(sched_num_threads == 2); // main + idle + + puts("Creating one thread and passing an argument ..."); + { + constexpr int i = 3; + thread t([=](const int j) { assert(j == i); }, i); + try { + t.join(); + } + catch (const std::system_error& e) { + assert(false); + } + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Creating detached thread ..."); + { + thread t([] { + // nop + }); + assert(t.joinable() == 1); + t.detach(); + assert(t.joinable() == 0); + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Join on 'finished' thread ..."); + { + thread t; + assert(t.joinable() == 0); + t = thread([] { + // nop + }); + assert(t.joinable() == 1); + try { + t.join(); + } + catch (const std::system_error& e) { + assert(false); + } + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Join on 'running' thread ..."); + { + mutex m; + thread t1, t2; + condition_variable cv; + assert(t1.joinable() == 0); + assert(t2.joinable() == 0); + t1 = thread([&m, &cv] { + unique_lock lk(m); + cv.wait(lk); + }); + assert(t1.joinable() == 1); + t2 = thread([&t1] { + try { + t1.join(); + } + catch (const std::system_error& e) { + assert(false); + } + }); + assert(t2.joinable() == 1); + cv.notify_one(); + try { + t2.join(); + } + catch (const std::system_error& e) { + assert(false); + } + assert(t1.joinable() == 0); + assert(t2.joinable() == 0); + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Testing sleep_for ..."); + { + timex_t before, after; + vtimer_now(&before); + this_thread::sleep_for(chrono::seconds(1)); + vtimer_now(&after); + auto diff = timex_sub(after, before); + assert(diff.seconds >= 1); + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Testing sleep_until ..."); + { + timex_t before, after; + vtimer_now(&before); + this_thread::sleep_until(riot::now() += chrono::seconds(1)); + vtimer_now(&after); + auto diff = timex_sub(after, before); + assert(diff.seconds >= 1); + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Swapping two threads ..."); + { + thread t1([] { + // nop + }); + thread t2([] { + // nop + }); + auto t1_old = t1.get_id(); + auto t2_old = t2.get_id(); + t1.swap(t2); + assert(t1_old == t2.get_id()); + assert(t2_old == t1.get_id()); + t1.join(); + t2.join(); + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Move constructor ..."); + { + thread t1([] { + // nop + }); + thread t2(move(t1)); + assert(t1.joinable() == 0); + assert(t2.joinable() == 1); + t2.join(); + } + puts("Done\n"); + + assert(sched_num_threads == 2); + + puts("Bye, bye."); + puts("******************************************"); + + return 0; +}