// ====================================================================== // \title Os/Task.hpp // \brief common function definitions for Os::Task // ====================================================================== #ifndef Os_Task_hpp_ #define Os_Task_hpp_ #include #include #include #include #include #include #include #include // Forward declare for UTs namespace Os { namespace Test { namespace Task { struct Tester; } } // namespace Test } // namespace Os namespace Os { // Forward declarations class TaskRegistry; //! Task handle representation class TaskHandle {}; class TaskInterface { public: //! Sentinel value to use a default value for the task argument to which this is supplied. //! //! Implementations of TaskInterface::start() should make a special case to use some default value that is //! valid for the target platform. static constexpr FwSizeType TASK_DEFAULT = std::numeric_limits::max(); //! Sentinel value to use a default priority for the task. //! //! Implementations of TaskInterface::start() should make a special case to use some default task priority //! that is valid for the target platform. static constexpr FwTaskPriorityType TASK_PRIORITY_DEFAULT = std::numeric_limits::max(); enum Status { OP_OK, //!< message sent/received okay INVALID_HANDLE, //!< Task handle invalid INVALID_PARAMS, //!< started task with invalid parameters INVALID_STACK, //!< started with invalid stack size UNKNOWN_ERROR, //!< unexpected error return value INVALID_AFFINITY, //!< unable to set the task affinity DELAY_ERROR, //!< error trying to delay the task JOIN_ERROR, //!< error trying to join the task ERROR_RESOURCES, //!< unable to allocate more tasks ERROR_PERMISSION, //!< permissions error setting-up tasks NOT_SUPPORTED, //!< Task feature is not supported INVALID_STATE, //!< Task is in an invalid state for the operation }; enum SuspensionType { INTENTIONAL, UNINTENTIONAL }; enum State { NOT_STARTED, STARTING, RUNNING, SUSPENDED_INTENTIONALLY, SUSPENDED_UNINTENTIONALLY, EXITED, UNKNOWN }; //! Prototype for task routine started in task context typedef void (*taskRoutine)(void* ptr); class Arguments { public: //! \brief construct a set of arguments to start a task //! //! Construct a set of arguments to start a task. It is illegal to supply a task routine that is //! set to a nullptr. //! //! \param name: name of the task //! \param routine: routine to run as part of this task //! \param routine_argument: (optional) argument to supply to the task routine //! \param priority: (optional) priority of this task //! \param stackSize: (optional) size of stack supplied to this task //! \param cpuAffinity: (optional) cpu affinity of this task. TODO: fix this into an array //! \param identifier: (optional) identifier for this task Arguments(const Fw::ConstStringBase& name, const taskRoutine routine, void* const routine_argument = nullptr, const FwTaskPriorityType priority = TASK_PRIORITY_DEFAULT, const FwSizeType stackSize = TASK_DEFAULT, const FwSizeType cpuAffinity = TASK_DEFAULT, const FwTaskIdType identifier = static_cast(TASK_DEFAULT)); public: const Os::TaskString m_name; taskRoutine m_routine; void* m_routine_argument; FwTaskPriorityType m_priority; FwSizeType m_stackSize; FwSizeType m_cpuAffinity; FwTaskIdType m_identifier; }; //! \brief default constructor TaskInterface() = default; //! \brief default virtual destructor virtual ~TaskInterface() = default; //! \brief copy constructor is forbidden TaskInterface(const TaskInterface& other) = delete; //! \brief assignment operator is forbidden TaskInterface& operator=(const TaskInterface& other) = delete; // ================= // Implementation functions (static) to be supplied by the linker // ================= //! \brief provide a pointer to a task delegate object //! //! This function must return a pointer to a `TaskInterface` object that contains the real implementation of the //! file functions as defined by the implementor. This function must do several things to be considered correctly //! implemented: //! //! 1. Assert that the supplied memory is non-null. e.g `FW_ASSERT(aligned_placement_new_memory != NULL);` //! 2. Assert that their implementation fits within FW_HANDLE_MAX_SIZE. //! e.g. `static_assert(sizeof(PosixTaskImplementation) <= sizeof Os::Task::m_handle_storage, //! "FW_HANDLE_MAX_SIZE to small");` //! 3. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT. //! e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixTaskImplementation)) == 0, "Bad handle alignment");` //! 4. Placement new their implementation into `aligned_placement_new_memory` //! e.g. `TaskInterface* interface = new (aligned_placement_new_memory) PosixTaskImplementation;` //! 5. Return the result of the placement new //! e.g. `return interface;` //! //! \return result of placement new, must be equivalent to `aligned_placement_new_memory` //! static TaskInterface* getDelegate(TaskHandleStorage& aligned_placement_new_memory); // ================= // Implementation functions (instance) to be supplied by the Os::TaskInterface children // ================= //! \brief perform required task start actions virtual void onStart() = 0; //! \brief block until the task has ended //! //! Blocks the current (calling) task until this task execution has ended. Callers should ensure that any //! signals required to stop this task have already been emitted or will be emitted by another task. //! //! \return status of the block virtual Status join() = 0; //! \brief suspend the task given the suspension type //! //! Suspends the task. Some implementations track if the suspension of a task was intentional or //! unintentional. The supplied `suspensionType` parameter indicates that this was intentional or //! unintentional. The type of suspension is also returned when calling `isSuspended`. //! //! \param suspensionType intentionality of the suspension virtual void suspend(SuspensionType suspensionType) = 0; //! \brief resume a suspended task //! //! Resumes this task. Not started, running, and exited tasks take no action. //! virtual void resume() = 0; //! \brief delay the currently scheduled task using the given architecture //! //! Delays, or sleeps, the current task by the supplied time interval. In non-preempting os implementations //! the task will resume no earlier than expected but an exact wake-up time is not guaranteed. //! //! \param interval: delay time //! \return status of the delay virtual Status _delay(Fw::TimeInterval interval) = 0; //! \brief determine if the task requires cooperative multitasking //! //! Some task implementations require cooperative multitasking where the task execution is run by a user //! defined task scheduler and not the operating system task scheduler. These tasks cooperatively on //! multitask by doing one unit of work and return from the function. //! //! This function indicates if the task requires cooperative support. //! The default implementation returns false. //! //! \return true when the task expects cooperation, false otherwise virtual bool isCooperative(); //! \brief return the underlying task handle (implementation specific) //! \return internal task handle representation virtual TaskHandle* getHandle() = 0; //! \brief start the task //! //! Starts the task given the supplied arguments. //! //! \param arguments: arguments supplied to the task start call //! \return status of the task start virtual Status start(const Arguments& arguments) = 0; }; //! Task class intended to be used by the rest of the fprime system. This is final as it is not intended to be a //! parent class. Instead it wraps a delegate provided by `TaskInterface::getDelegate()` to provide system specific //! behaviour. class Task final : public TaskInterface { friend struct Os::Test::Task::Tester; public: //! Wrapper for task routine that ensures `onStart()` is called once the task actually begins class TaskRoutineWrapper { public: explicit TaskRoutineWrapper(Task& self); //! \brief run the task routine wrapper //! //! Sets the Os::Task to started via the setStarted method. Then runs the user function passing in the //! user argument. //! \param task_pointer: pointer to TaskRoutineWrapper being run static void run(void* task_pointer); //! \brief invoke the run method with "self" as argument void invoke(); Task& m_task; //!< Reference to owning task taskRoutine m_user_function = nullptr; //!< User function to run once started void* m_user_argument = nullptr; //!< Argument to user function }; //! \brief backwards-compatible parameter type typedef FwSizeType ParamType; //! \brief default constructor Task(); //! \brief default virtual destructor ~Task() final; //! \brief copy constructor is forbidden Task(const Task& other) = delete; //! \brief assignment operator is forbidden Task& operator=(const Task& other) = delete; //! \brief suspend the current task //! //! Suspend the current task unintentionally. If the user needs to indicate that the task was suspended //! intentionally then a call to `suspend(SuspensionType::INTENTIONAL)` should be used. void suspend(); //! \brief get the task's state //! //! Returns the task state: not started, running, suspended (intentionally), suspended (unintentionally), //! and exited. //! //! \return task state State getState(); //! \brief start this task //! //! Start this task with supplied name, task routine (run function), priority, stack, affinity, and task //! identifier. These arguments are supplied into an Arguments class and that version of the function is called. //! It is illegal to supply a nullptr as routine. //! //! \param name: name of the task to start //! \param routine: user routine to run //! \param arg: (optional) user argument to supply to task routine //! \param priority: (optional) priority of this task //! \param stackSize: (optional) stack size of this task //! \param cpuAffinity: (optional) affinity of this task. Use `Task::start(Arguments&)` to supply affinity set. //! \param identifier: (optional) identifier of this task //! \return: status of the start call DEPRECATED(Status start(const Fw::ConstStringBase& name, const taskRoutine routine, void* const arg = nullptr, const FwTaskPriorityType priority = TASK_PRIORITY_DEFAULT, const ParamType stackSize = TASK_DEFAULT, const ParamType cpuAffinity = TASK_DEFAULT, const ParamType identifier = TASK_DEFAULT), "Switch to Task::start(Arguments&)"); //! \brief start the task //! //! Starts the task given the supplied arguments. This is done via a task routine wrapper intermediary that //! ensures that `setStarted` is called once the task has actually started to run. The task then runs the user //! routine. This function may return before the new task begins to run. // //! It is illegal for arguments.m_routine to be null. //! //! \param arguments: arguments supplied to the task start call //! \return status of the task start Status start(const Arguments& arguments) override; //! \brief perform delegate's required task start actions void onStart() override; //! \brief invoke the task's routine //~ //! This will invoke the task's routine passing this as the argument to that call. This is used as a helper when //! running this task (e.g. repetitive cooperative calls). void invokeRoutine(); //! \brief join calling thread to this thread //! //! Note: this function is deprecated as the value_ptr object is not used anyway and should always be set //! to nullptr. //! //! \param value_ptr must be set to nullptr //! \return status of the join DEPRECATED(Status join(void** value_ptr), "Please switch to argument free join."); //! \brief block until the task has ended //! //! Blocks the current (calling) task until this task execution has ended. Callers should ensure that any //! signals required to stop this task have already been emitted or will be emitted by another task. //! //! \return status of the block Status join() override; //!< Wait for task to finish //! \brief suspend the task given the suspension type //! //! Suspends the task. Some implementations track if the suspension of a task was intentional or //! unintentional. The supplied `suspensionType` parameter indicates that this was intentional or //! unintentional. The type of suspension is also returned when calling `isSuspended`. //! //! \param suspensionType intentionality of the suspension void suspend(SuspensionType suspensionType) override; //! \brief resume a suspended task //! //! Resumes this task. Not started, running, and exited tasks take no action. //! void resume() override; //! \brief delay the current task //! //! Delays, or sleeps, the current task by the supplied time interval. In non-preempting os implementations //! the task will resume no earlier than expected but an exact wake-up time is not guaranteed. //! //! \param interval: delay time //! \return status of the delay Status _delay(Fw::TimeInterval interval) override; //! \brief determine if the task is cooperative multitasking (implementation specific) //! \return true if cooperative, false otherwise bool isCooperative() override; //! \brief get the task priority FwTaskPriorityType getPriority(); //! \brief return the underlying task handle (implementation specific) //! \return internal task handle representation TaskHandle* getHandle() override; //! \brief initialize singleton static void init(); //! \brief get the current number of tasks //! \return current number of tasks static FwSizeType getNumTasks(); //! \brief register a task registry to track Threads //! static void registerTaskRegistry(TaskRegistry* registry); //! \brief get a reference to singleton //! \return reference to singleton static Task& getSingleton(); //! \brief delay the current task //! //! Delays, or sleeps, the current task by the supplied time interval. In non-preempting os implementations //! the task will resume no earlier than expected but an exact wake-up time is not guaranteed. //! //! \param interval: delay time //! \return status of the delay static Status delay(Fw::TimeInterval interval); private: static TaskRegistry* s_taskRegistry; //!< Pointer to registered task registry static FwSizeType s_numTasks; //!< Stores the number of tasks created. static Mutex s_taskMutex; //!< Guards s_numTasks TaskString m_name; //!< Task object name TaskInterface::State m_state = Task::NOT_STARTED; Mutex m_lock; //!< Guards state transitions TaskRoutineWrapper m_wrapper; //!< Concrete storage for task routine wrapper FwTaskPriorityType m_priority = 0; // Storage of priority bool m_registered = false; //!< Was this task registered // This section is used to store the implementation-defined file handle. To Os::File and fprime, this type is // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in // the byte-array here and set `handle` to that address for storage. // alignas(FW_HANDLE_ALIGNMENT) TaskHandleStorage m_handle_storage; //!< Storage for aligned FileHandle data TaskInterface& m_delegate; //!< Delegate for the real implementation }; class TaskRegistry { public: //! \brief default task registry constructor TaskRegistry() = default; //! \brief default task registry constructor virtual ~TaskRegistry() = default; //! \brief add supplied task to the registry //! //! \param task: pointer to task to register virtual void addTask(Task* task) = 0; //!< Add a task to the registry //! \brief remove supplied task to the registry //! //! \param task: pointer to task to deregister virtual void removeTask(Task* task) = 0; }; } // namespace Os #endif