Files CCore/inc/Task.h CCore/src/Task.cpp
Subfolders CCore/inc/task CCore/src/task
Tasks and synchronization objects are the heart of any real-time system platform. The word "task" is used by tradition instead of thread. A typical embedded application may be regarded as a multi-threaded process, running in a single memory space. It has the direct access to all hardware features. The functionality of the application is implemented in a series of tasks. The board starts after reset, performs a core initialization and runs the main task. At start you need a working RAM, CPU, timers, interrupt controller, serial port and may be some extra peripherals. Main task performs additional working hardware initialization, prepares and runs tasks. There is a special "interrupt context", this execution context is used to handle hardware interrupts. All actions in this context are limited: they must be "quick", in particular, it is forbidden to use any "blocking" calls. XCore does not support nested interrupts, so the interrupt context is multi-task safe.
In XCore the entry point to the main task is the usual function main(). Global objects are constructed before the main() and destructed after. During this phase interrupts are disabled and the running extra tasks is not permitted. When main() is finished, the main task is blocked until all extra tasks are completed and then global objects are destroyed.
The header Task.h includes all task related entities. XCore variants of the same classes have extra features than HCore.
To run a task the class Task is used.
The class Task is polymorphic, with a virtual destructor and derived from the MemBase_nocopy. A task object must not be destroyed until the associated thread of execution is running.
There are two virtual methods of this class to be overloaded in a derived class: entry() and exit(). The first implements a task functionality. The second is called to complete the task execution. You may assume the task is logically finished when this method is called.
class Task : public MemBase_nocopy
{
....
private:
virtual void entry();
virtual void exit();
public:
Task();
virtual ~Task();
bool run_or_exit();
// task control
static void Yield() { Sys::YieldTask(); }
static void Sleep(MSec time) { Sys::SleepTask(time); }
static Task * GetCurrent();
};
run_or_exit() performs "run or exit" operation. I.e. it tries to start a task. If the task is started, true is returned. Otherwise, the method exit() is called and false is returned. You cannot run the same task twice. So exit() is called either way: as the part of the run_or_exit() call or after the task is finished. This method can be used to destroy a dynamically allocated task object, to signal about task completion or to do another completion quick job.
Yield() rolls the CPU to another running task, if any.
Sleep() sleeps the current task for the given period of time in milliseconds.
GetCurrent() returns the current running task object. If the current thread was not spawned from the Task class, the return value is null.
XCore Task class is similar to HCore, but has some extra features.
class Task : public MemBase_nocopy
{
....
private:
virtual void entry();
virtual void exit();
public:
explicit Task(TaskPriority priority=DefaultTaskPriority,
ulen stack_len=DefaultStackLen);
explicit Task(TextLabel name,
TaskPriority priority=DefaultTaskPriority,
ulen stack_len=DefaultStackLen);
virtual ~Task();
TextLabel getName() const;
TaskPriority getPriority() const;
bool run_or_exit();
// task control
static void Yield();
static void Sleep(MSec time);
static void RelaxPriority(unsigned period_tick,TaskPriority priority=IdleTaskPriority);
static Task * GetCurrent() { return Current; }
};
XCore Task constructors have additional task parameters. Task can be explicitly named. If the name is not provided, some auto-generated name is used. This name is used in the system logging and the event recording. The TaskPriority value is assigned to the task. Task priorities determine tasks run sequence: a task with higher priority preempts an execution of tasks with lower priorities. The task priority cannot be changed, but can be temporary raised automatically by synchronization objects. Finally, the task stack_len can be specified. The minimum and the default task stack length are defined by the target.
enum TaskPriority : unsigned
{
ExitTaskPriority = 0, // highest
CompleteTaskPriority = 1,
DefaultTaskPriority = 10000, // default
MainTaskPriority = DefaultTaskPriority,
IdleTaskPriority = unsigned(-1) // lowest
};
inline bool Preempt(TaskPriority a,TaskPriority b) { return a<b; }
PrintTaskPriority GetTextDesc(TaskPriority priority);
Task priority counts down: the less value means the higher priority. There are three special tasks in the system: Exit Task, Complete Task and Idle Task.
Idle Task runs while other tasks are blocked. It has the lowest priority.
Exit Task is used to call the method exit(). This task can safely destroy another task object. Exit Task has the highest priority.
Complete Task can be used for the packet completion. It has the next highest priority after the Exit Task. Normally packets are completed by processing tasks, but in some cases it should be completed in the interrupt context. This is not possible, so the special task is provided for such packet completion.
It is not recommended to run a task with one of the special task priorities.
XCore performs tick task switch. The internal system tick is driven by a timer interrupt. This tick runs on a high frequency (like 10 kHz). Tick switches ready-to-run tasks and performs timed jobs, like timeout processing. Without the task priority relaxation only a task with the highest priority gains CPU time. If there are several such tasks they are switched in a round-robin order.
The static method Task::RelaxPriority() enables the task priority relaxation. It means that during some ticks task priorities are ignored. The period_tick determines the period of the relaxed ticks. The value 0 means the task priority relaxation is disabled. The value 1 means that all ticks are relaxed. The value 2 means that each second tick is relaxed, and so on. The argument priority determines the cut of priority for the task relaxation. If the task has this priority or less, it does not participate in the relaxation. For example, the Idle Task never gains CPU because of the task priority relaxation.