From cf95826089f36a42a69a1b12691e453428c0b2af Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Sat, 3 Oct 2020 16:13:24 +1000 Subject: cpukit/librcxx: Add a C++ thread interface with attributes --- cpukit/include/rtems/thread.hpp | 477 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 477 insertions(+) create mode 100644 cpukit/include/rtems/thread.hpp (limited to 'cpukit/include/rtems/thread.hpp') diff --git a/cpukit/include/rtems/thread.hpp b/cpukit/include/rtems/thread.hpp new file mode 100644 index 0000000000..e90e664dfa --- /dev/null +++ b/cpukit/include/rtems/thread.hpp @@ -0,0 +1,477 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSC++ + * + * @brief C++ standard thread support with thread attribute control. + * + * Provide a way to create a thread in C++ with attributes that let + * you control the real-time embedded parameters need to run + * threads on RTEMS. + * + * The code requires the `-std=c++17` option to access `std::invoke()`. + */ + +/* + * Copyright (C) 2020 Chris Johns (http://contemporary.software) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER OR 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. + */ + +#if !defined(RTEMS_THREAD_HPP) +#define RTEMS_THREAD_HPP + +#include +#include +#include +#include +#include + +namespace rtems +{ + namespace thread + { + /** + * @brief Manage the attributes of a thread. + */ + class attributes + { + public: + /** + * The scheduler attribute. + */ + enum sched_attr { + sched_inherit, /**< Inherit the scheduler attributes + * from the creating thread. */ + sched_explicit /**< Explicitly set the scheduler to these + * attributes. */ + }; + + /** + * The scheduler policies. + */ + enum sched_policy { + sched_other, /**< Other scheduler policy */ + sched_fifo, /**< FIFO scheduler policy */ + sched_roundrobin, /**< Round robin scheduler policy */ + sched_sporadic /**< Sporadic scheduler policy */ + }; + + /** + * Construct a thread attributes object with the current settings of the + * executing thread. The stack size is set to the configured minimum + * stack size. + */ + attributes (); + + /* + * Copy construct the thread attributes. + * + * @param attr The attributes to copy. + */ + attributes (const attributes& attr); + + /** + * Set the name of the thread. The thread is a classic API thread and + * the name is only 4 characters. + * + * @param name The name as a string. + */ + void set_name (const std::string& name); + + /** + * Set the name of the thread. The thread is a classic API thread and + * the name is only 4 characters. + * + * @param name The name as a string. + */ + void set_name (const char* name); + + /** + * Get the name of the thread. + * + * @retval const std::string& The name of the thread. + */ + const std::string& get_name () const; + + /** + * Set the priority of the thread. + * + * @param priority The POSIX API priority of the thread. + */ + void set_priority (int priority); + + /** + * Get the POSIX API priority of the thread. + * + * @retval int The POSIX API thread priority. + */ + int get_priority () const; + + /** + * Set the stack size. If the size is less than the configured minimum + * the minimum value is used. + * + * @param size The stack size in bytes. + */ + void set_stack_size (size_t size); + + /** + * Get the stack size. + * + * @retval size_t The stack size in bytes. + */ + size_t get_stack_size () const; + + /** + * Set the scheduler name. If not set no scheduler is set. + * + * @parrm scheduler The name of the scheduler. + */ + void set_scheduler (const std::string& scheduler); + + /** + * Set the scheduler name. If not set no scheduler is set. + */ + void set_scheduler (const char* scheduler); + + /** + * Get scheduler name. + */ + const std::string& get_scheduler (); + + /** + * Get the attributes' scheduler attribute for the thread. + * + * @return sched_attr The attributes' scheduler attribute + */ + sched_attr get_scheduler_attr () const; + + /** + * Set the scheduler policy for the thread. This call sets the + * scheduler attribute to @ref sched_explicit. + * + * @param policy The scheduler policy. + */ + void set_scheduler_policy (sched_policy policy); + + /** + * Get the scheduler policy for the thread. + */ + sched_policy get_scheduler_policy () const; + + /** + * Commit any changes to the executing thread. + * + * @note only the priority and attribute of a thread can be changed. The + * name and stack size are ignored. + */ + void commit (); + + /** + * Update the attribute values from the executing thread. The attributes + * are updated from the current thread when constructed and the values + * returned are those held since then. If another thread changes the + * attributes of the current thread those changes will not be seen until + * this method is called. Except for the name and stack size any local + * changes made will lost then the update call is made. + */ + void update (); + + /** + * Copy operator. + */ + attributes& operator= (const attributes& attr); + + /** + * The comparison operator does not check the name or stack size + * of a thread. + */ + bool operator== (const attributes& attr) const; + + private: + std::string name; /**< Name of the thread */ + int priority; /**< POSIX API priority */ + size_t stack_size; /**< Stack size in bytes */ + std::string scheduler; /**< Name of the scheduler */ + sched_attr attr; /**< Scheduler's attribute */ + sched_policy policy; /**< Scheduler's policy */ + /* affinity, cpu set size is? */ + }; + + /** + * @brief Create a thread with thread attributes. + * + * Create a thread optionally with thread attributes. The usage of this + * class follows the C++ standard for std::thread. The standard support + * for creating a thread does not let you control the attributes of a + * thread and control is important in embedded real-time + * applications. This class lets you control a thread attributes and use + * the extensive an excellent thread support the C++ standard provides. + * + * There is no indication attribute support for threads will be added to + * the C++ standard and what it will look like. The support provided here + * is designed to make as little impact on a code base as possible. While + * the attributes supported are specific to RTEMS they are common to all + * embedded operating systems. + * + * The support provided here is specific to GCC due to the use of some + * non-standard interfaces to get the indices of the template argument + * pack in new thread's context. A standards only solution would be + * preferred. + */ + class thread + { + friend void* thread_generic_entry (void* arg); + + /** + * Base state class to interface to derived template of the thread + * state from the generic entry point for the thread. + */ + struct state_base + { + virtual ~state_base (); + virtual const attributes get_attributes () = 0; + virtual void run () = 0; + }; + + /** + * The state is passed to the new thread context as a unique + * pointer. This handles the hand over and clean up. + */ + using state_ptr = std::unique_ptr; + + public: + + /** + * Template check to see if the first argument of a thread is a set of + * attributes. + */ + template ::type> + using enable_if_attributes = typename std::enable_if + ::value>::type; + + /** + * We need our own id type so the thread class can access the pthread + * handle to initialise it. + */ + class id { + public: + id () noexcept : id_ (0) { } + explicit id (pthread_t id_) : id_ (id_) { } + private: + pthread_t id_; + + friend class thread; + friend bool operator== (thread::id l, thread::id r) noexcept; + + template + friend std::basic_ostream& + operator<< (std::basic_ostream& out, thread::id id_); + }; + + /** + * The default thread constructions. + */ + thread () noexcept = default; + + /** + * The std::thread equivalent constructor. The attributes will be the + * same as the executing thread with a default thread name and the + * configured minimum stack size. + */ + template + explicit thread (F&& func, Args&&... args); + + /** + * Create a thread with the provided attributes. The entry point and + * optional arguments are the same as std::thread. + */ + template > + explicit thread (A&& attr, F&& func, Args&&... args); + + /** + * Move the thread id to this instance. + */ + thread& operator= (thread&& thread_); + + void swap(thread& thread_) noexcept; + + bool joinable() const noexcept; + + /* + * Constrain use. These are not available. + */ + thread (thread&) = delete; + thread (const thread&) = delete; + thread (const thread&&) = delete; + thread& operator= (const thread&) = delete; + + std::thread::id get_id() const noexcept; + + private: + + id id_; + + /** + * Invoke the thread's entry point with the parameter pack in the new + * thread's context. This object holds the parameters copied onto the + * new thread's stack making them available to entry point routine. + */ + template + struct invoker { + Parms p; + + template + static std::__tuple_element_t&& declval (); + + template + auto invoke (std::_Index_tuple) + noexcept (noexcept (std::invoke (declval()...))) + -> decltype (std::invoke (declval ()...)) { + return std::invoke (std::get (std::move (p))...); + } + + using indices = + typename std::_Build_index_tuple::value>::__type; + + void run () { + invoke (indices ()); + } + }; + + /** + * The state holds the invoker with the parameters. The generic entry + * point calls the virtual methods to get the attributes and to run the + * new thread in the new thread's context. + */ + template + struct state : state_base { + const attributes attr; + Invoker i; + + state (const attributes& attr, Invoker&& i) + : attr (attr), + i (std::forward (i)) { + } + + const attributes get_attributes () override { + return attr; + } + + void run () override { + i.run (); + } + }; + + /** + * Make the state. This dynamic memory is managed by the unique pointer + * and is passed to the generic thread entry point. + */ + template + static state_ptr + make_state (const attributes& attr, Invoker&& i) { + using state_impl = state; + return state_ptr{ new state_impl (attr, std::forward (i)) }; + } + + /** + * Decay the parameters so they can be correctly packed into the + * parameter tuple. + */ + template + using decayed_tuple = std::tuple::type...>; + + /** + * Make the invoker with the parameters. + */ + template + static invoker> + make_invoker (F&& func, Args&&... args) + { + return { + decayed_tuple { + std::forward (func), std::forward (args)... + } + }; + } + + /** + * Create and start the thread. + */ + void start_thread (state_ptr s); + }; + + template + inline typename std::decay::type + decay_copy (T&& t) { + return std::forward (t); + } + + template + thread::thread (F&& func, Args&&... args) + : id_ (0) { + attributes attr; + start_thread ( + make_state (attr, + make_invoker (decay_copy (std::forward (func)), + decay_copy (std::forward (args))...)) + ); + } + + template> + thread::thread (A&& attr, F&& func, Args&&... args) + : id_ (0) { + start_thread ( + make_state (attr, + make_invoker (decay_copy (std::forward (func)), + decay_copy (std::forward (args))...)) + ); + } + + inline std::thread::id thread::get_id() const noexcept { + return std::thread::id (id_.id_); + } + + inline bool + operator== (thread::id l, thread::id r) noexcept { + return l.id_ == r.id_; + } + + inline bool + operator!= (thread::id l, thread::id r) noexcept { + return !(l == r); + } + + template + inline std::basic_ostream& + operator<< (std::basic_ostream& out, thread::id id_) { + return out << std::thread::id (id_.id_); + } + }; +}; + +#endif -- cgit v1.2.3