What is a Task?
A task is a program element that can compete on its own for system resources. From RTOS point of view a task is ”a subroutine” that is used as the entry point of the task.
A task is created (or spawned) by the RTOS, and in this RTOS call the entry point to the task is specified. It is normally acceptable that several instances of the same task are created. That means that each instance runs exactly the same code, which of course has to be re-entrant. If you want to end the task, do not run to the end of the main function of the task, as this last statement basically is a ”return from subroutine”, and as the task was not called as a subroutine, some RTOSs may not be able to handle this correctly. Instead always use the delete (or kill) task call, so that the RTOS fully understands that you want to end the task and the RTOS has the possibility to ”clean up” after the task.
If the task should exist forever, you normally have an endless loop in your main function of the task, see figure 1.
It is very common that some code are unique for each task, but some code may also be shared by several tasks like e.g. different kinds of subroutines and libraries, see figure 2.
There are a number of conceptions related to a task. These may of course vary for different RTOSs.
Task names make it easier for us to discuss with each other when we talk about the tasks. The name can also be used in the application to identify a task. But handling strings in a computer is time consuming, so the RTOS gives the task an identity when it is created, and you should use this identity in your application when you want to do something with your task.
Other things that you need to specify when you create a task are stack sizes, task priority and mode.
Depending on which CPU and RTOS you are going to use, the task may need one or several stacks. You also need to know if these stacks are used by device drivers, ISRs or the RTOS itself, or if they are using their own stacks. So what you need to do is to read the RTOS manual and CPU manual to find out.
Each task should have a priority. If you have less tasks than priority levels, then it is a good rule to give each task a unique priority. Only if some tasks will use round-robin (timeslicing) you should give them the same priority.
A task can also work in different modes. This is also dependent on which CPU you are using and what is supported in the RTOS. Examples are: supervisor – user mode, preemption – non-preemption mode, interrupt mask, timeslicing on – off.
A task can be in different states. The names of these states and the number of states differs from one RTOS to another. In this article I will use: Executing, Ready, Blocked and Suspended.
Executing means that task is running i.e. this is the task that uses the CPU at this moment. Of course in a single CPU system only one task can have this state. The task that is executing is normally (if you are using pre-emption mode for all the tasks) the task with highest priority of all tasks that are ready. Ready means that the task is ready to start to execute as soon as its priority is the highest of all tasks that are ready. A task that is blocked is waiting for a resource e.g. a message in a message queue, a semaphore, or maybe just a number of ticks, if the task just have been delayed. A task is suspended when another task or the task itself calls the task suspend call to the RTOS. Suspended is more like a semi-state as a task may be blocked at the same time and may change its state to ready, but it cannot start to execute until another task has called the task resume call for that task.
A task can only change its state when a task or an ISR calls the RTOS, see figure 3.
In figure 4 an example is shown on how the RTOS organizes the tasks depending on their states.
What you need to know before you can make a task design
Doing the task design may be the most hard and critical part of the design. But it is also a very innovative part, as it is really here that you should solve all the tricky design problems. My recommendation is that you really spend enough time here, maybe up to 30 to 50 % of the project time, as a good and well documented design will make the implementation, the tests and the maintenance so much easier.
What you need to know before you can make your design is of course all system requirements and all deadlines in the system, as these will effect the way you can and should design your tasks.
What you need to specify when you do a task design
There are a number of things that you need to specify in your task design:
- Scheduling, specify if the task should be time-driven (periodic), be event-driven, or using round-robin.
- Priority, if you give the task the wrong priority in the design, no problem, as this is very easy to change in the implementation.
- Functionality, in detail. I prefer to specify the functionality of in principle each function that should be called by the task.
- Interface, which data the task should share with other tasks and how.
- Blocking condition, each task except for the background task needs to block on something, so that tasks with equal or lower priority will have a chance to execute.
- Unblocking condition, how should the task be unblocked and when and by whom.
Setting task priorities is many times very difficult. But on the other hand it is very easy to change the priority of a task in your code, you just have to change a figure in the create call. Changing priorities dynamically should be avoided, as this will make it much more complicated for you to verify that all tasks meet their deadlines all the time.
Two rules of thumbs can be used when you set task priorities:
- Criterion of Urgency: Task with shorter deadline should be given Higher Priority
- Criterion of Criticality: Task whose failure to meet its deadline will be very harmful should be given a very High Priority.