fprime/Os/Task.hpp
Philip Romano bf12f48c27
Adjust task priorities to fit within supported platform priority ranges (#4337)
* Adjust task priorities to fit in supported platforms' priority ranges

Darwin's task priority range is most restrictive (15-47); adjusted priorities
to reside within that range.

* Add comment clarifying TASK_PRIORITY_DEFAULT and TASK_DEFAULT

* Adjust relative task priorities for uplink and downlink

This moves uplink tasks to higher priority than downlink tasks and places
consumer tasks at higher priority than producer tasks.
2025-10-22 15:46:05 -07:00

420 lines
17 KiB
C++

// ======================================================================
// \title Os/Task.hpp
// \brief common function definitions for Os::Task
// ======================================================================
#ifndef Os_Task_hpp_
#define Os_Task_hpp_
#include <Fw/FPrimeBasicTypes.hpp>
#include <Fw/Time/TimeInterval.hpp>
#include <Fw/Types/Serializable.hpp>
#include <Os/Mutex.hpp>
#include <Os/Os.hpp>
#include <Os/TaskString.hpp>
#include <Fw/Deprecate.hpp>
#include <limits>
// 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<FwSizeType>::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<FwTaskPriorityType>::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::StringBase& 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<FwTaskIdType>(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::StringBase& 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