| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title Os/Task.hpp | ||
| 3 | // \brief common function definitions for Os::Task | ||
| 4 | // ====================================================================== | ||
| 5 | #ifndef Os_Task_hpp_ | ||
| 6 | #define Os_Task_hpp_ | ||
| 7 | |||
| 8 | #include <Fw/FPrimeBasicTypes.hpp> | ||
| 9 | #include <Fw/Time/TimeInterval.hpp> | ||
| 10 | #include <Fw/Types/Serializable.hpp> | ||
| 11 | #include <Os/Mutex.hpp> | ||
| 12 | #include <Os/Os.hpp> | ||
| 13 | #include <Os/TaskString.hpp> | ||
| 14 | |||
| 15 | #include <Fw/Deprecate.hpp> | ||
| 16 | #include <limits> | ||
| 17 | |||
| 18 | // Forward declare for UTs | ||
| 19 | namespace Os { | ||
| 20 | namespace Test { | ||
| 21 | namespace Task { | ||
| 22 | struct Tester; | ||
| 23 | } | ||
| 24 | } // namespace Test | ||
| 25 | } // namespace Os | ||
| 26 | |||
| 27 | namespace Os { | ||
| 28 | |||
| 29 | // Forward declarations | ||
| 30 | class TaskRegistry; | ||
| 31 | |||
| 32 | //! Task handle representation | ||
| 33 | class TaskHandle {}; | ||
| 34 | |||
| 35 | class TaskInterface { | ||
| 36 | public: | ||
| 37 | //! Sentinel value to use a default value for the task argument to which this is supplied. | ||
| 38 | //! | ||
| 39 | //! Implementations of TaskInterface::start() should make a special case to use some default value that is | ||
| 40 | //! valid for the target platform. | ||
| 41 | static constexpr FwSizeType TASK_DEFAULT = std::numeric_limits<FwSizeType>::max(); | ||
| 42 | |||
| 43 | //! Sentinel value to use a default priority for the task. | ||
| 44 | //! | ||
| 45 | //! Implementations of TaskInterface::start() should make a special case to use some default task priority | ||
| 46 | //! that is valid for the target platform. | ||
| 47 | static constexpr FwTaskPriorityType TASK_PRIORITY_DEFAULT = std::numeric_limits<FwTaskPriorityType>::max(); | ||
| 48 | |||
| 49 | enum Status { | ||
| 50 | OP_OK, //!< message sent/received okay | ||
| 51 | INVALID_HANDLE, //!< Task handle invalid | ||
| 52 | INVALID_PARAMS, //!< started task with invalid parameters | ||
| 53 | INVALID_PRIORITY, //!< started task with invalid priority | ||
| 54 | INVALID_STACK, //!< started with invalid stack size | ||
| 55 | UNKNOWN_ERROR, //!< unexpected error return value | ||
| 56 | INVALID_AFFINITY, //!< unable to set the task affinity | ||
| 57 | DELAY_ERROR, //!< error trying to delay the task | ||
| 58 | JOIN_ERROR, //!< error trying to join the task | ||
| 59 | ERROR_RESOURCES, //!< unable to allocate more tasks | ||
| 60 | ERROR_PERMISSION, //!< permissions error setting-up tasks | ||
| 61 | NOT_SUPPORTED, //!< Task feature is not supported | ||
| 62 | INVALID_STATE, //!< Task is in an invalid state for the operation | ||
| 63 | }; | ||
| 64 | |||
| 65 | enum SuspensionType { INTENTIONAL, UNINTENTIONAL }; | ||
| 66 | |||
| 67 | enum State { NOT_STARTED, STARTING, RUNNING, SUSPENDED_INTENTIONALLY, SUSPENDED_UNINTENTIONALLY, EXITED, UNKNOWN }; | ||
| 68 | |||
| 69 | //! Prototype for task routine started in task context | ||
| 70 | typedef void (*taskRoutine)(void* ptr); | ||
| 71 | |||
| 72 | class Arguments { | ||
| 73 | public: | ||
| 74 | //! \brief construct a set of arguments to start a task | ||
| 75 | //! | ||
| 76 | //! Construct a set of arguments to start a task. It is illegal to supply a task routine that is | ||
| 77 | //! set to a nullptr. | ||
| 78 | //! | ||
| 79 | //! \param name: name of the task | ||
| 80 | //! \param routine: routine to run as part of this task | ||
| 81 | //! \param routine_argument: (optional) argument to supply to the task routine | ||
| 82 | //! \param priority: (optional) priority of this task | ||
| 83 | //! \param stackSize: (optional) size of stack supplied to this task | ||
| 84 | //! \param cpuAffinity: (optional) cpu affinity of this task. TODO: fix this into an array | ||
| 85 | //! \param identifier: (optional) identifier for this task | ||
| 86 | Arguments(const Fw::ConstStringBase& name, | ||
| 87 | const taskRoutine routine, | ||
| 88 | void* const routine_argument = nullptr, | ||
| 89 | const FwTaskPriorityType priority = TASK_PRIORITY_DEFAULT, | ||
| 90 | const FwSizeType stackSize = TASK_DEFAULT, | ||
| 91 | const FwSizeType cpuAffinity = TASK_DEFAULT, | ||
| 92 | const FwTaskIdType identifier = static_cast<FwTaskIdType>(TASK_DEFAULT)); | ||
| 93 | |||
| 94 | public: | ||
| 95 | const Os::TaskString m_name; | ||
| 96 | taskRoutine m_routine; | ||
| 97 | void* m_routine_argument; | ||
| 98 | FwTaskPriorityType m_priority; | ||
| 99 | FwSizeType m_stackSize; | ||
| 100 | FwSizeType m_cpuAffinity; | ||
| 101 | FwTaskIdType m_identifier; | ||
| 102 | }; | ||
| 103 | |||
| 104 | //! \brief default constructor | ||
| 105 | 2 | TaskInterface() = default; | |
| 106 | |||
| 107 | //! \brief default virtual destructor | ||
| 108 | 4 | virtual ~TaskInterface() = default; | |
| 109 | |||
| 110 | //! \brief copy constructor is forbidden | ||
| 111 | TaskInterface(const TaskInterface& other) = delete; | ||
| 112 | |||
| 113 | //! \brief assignment operator is forbidden | ||
| 114 | TaskInterface& operator=(const TaskInterface& other) = delete; | ||
| 115 | |||
| 116 | // ================= | ||
| 117 | // Implementation functions (static) to be supplied by the linker | ||
| 118 | // ================= | ||
| 119 | |||
| 120 | //! \brief provide a pointer to a task delegate object | ||
| 121 | //! | ||
| 122 | //! This function must return a pointer to a `TaskInterface` object that contains the real implementation of the | ||
| 123 | //! file functions as defined by the implementor. This function must do several things to be considered correctly | ||
| 124 | //! implemented: | ||
| 125 | //! | ||
| 126 | //! 1. Assert that the supplied memory is non-null. e.g `FW_ASSERT(aligned_placement_new_memory != NULL);` | ||
| 127 | //! 2. Assert that their implementation fits within FW_HANDLE_MAX_SIZE. | ||
| 128 | //! e.g. `static_assert(sizeof(PosixTaskImplementation) <= sizeof Os::Task::m_handle_storage, | ||
| 129 | //! "FW_HANDLE_MAX_SIZE to small");` | ||
| 130 | //! 3. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT. | ||
| 131 | //! e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixTaskImplementation)) == 0, "Bad handle alignment");` | ||
| 132 | //! 4. Placement new their implementation into `aligned_placement_new_memory` | ||
| 133 | //! e.g. `TaskInterface* interface = new (aligned_placement_new_memory) PosixTaskImplementation;` | ||
| 134 | //! 5. Return the result of the placement new | ||
| 135 | //! e.g. `return interface;` | ||
| 136 | //! | ||
| 137 | //! \return result of placement new, must be equivalent to `aligned_placement_new_memory` | ||
| 138 | //! | ||
| 139 | static TaskInterface* getDelegate(TaskHandleStorage& aligned_placement_new_memory); | ||
| 140 | |||
| 141 | // ================= | ||
| 142 | // Implementation functions (instance) to be supplied by the Os::TaskInterface children | ||
| 143 | // ================= | ||
| 144 | |||
| 145 | //! \brief perform required task start actions | ||
| 146 | virtual void onStart() = 0; | ||
| 147 | |||
| 148 | //! \brief block until the task has ended | ||
| 149 | //! | ||
| 150 | //! Blocks the current (calling) task until this task execution has ended. Callers should ensure that any | ||
| 151 | //! signals required to stop this task have already been emitted or will be emitted by another task. | ||
| 152 | //! | ||
| 153 | //! \return status of the block | ||
| 154 | virtual Status join() = 0; | ||
| 155 | |||
| 156 | //! \brief suspend the task given the suspension type | ||
| 157 | //! | ||
| 158 | //! Suspends the task. Some implementations track if the suspension of a task was intentional or | ||
| 159 | //! unintentional. The supplied `suspensionType` parameter indicates that this was intentional or | ||
| 160 | //! unintentional. The type of suspension is also returned when calling `isSuspended`. | ||
| 161 | //! | ||
| 162 | //! \param suspensionType intentionality of the suspension | ||
| 163 | virtual void suspend(SuspensionType suspensionType) = 0; | ||
| 164 | |||
| 165 | //! \brief resume a suspended task | ||
| 166 | //! | ||
| 167 | //! Resumes this task. Not started, running, and exited tasks take no action. | ||
| 168 | //! | ||
| 169 | virtual void resume() = 0; | ||
| 170 | |||
| 171 | //! \brief delay the currently scheduled task using the given architecture | ||
| 172 | //! | ||
| 173 | //! Delays, or sleeps, the current task by the supplied time interval. In non-preempting os implementations | ||
| 174 | //! the task will resume no earlier than expected but an exact wake-up time is not guaranteed. | ||
| 175 | //! | ||
| 176 | //! \param interval: delay time | ||
| 177 | //! \return status of the delay | ||
| 178 | virtual Status _delay(const Fw::TimeInterval& interval) = 0; | ||
| 179 | |||
| 180 | //! \brief determine if the task requires cooperative multitasking | ||
| 181 | //! | ||
| 182 | //! Some task implementations require cooperative multitasking where the task execution is run by a user | ||
| 183 | //! defined task scheduler and not the operating system task scheduler. These tasks cooperatively on | ||
| 184 | //! multitask by doing one unit of work and return from the function. | ||
| 185 | //! | ||
| 186 | //! This function indicates if the task requires cooperative support. | ||
| 187 | //! The default implementation returns false. | ||
| 188 | //! | ||
| 189 | //! \return true when the task expects cooperation, false otherwise | ||
| 190 | virtual bool isCooperative(); | ||
| 191 | |||
| 192 | //! \brief return the underlying task handle (implementation specific) | ||
| 193 | //! \return internal task handle representation | ||
| 194 | virtual TaskHandle* getHandle() = 0; | ||
| 195 | |||
| 196 | //! \brief start the task | ||
| 197 | //! | ||
| 198 | //! Starts the task given the supplied arguments. | ||
| 199 | //! | ||
| 200 | //! \param arguments: arguments supplied to the task start call | ||
| 201 | //! \return status of the task start | ||
| 202 | virtual Status start(const Arguments& arguments) = 0; | ||
| 203 | }; | ||
| 204 | |||
| 205 | //! Task class intended to be used by the rest of the fprime system. This is final as it is not intended to be a | ||
| 206 | //! parent class. Instead it wraps a delegate provided by `TaskInterface::getDelegate()` to provide system specific | ||
| 207 | //! behaviour. | ||
| 208 | class Task final : public TaskInterface { | ||
| 209 | friend struct Os::Test::Task::Tester; | ||
| 210 | |||
| 211 | public: | ||
| 212 | //! Wrapper for task routine that ensures `onStart()` is called once the task actually begins | ||
| 213 | class TaskRoutineWrapper { | ||
| 214 | public: | ||
| 215 | explicit TaskRoutineWrapper(Task& self); | ||
| 216 | |||
| 217 | //! \brief run the task routine wrapper | ||
| 218 | //! | ||
| 219 | //! Sets the Os::Task to started via the setStarted method. Then runs the user function passing in the | ||
| 220 | //! user argument. | ||
| 221 | //! \param task_pointer: pointer to TaskRoutineWrapper being run | ||
| 222 | static void run(void* task_pointer); | ||
| 223 | |||
| 224 | //! \brief invoke the run method with "self" as argument | ||
| 225 | void invoke(); | ||
| 226 | |||
| 227 | Task& m_task; //!< Reference to owning task | ||
| 228 | taskRoutine m_user_function = nullptr; //!< User function to run once started | ||
| 229 | void* m_user_argument = nullptr; //!< Argument to user function | ||
| 230 | }; | ||
| 231 | |||
| 232 | //! \brief backwards-compatible parameter type | ||
| 233 | typedef FwSizeType ParamType; | ||
| 234 | |||
| 235 | //! \brief default constructor | ||
| 236 | Task(); | ||
| 237 | |||
| 238 | //! \brief default virtual destructor | ||
| 239 | ~Task() final; | ||
| 240 | |||
| 241 | //! \brief copy constructor is forbidden | ||
| 242 | Task(const Task& other) = delete; | ||
| 243 | |||
| 244 | //! \brief assignment operator is forbidden | ||
| 245 | Task& operator=(const Task& other) = delete; | ||
| 246 | |||
| 247 | //! \brief suspend the current task | ||
| 248 | //! | ||
| 249 | //! Suspend the current task unintentionally. If the user needs to indicate that the task was suspended | ||
| 250 | //! intentionally then a call to `suspend(SuspensionType::INTENTIONAL)` should be used. | ||
| 251 | void suspend(); | ||
| 252 | |||
| 253 | //! \brief get the task's state | ||
| 254 | //! | ||
| 255 | //! Returns the task state: not started, running, suspended (intentionally), suspended (unintentionally), | ||
| 256 | //! and exited. | ||
| 257 | //! | ||
| 258 | //! \return task state | ||
| 259 | State getState() const; | ||
| 260 | |||
| 261 | //! \brief start this task | ||
| 262 | //! | ||
| 263 | //! Start this task with supplied name, task routine (run function), priority, stack, affinity, and task | ||
| 264 | //! identifier. These arguments are supplied into an Arguments class and that version of the function is called. | ||
| 265 | //! It is illegal to supply a nullptr as routine. | ||
| 266 | //! | ||
| 267 | //! \param name: name of the task to start | ||
| 268 | //! \param routine: user routine to run | ||
| 269 | //! \param arg: (optional) user argument to supply to task routine | ||
| 270 | //! \param priority: (optional) priority of this task | ||
| 271 | //! \param stackSize: (optional) stack size of this task | ||
| 272 | //! \param cpuAffinity: (optional) affinity of this task. Use `Task::start(Arguments&)` to supply affinity set. | ||
| 273 | //! \param identifier: (optional) identifier of this task | ||
| 274 | //! \return: status of the start call | ||
| 275 | DEPRECATED(Status start(const Fw::ConstStringBase& name, | ||
| 276 | const taskRoutine routine, | ||
| 277 | void* const arg = nullptr, | ||
| 278 | const FwTaskPriorityType priority = TASK_PRIORITY_DEFAULT, | ||
| 279 | const ParamType stackSize = TASK_DEFAULT, | ||
| 280 | const ParamType cpuAffinity = TASK_DEFAULT, | ||
| 281 | const ParamType identifier = TASK_DEFAULT), | ||
| 282 | "Switch to Task::start(Arguments&)"); | ||
| 283 | |||
| 284 | //! \brief start the task | ||
| 285 | //! | ||
| 286 | //! Starts the task given the supplied arguments. This is done via a task routine wrapper intermediary that | ||
| 287 | //! ensures that `setStarted` is called once the task has actually started to run. The task then runs the user | ||
| 288 | //! routine. This function may return before the new task begins to run. | ||
| 289 | // | ||
| 290 | //! It is illegal for arguments.m_routine to be null. | ||
| 291 | //! | ||
| 292 | //! \param arguments: arguments supplied to the task start call | ||
| 293 | //! \return status of the task start | ||
| 294 | Status start(const Arguments& arguments) override; | ||
| 295 | |||
| 296 | //! \brief perform delegate's required task start actions | ||
| 297 | void onStart() override; | ||
| 298 | |||
| 299 | //! \brief invoke the task's routine | ||
| 300 | //~ | ||
| 301 | //! This will invoke the task's routine passing this as the argument to that call. This is used as a helper when | ||
| 302 | //! running this task (e.g. repetitive cooperative calls). | ||
| 303 | void invokeRoutine(); | ||
| 304 | |||
| 305 | //! \brief join calling thread to this thread | ||
| 306 | //! | ||
| 307 | //! Note: this function is deprecated as the value_ptr object is not used anyway and should always be set | ||
| 308 | //! to nullptr. | ||
| 309 | //! | ||
| 310 | //! \param value_ptr must be set to nullptr | ||
| 311 | //! \return status of the join | ||
| 312 | DEPRECATED(Status join(void** value_ptr), "Please switch to argument free join."); | ||
| 313 | |||
| 314 | //! \brief block until the task has ended | ||
| 315 | //! | ||
| 316 | //! Blocks the current (calling) task until this task execution has ended. Callers should ensure that any | ||
| 317 | //! signals required to stop this task have already been emitted or will be emitted by another task. | ||
| 318 | //! | ||
| 319 | //! \return status of the block | ||
| 320 | Status join() override; //!< Wait for task to finish | ||
| 321 | |||
| 322 | //! \brief suspend the task given the suspension type | ||
| 323 | //! | ||
| 324 | //! Suspends the task. Some implementations track if the suspension of a task was intentional or | ||
| 325 | //! unintentional. The supplied `suspensionType` parameter indicates that this was intentional or | ||
| 326 | //! unintentional. The type of suspension is also returned when calling `isSuspended`. | ||
| 327 | //! | ||
| 328 | //! \param suspensionType intentionality of the suspension | ||
| 329 | void suspend(SuspensionType suspensionType) override; | ||
| 330 | |||
| 331 | //! \brief resume a suspended task | ||
| 332 | //! | ||
| 333 | //! Resumes this task. Not started, running, and exited tasks take no action. | ||
| 334 | //! | ||
| 335 | void resume() override; | ||
| 336 | |||
| 337 | //! \brief delay the current task | ||
| 338 | //! | ||
| 339 | //! Delays, or sleeps, the current task by the supplied time interval. In non-preempting os implementations | ||
| 340 | //! the task will resume no earlier than expected but an exact wake-up time is not guaranteed. | ||
| 341 | //! | ||
| 342 | //! \param interval: delay time | ||
| 343 | //! \return status of the delay | ||
| 344 | Status _delay(const Fw::TimeInterval& interval) override; | ||
| 345 | |||
| 346 | //! \brief determine if the task is cooperative multitasking (implementation specific) | ||
| 347 | //! \return true if cooperative, false otherwise | ||
| 348 | bool isCooperative() override; | ||
| 349 | |||
| 350 | //! \brief get the task name | ||
| 351 | TaskString getName(); | ||
| 352 | |||
| 353 | //! \brief get the task priority | ||
| 354 | FwTaskPriorityType getPriority(); | ||
| 355 | |||
| 356 | //! \brief return the underlying task handle (implementation specific) | ||
| 357 | //! \return internal task handle representation | ||
| 358 | TaskHandle* getHandle() override; | ||
| 359 | |||
| 360 | //! \brief initialize singleton | ||
| 361 | static void init(); | ||
| 362 | |||
| 363 | //! \brief get the current number of tasks | ||
| 364 | //! \return current number of tasks | ||
| 365 | static FwSizeType getNumTasks(); | ||
| 366 | |||
| 367 | //! \brief register a task registry to track Threads | ||
| 368 | //! | ||
| 369 | static void registerTaskRegistry(TaskRegistry* registry); | ||
| 370 | |||
| 371 | //! \brief get a reference to singleton | ||
| 372 | //! \return reference to singleton | ||
| 373 | static Task& getSingleton(); | ||
| 374 | |||
| 375 | //! \brief delay the current task | ||
| 376 | //! | ||
| 377 | //! Delays, or sleeps, the current task by the supplied time interval. In non-preempting os implementations | ||
| 378 | //! the task will resume no earlier than expected but an exact wake-up time is not guaranteed. | ||
| 379 | //! | ||
| 380 | //! \param interval: delay time | ||
| 381 | //! \return status of the delay | ||
| 382 | static Status delay(const Fw::TimeInterval& interval); | ||
| 383 | |||
| 384 | private: | ||
| 385 | static TaskRegistry* s_taskRegistry; //!< Pointer to registered task registry | ||
| 386 | static FwSizeType s_numTasks; //!< Stores the number of tasks created. | ||
| 387 | static Mutex s_taskMutex; //!< Guards s_numTasks | ||
| 388 | |||
| 389 | TaskString m_name; //!< Task object name | ||
| 390 | TaskInterface::State m_state = Task::NOT_STARTED; | ||
| 391 | mutable Mutex m_lock; //!< Guards state transitions | ||
| 392 | TaskRoutineWrapper m_wrapper; //!< Concrete storage for task routine wrapper | ||
| 393 | FwTaskPriorityType m_priority = 0; // Storage of priority | ||
| 394 | |||
| 395 | bool m_registered = false; //!< Was this task registered | ||
| 396 | |||
| 397 | // This section is used to store the implementation-defined file handle. To Os::File and fprime, this type is | ||
| 398 | // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in | ||
| 399 | // the byte-array here and set `handle` to that address for storage. | ||
| 400 | // | ||
| 401 | alignas(FW_HANDLE_ALIGNMENT) TaskHandleStorage m_handle_storage; //!< Storage for aligned FileHandle data | ||
| 402 | TaskInterface& m_delegate; //!< Delegate for the real implementation | ||
| 403 | }; | ||
| 404 | |||
| 405 | class TaskRegistry { | ||
| 406 | public: | ||
| 407 | //! \brief default task registry constructor | ||
| 408 | ✗ | TaskRegistry() = default; | |
| 409 | //! \brief default task registry constructor | ||
| 410 | ✗ | virtual ~TaskRegistry() = default; | |
| 411 | //! \brief add supplied task to the registry | ||
| 412 | //! | ||
| 413 | //! \param task: pointer to task to register | ||
| 414 | virtual void addTask(Task* task) = 0; //!< Add a task to the registry | ||
| 415 | |||
| 416 | //! \brief remove supplied task to the registry | ||
| 417 | //! | ||
| 418 | //! \param task: pointer to task to deregister | ||
| 419 | virtual void removeTask(Task* task) = 0; | ||
| 420 | }; | ||
| 421 | } // namespace Os | ||
| 422 | |||
| 423 | #endif | ||
| 424 |