Date post: | 03-Apr-2018 |
Category: |
Documents |
Upload: | bilal-shahid |
View: | 213 times |
Download: | 0 times |
of 38
7/28/2019 RTbook Chapter 2
1/38
2 Real-Time Operating Systems (by Damir Isovic)Summary: The previous chapter provided a general introduction to real-time systems. Thischapter discusses the operating system level supports essential for the realization of real-time
applications. There exist a multitude of real-time kernels1 and they provide varying levels of
support with regard to real-time systems realization. We provide an overview of commercially
available RTOSs as well dwell upon some of the academic research initiatives. The goal of
this chapter is provide the student with awareness of the characteristics of RTOSs and
available alternatives.
2.1 Learning objectives of this chapterAfter reading this chapter you should be able to:
Understand basic concepts in operating systems, such as communication, synchronization,
interrupts, I/O, memory management, time-management etc., with special emphasis on
their real-time implications.
Obtain a thorough knowledge regarding classes of operating systems and features
commonly supported.
Get an overall perspective of the various commercial operating systems and academic
research kernels and how they realize/implement real-time properties.
Understand the important issues to consider when choosing a real-time operating system
for your development project (especially the role of application characteristics in this
selection).
2.2 Concurrent task executionIn Chapter 1, we said that one of the main characteristics of real-time systems is multitasking,
where several tasks compete for execution on a same CPU. Note that this is simulated
parallelism tasks are not really executed in parallel but the operating system creates an
impression of parallelism by switching the execution of tasks very often and fast. This is
different from true parallelism where several processing units are used simultaneously, e.g. as
in multiprocessor systems. Simulated parallelism does not come without problems; we will
look into some of them.
Let us consider again the example with the electrical motor from Chapter 1, see Figure 3. The
application consist of an electrical motor with a sensor and an actuator, user input throttle and
1
We
will
use
the
terms
Operating
System
(OS)
and
kernel
interchangeably,
even
though
an
OS
typically
consistsofitscentralparts thekernel andadditionalservices.DuetothesimplicityofmanyRealTimeOSs,
theydonotprovideanyadditionalservices,suchasafilesystem.
7/28/2019 RTbook Chapter 2
2/38
a computer system (controller). The question now is how we should design the controller
software.
The first question we can ask ourselves is what the controller software should do. The answer
is that it calculates the control values to be sent to the motor based on sensor inputs, as
described in Figure 1. Hence, the software needs to periodically read the values from thesensor and the user, compute the new control values, and actuate the motor.
Figure1:Electricenginesoftwareinteraction.
So, how should we structure the software? Lets try first with a simple, naive approach, which
is to put all functionality in a single loop that repeats itself periodically, as described below.
void main (void){
/* declare all variables */
...
/* repeat continuously */
while(1) {
sensor_val = read_sensor(); / * r ead sensor */
user_val = read_user(); / * r ead user i nput */
control(sensor_val,user_val,&signal); / * comput e new val ue*/
write_actuator(signal); / * act uate mot or */
}
}
Can you see any problems with this solution? One big problem is blocking in execution. For
example, if the function read_sensor() is blocking, i.e., it does not return any result until a
new sensor value has been generated, then the whole system will be blocked the system is
busy waiting. It gets worse if the function read_user() is blocking, since it waits for even
more rare events generated by the user. We see that the functions in the example above are
Controller
(program)
sensor
joystick
read
read
actuator
write
7/28/2019 RTbook Chapter 2
3/38
blocking each other, despite the fact that they are independentfrom each other. The lack of
user input should not affect reading of the engine sensor and vice versa.
We can avoid the blocking problem above by checking in the main loop if the values are
ready to be read, by checking the status registers of the input ports. For simplicity we can
assume we have input ports that automatically reset their status bits when the corresponding
data registers are read a rather common case in practice.
...
while(1) {
i f ( SENSOR_VALUE_READY) {
sensor_val = read_sensor();
control(sensor_val,user_val,&signal);write_actuator(signal);
}
i f ( USER_VALUE_READY) {
user_val = read_user();
}
}
}
This solution is better than the first one, since no blocking will occur. However, it is not
resource-efficient, because the while-loop will run all the time, i.e., it will consume all CPU
time regardless how frequently the sensor values are generated if there is a sensor value
available the corresponding action will be performed, if there is no value, the loop will
proceed right away. This is a big CPU time wastage; we should be able to execute other tasks
in the system when there are no sensor values ready at the input ports.
We could avoid unnecessary execution by checking first if any interrupts has been generated:
...
while(1) {
wai t _f or_syst em_I RQ( ) ; /* stop execution until next interrupt */
if(SENSOR_VALUE_READY){ ... }
if(USER_VALUE_READY){... }
}
7/28/2019 RTbook Chapter 2
4/38
This solution is called cyclic executive. The main idea is to put all functions in a sequence that
executes in a joint loop with certain periodicity. If we do not want all functions to run at each
loop iteration, this can be controlled by loop counters.
Cyclic executive is simple and deterministic, thats why it is still a commonly used solution in
simple embedded systems in the industry. On the other hand, one disadvantage is that the
execution schedule is handmade, i.e., it must be redone for each single change in the
system. However, the biggest disadvantage of cyclic executive is that it does not consider the
execution time of the independent computations in the loop.
For example, what happens if the sensor values are much more frequent than the user values,
while the processing time of the user values is much larger? In this case, several sensor values
will be generated while the computer responds to the user input sensor values will be lost!
This whole situation is depicted in Figure 2.
Figure2:Lostsensorvalues.
To prevent losing any sensor values, we need to preemptthe execution of the user task each
time a new sensor value has been generated, process it, and then re-invoke the execution of
the user task as shown in Figure 3.
Figure3:Manualinterleavingofprogramexecution.
In other words, we need to do manual interleaving of execution, i.e., we need to do changes inthe program so that the processing of user input will be stopped whenever a new sensor value
time
user
sensor
time
sensor
user
Preempt execution of user task
and run sensor task
7/28/2019 RTbook Chapter 2
5/38
is generated. Generally, it is not easy to do code interleaving by hand, i.e., writing in the code
where the preemption should occur it usually result in many inserted if-cases in the program
that check if it is time to preempt the current execution and let somebody else execute.
Instead, we would like to split the application into independent tasks in the system, implement
them as separate threads2, and let the operating system (OS) take care of interleaving, i.e.,
interleaving is automatic. This means that the application programmer does not have to insert
any special code to make task switching happen, or take specific action to save the local
context when switching occurs. On the other hand, the programmer must be prepared that
switching might occur at any time.
In our electrical motor example, we would create two tasks, one that takes care of the sensor
values and calculate new control values, and one that check the user input, and implement
them as real-time threads described below:
When activated by the operating system, both tasks will performs their actions and, when
done, they will call a sleep function that waits until it is time to repeat the computation. By
calling sleep, the execution of the task is suspended for a specified time interval, which means
that other tasks in the system can use the CPU. If a sensor value is generated while the user
input is processed (User_Task executes), the operating system will preempt it, and start
executing Sensor_Task. When Sensor_Task is done, the OS will continue the execution of
User_Task at the exact point it was interrupted.
So, as an application programmer, we need to structure our application as separate tasks,
assign appropriate timing constraints to them (e.g., deadlines and periods) and let the
operating system take care of all multitasking issues, such as interleaving, resource allocation,
scheduling etc. We will now start looking at a special type of operating systems that is
suitable for handling real-time tasks, real-time operating systems.
2WesaidinChapter1thatathreadisanimplementationofatask,andthattheyareusuallyusedas
synonymous.Hence,wewilluseonlythetermtaskinthefuturetoavoidconfusion.
void Sensor_Task(){
while(1) {
sensor_val = read_sensor();
control(sensor_val,user_val,&signal);
write_actuator(signal);
sleep();
}}
void User _Task(){
while(1) {
user_val = read_sensor();
sleep();
}
}
7/28/2019 RTbook Chapter 2
6/38
2.3 What is a Real-Time Operating System?A real-time operating system (RTOS) is an operating system capable to guarantees certain
functionality within specified time boundaries. Special functions available in a RTOS
simplify and make it more efficient to develop software for real-time systems. We can say
that a RTOS is a platform suitable for development of real-time applications. Figure 4
illustrates a simplified model of a RTOS used to provide services to a real-time application.
At the bottom of the figure there is the physical hardware, i.e., the CPU itself with I/O
devices, registers, memory, communication circuits, analog/digital convertors, etc. Hardware
Adaptation Layer (HAL) contains hardware dependent code needed to communicate with the
underlying hardware, i.e., device drivers, register handling code, interrupt handling code etc.
The RTOS itself uses the functionality provided by HAL, for e.g., scheduling, communication
and synchronization. The RTOS can also communicate directly with the hardware, but it is
better to use a HAL, since it will make the application easier to port to different platforms we only need to change the HAL when moving applications to other platforms, not the
application or RTOS code. Finally, the real-time application uses the services provided by the
RTOS. Those services are usually calledsystem calls.
Figure4:ARealTimeOperatingSysteminitsenvironment.
2.4 RTOS characteristicsIt is certainly possible to implement real-time applications without using a real-time operating
system, but an RTOS make the work much easier, mainly due to the following properties:
Task management By using an RTOS, the development of real-time application software
becomes easier and more efficient. We could see in previous examples that if we need to
develop an application consisting of several independent computations, it is a good idea to
split the application into independent tasks and let the operating system manage their
execution. Services in this category include the ability to launch tasks and assign priorities to
them. The most important service in this category is scheduling of tasks, which will make the
task execute in a very timely and responsive fashion. We will talk about different real-time
scheduling policies in Chapter 3.
RTOS
Hardware Adaptation Layer
Hardware
Application software
7/28/2019 RTbook Chapter 2
7/38
Resource management an RTOS provides a uniform framework for organizing and
accessing the hardware devices that are typical of an embedded real-time system. This
includes services for management of I/O devices, memory, disks, etc.
Communication and synchronization These services make it possible for tasks to pass data
from one to another, without danger of that data to be damaged. They also make it possible
for tasks to coordinate, so that they can productively cooperate. Without the help of these
RTOS services, tasks might well communicate corrupted data or otherwise interfere with each
other.
Time services Obviously, good time services are essential to real-time applications. Since
many embedded systems have stringent timing requirements, most RTOS kernels also provide
some basic timer services, such as task delays and time-outs.
Homogeneous programming model an RTOS provides a number of well defined system
calls, which makes it easier to understand and maintain the application code. Furthermore,
this reduces the development effort and risk, since a homogenous programming model usually
implies usage of a streamlined set of tools and methods to get a quality product into
production as quickly as possible. By using an RTOS, we use existing reliable proven
building blocks, which substantially increase the quality of the product.
Portability an RTOS simplifies the porting between different platforms. Since most of the
available RTOSs can be adjusted by the manufacturer to support different platforms, using an
RTOS makes it simpler for the customer to change the hardware platform. Besides, standards
are covering all the possible interaction and interchanges between subsystems, componentsand building blocks.
In general, we can say that using an RTOS makes it much easier to implement and maintain
real-time applications compared with doing everything from scratch. Hence, an RTOS should
be used whenever possible when developing real-time systems.
2.5 RTOS vs GPOSWhat is the difference between an RTOS and a general-purpose operating system, such as
Windows or Linux? Many non-real-time operating systems also provide similar kernel
services as an RTOS, so why use an RTOS?
The key difference between general-purpose operating systems (GPOS) and real-time
operating systems is the need for deterministic timing behavior in real- time operating
systems. Deterministic means that provided OS services consume only known and expected
amounts of time. General-purpose operating systems are often quite non-deterministic. Their
services can inject random delays into application software and thus cause slow
responsiveness of an application at unexpected times.
7/28/2019 RTbook Chapter 2
8/38
Hence, the fundamental difference between an RTOS and a GPOS is the view on result, i.e.,
the temporal aspect is very important in an RTOS, which means, among other things, that:
Service calls must be predictable with a known upper bound on execution time.
Task execution switching has to be done by some algorithm that can be analyzed for
its timing.
Delay spent in waiting for shared resources must be possible to determine.
The maximum time that interrupts can be disabled must be known.
Another thing that differs is the clock resolution; which is higher in an RTOS than in a GPOS.
Each operating system has a system clock used for scheduling of activities. To generate a time
reference, a timer circuit is programmed to interrupt the processor at a fixed rate. The internal
system time is represented by an integer variable, which is reset at system initialization and is
incremented at each timer interrupt. The interval of time with which the timer is programmed
to generate interrupts defines the unit of time in the system (time resolution). The unit of timein the system is called a system clock tick, see Figure 5.
Figure5:Systemclock.
The value to be assigned to the tick depends on the specific application. In general, small
values of the tick improve system responsiveness and allow handling of periodic activities
with the higher activation rates. On the other hand, a very small tick causes a large run-time
overhead due to the timer handling routine: the smaller the tick value, the more CPU time is
needed for the time administration. Typical values used for the time resolution in an RTOS
are on a millisecond level or less, while in a GPOS, the clock resolution is an order of
magnitude higher, i.e., tens of milliseconds.
2.6 Types of RTOSsWe said before that one classification of real-time system is into event-triggered and time-
triggered, based on if the system activities are carried out as they come, or at predefined
points in time. Even real-time operating systems are divided into these two types.
In an event-triggered RTOS, each task is assigned a priority relative to other tasks in the
system. High priority values represent the need for quicker responsiveness, i.e., if there are
several tasks competing to execute on a single CPU, the task with the highest priority will beexecuted first. Priorities can be assigned before run-time of the system (static priority
1time
0 2 43
System clock
Clockresolution
Clock ticks
7/28/2019 RTbook Chapter 2
9/38
assignment), or at run-time (dynamic priority assignment). Synchronization between tasks is
usually done in asynchronous way, by sending messages during execution.
Although we use priorities to determine the order of task execution, it is not always
guaranteed that this order will be preserved at run-time. That is because of shared resources,
such as shared variables or shared I/O devices. If a low priority task is currently using a
resource that is requested by a high priority task, the high priority task will need to wait until
the resource becomes free. However, in an RTOS, the waiting time can be calculated and
guaranteed (which is not the case in a general-purpose operating system).
In a time-triggered RTOS, tasks are executed according to a schedule determined before the
execution. Time acts a means for synchronization. Since all decisions about task execution,
synchronization and communication are made before run-time, the run-time mechanism is
quite simple it just reads the schedule and executes the task according to it.
You can compare this type of RTOS with a bus schedule; a bus company makes a schedule
for a bus that is valid during some time period. Every day the bus drives according to the
schedule, which makes it possible for the people to know when the bus is arriving at a certain
bus stop. The schedule is repeated over and over. This is exactly what happens in time-
triggered real-time systems: we use some scheduling algorithm to create a schedule before
putting the system into use, and then, at run-time, we just follow the schedule. For example,
the schedule could say at time 5 run task A until time 8, at time 8 run task B until 12, etc.
There are also real-time operating systems that support both the event-triggered and time-
triggered paradigms. A difficulty with those hybrid systems is the communication delaysbetween the two parts as well as the CPU sharing between event-triggered and time-triggered
tasks.
2.7 Event-triggered Real-Time Operating SystemsMost commercial real-time operating systems are priority-driven, that's why we will put the
emphasis on event-triggered RTOSs in this book. Here we describe some of the most common
services provided in an event-triggered RTOS. We will not focus on a specific RTOS, but
discus some general mechanisms common for most of them. A comparison between differentcommercial RTOSs will be presented at the end of this chapter.
Preemption and context switch
In most event-triggered real-time operating systems, a task with assigned higher priority will
be able to preempt the execution of currently running lower priority task.
7/28/2019 RTbook Chapter 2
10/38
Figure6:Preemptionbetweentasks.
Figure 6 illustrates what happens when preemption occurs. We see in the figure that the
RTOS will stop the execution of a task if there is a higher-priority task that wants to execute.
When task2 becomes ready to execute at time t2, it will preempt the lower-priority task 1,
and when the highest-priority task3 gets ready, it preempts 2 (and hence, indirectly preempts
even 1).
Each time the priority-based preemptive RTOS is alerted by an external world trigger (such as
a switch closing) or a software trigger (such as a message arrival), must determine whether
the currently running task should continue to run. If not, the following steps are made:
1. Determine which task should run next.
2. Save the environment of the task that was stopped (so it can continue later).
3. Set up the running environment of the task that will run next.
4. Allow this task to run.
These steps are together called task switching(orcontext switch).
The time it takes to do task switching is of interest when evaluating an operating system. A
general-purpose operating system might do task switching only at timer tick times, which
could be tens of milliseconds apart. Such a delay would be unacceptable in most real-time
systems. For this reason, most real-time operating systems do not rely on system clock
scheduling alone. Rather, it is used in combination with other events in the system, such as a
new task is released, a task gets blocked, an external interrupt occurs etc.
Task structure
A real-time task in an event-triggered RTOS consists of:
Task Control Block (TCB) a data structure that contains task ID, task state, start
address of the task code, and some registers, such as program counter and status
register.
Program code the binary representation of the code to be executed by the task,
which was originally implemented in some programming language, e.g., the C-
language.
2preempts 1
High-priority task3
Low-priority task1
Middle-priority task2
timet1 t2
t3 t4
t5
1preempts 2
7/28/2019 RTbook Chapter 2
11/38
Data area stack and heap
Figure
7:
A
structure
of
a
task
When a real-time kernel creates a task, it allocates memory space to the task and brings the
code to be executed by the task into memory. In addition, it instantiates the Task Control
Block and uses the structure to keep all the information it will need to manage and schedule
the task. When a task is executing, its context changes continuously. When the task stops
executing, the kernel keeps its context at the time in the task's TCB. When we say that the
RTOS inserts a task in a queue (e.g., the ready-queue), we mean that it inserts a pointer to the
TCB of the task into the queue. The kernel terminates a task by deleting its TCB and de-allocating its memory space.
The TCB has pointers to the task code and data area, see Figure 7. Separation between task
code and task data is done because we want to be able to store the code and the data in
different types of memories. In embedded systems the task code is usually stored in an
EPROM3, especially in system-on-chip computers, where the objective is to avoid usage of
external memory as much as possible. In such systems, the resources are usually limited, not
allowing the code to use read- and write memory.
Another reason of separating program code from the data area is to be able to reuse the samecode for different tasks. For example, assume a PID4 controller that has been used and tested
for a while. We want to add an additional PID controller, that will perform the same action
but with different input values, e.g., with different control parameters and periodicity. In this
case we can use the same program code for both PID controllers, as illustrated in Figure 8.
3EPROM:ErasableProgrammableReadOnlyMemoryisatypeofmemorythatcanbeerasedandrewritten,
usuallybyusingultravioletlight.4PID:Aproportionalintegralderivative controllerisagenericcontrolloopfeedbackmechanismwidelyused
inindustrial
control
systems.
It
attempts
to
correct
the
error
between
ameasured
process
variable
and
adesiredsetpointbycalculatingandthenoutputtingacorrectiveactionthatcanadjusttheprocessaccordingly
andrapidly,tokeeptheerrorminimal.
Data area
main(){
init;loop forever
....
wait(...);
}
Program codeTCB
- Task state- Staus register
- Program counter{
}
- Task ID
7/28/2019 RTbook Chapter 2
12/38
Figure8:Sharedprogramcode.
Reentrant code
To be able to reuse the same code in different tasks, as in PID example above, the code must
be pre-emptable in the middle of the execution without any side effects, i.e., the code must be
reentrant. A reentrant piece of code can be simultaneously executed by two or more tasks.
Example: Is the function swap that exchanges the values of two variables reentrant?
int temp;
int swap(int *x, int *y){
temp = *x;
*x = *y;
*y=temp;
}
The answer is no, function swap is not reentrant. The reason for this is usage of a global
variable temp. Assume a low priority task L and a high priority task H that both use function
swap. Assume H does not want to execute for the moment, and L starts to execute. After L has
executed the code line temp=*x, task H becomes ready and preempts task L. During its
execution, H will change the value oftemp, so when L resumes its execution again, the value
of*y will be wrong. The whole scenario is illustrated below.
main(){init;
loop forever
....
wait(...);
}
Shared
program code
Parameters
(PID 1) {
}
Parameters
(PID 2)
Data area
(PID 2)
Data area
(PID 1)
TCB PID 1
- Task state- Staus register
- Program counter
- Task ID
TCB PID 2
- Task state
- Staus register
- Program counter
- Task ID
7/28/2019 RTbook Chapter 2
13/38
task_L(){
int x=1, y=2;
swap(&x, &y); ------------->temp = *x; / * t emp=1 */
(H preempts L)
-------------->
task_H(){
int z=3,t=4;
swap(&z,&t); ------->
temp=*z; / * t emp=3 */
*z=*y; / * z=4 */
*y=*temp; / * y=3 */
7/28/2019 RTbook Chapter 2
14/38
Figure9:Taskstatesandstatetransitions.
Dormant This state means that the task is not yet consuming any resources in the system.
The task is registered in the system but it is either not activated yet or has terminated.
Executing A task enters this state as it starts to run its code on the processor. Only one task
can be executing at a time (on a single-core processor).
Ready By entering the ready state, a task expresses its wish to gain access to the processor,
i.e., when it wants to execute. A ready task cannot gain control of the CPU until all higher
priority tasks in the ready or executing state either complete, or become dormant.
Waiting A task enters this state when it waits for an event, e.g., timeout expiration, or a
synchronization signal from another task. The before mentioned sleep function will put a task
in a ready state.
Blocked A task is blocked when it released but cannot continue its execution for some
reason. For example, it may be blocked waiting for a shared resource to be free.
Not all states have to be supported by an RTOS, but in any kernel that supports execution of
concurrent tasks on a single processor, there are at least states executing, ready and waiting.
The next obvious question we can ask ourselves is which state transitions are valid. This is
summarized in Figure 9.
DormantReady: This transition occurs when a task is activated.
Ready Executing: The task that has the highest priority among all ready tasks at the
moment will start to execute.
ExecutingReady: If another task, with higher priority than the currently executing task, has
become ready, it will preempt the current task, and become executing itself. The preempted
task will then go to the ready state (where it has to compete again with all other ready tasks).
Executing Waiting: An executing task becomes waiting by e.g., invoking a system call
sleep at the end of its execution in the current period time. When the waiting time has elapsed,the task will become ready again.
Ready
Waiting
Dormant
Executing
Blocked
7/28/2019 RTbook Chapter 2
15/38
Executing Blocked: An executing task becomes blocked when it comes to a point in its
execution that it cannot proceed, since it cannot get the access to a necessary resource that is
locked by some other task. It is important to note the difference between blocked and waiting
a task is forced to enter the blocked state, while it voluntarily enters the waiting state.
Executing Dormant: When a task has terminated or completed its execution, it becomes
dormant. Tasks in this state may be destroyed.
WaitingReady: When a task has spent the desired time period in the waiting state, it goes
back to ready. Why can't it enter executing state directly? The answer is: there might be other
tasks in the system that also are ready, and some of them might have higher priority. Hence,
all transitions to the executing state go through the ready state, i.e., all ready tasks are
transferred in the ready queue, and sorted to execute based on their priorities.
Blocked Ready: When the resource that caused a task to become blocked has been freed,
the task can proceed to execute. However, it must go via the ready queue for the same reason
as discussed above.
When does a state transition occur? The answer is:
At system clock timer interrupts. At each clock tick, the RTOS increases the systemtime, and then checks if there are any task transitions to be made. For example, if there
is a ready task with higher priority than the currently execution one, task switch
occurs.
When a task invokes a system call such as e.g., sleep.
At external interrupts, i.e., when an interrupt routine invokes a system call that causesa task switch.
The kernel invokes the scheduler to update the ready queue whenever it wakes up or releases
a task, finds a task unblocked, creates a new task, and so on. Thus, a task is placed in the
proper place in the ready-queue as soon as it becomes ready.
Time handling functions
We mentioned before that an RTOS has a system clock for scheduling of activities. Lets have
a look on some functions that operate on the system clock.
getTime() get system time
setTime(t) set system timeadjustTime(t)- adjust system time
The first two services are pretty self-explanatory, but why do we need adjustTime(t)? Can't
we just use setTime(t) if we want to change the system time? Yes, in many cases we can,
but consider the following case: assume that we have scheduled a number of tasks to run at
different clock ticks that belong to a time interval [t1, t2]. Assume that, at time t1 we call
setTime(t2). If we jump from t1 to t2, all scheduled tasks in the interval will be released at
once, compared to different release times if we do not set a new time. This will create a
temporary overload in the system that can crash it. If we, however, use the adjustTime
function, it will adjust the time in discrete steps, i.e., it will either speed up or slow down the
system clock during a certain period until we reach the desired time. This will avoid multiplesimultaneous task releases.
7/28/2019 RTbook Chapter 2
16/38
Another problem that you should be aware of when using time functions is a risk for wrong
timestamps. Consider a task where we read some sensor data and timestamp it, i.e., we record
at which point in time the reading occurred. Assume that the task code looks like this:
void Task_T() {struct a;...a.value = read(sensor);a.timeStamp = getTime();...
}
Figure 10-a illustrates the case when the task executes without any interruption. Thetimestamp value will be correct. But what if the task gets preempted after reading the sensor
value, but before timestamping it, as depicted in Figure 10-b? In this case, the timestamp will
not reflect the actual reading time, since we will read the data at a certain point in time that
differ from the one recorded, i.e., the recorded time stamp can be much later than the actual
reading took place, because of the preemption which delayed reading of current system time.
Figure10:Wrongtimestampduetopreemption.
One possible solution to this problem could be to disable interrupts while reading the sensor
data and timestamping it. However, this is a quite drastic solution because disabling interrupts
means also not being able to respond to external events within that time, which may be a
preemption
time
read(sensor)
getTime()
time
Task execution
read(sensor) getTime()
High-priority task
a)
b)
7/28/2019 RTbook Chapter 2
17/38
problem in case of urgent events. A better solution would be to protect the code with some
resource access mechanism, such assemaphores, which will be described next:
Semaphores
A critical region is a sequence of statements in the code that must appear to be executedindivisibly (or atomically). In the timestamp example above, the critical region contains both
reading the sensor value and putting a timestamp on it.
Asemaphore is a data structure used for protection of critical regions. In more general terms,
we can say that semaphores are used for synchronization between tasks by providing mutual
exclusion when several tasks are accessing same resources. Mutual exclusion means that only
one task is using the resource at a time. Here is a code example on how to use semaphores:
void Task_T(){
...
if (l ockSemaphore( S) ) { /* try to get semaphore S */
/* critical region entered*/
a.value = read(sensor);
a.timeStamp = getTime();
unl ockSemaphore( S) ;
/* critical region exited */
}else
/* failed to lock semaphore S */
...
}
The typical semaphore mechanism used in traditional operating systems is not suited for
implementing real-time applications because it is subject to priority inversion, which occurs
when a high-priority task is blocked by a low-priority task for an unbounded interval of time.
Priority inversion must absolutely be avoided in real-time systems, since it introduces non-
deterministic delays on the execution of critical tasks. Priority inversion can be avoided by
adopting particular protocols that must be used every time a task wants to enter a critical
region. We will talk about those real-time resource access protocols later, in the scheduling
chapter.
7/28/2019 RTbook Chapter 2
18/38
Interrupt handling
If not handled properly, interrupts generated by external devices can cause a serious problem
for predictability of a real-time system, since they can introduce unbounded delays in task
executions. The objective of the interrupt handling mechanism of an RTOS is to provide
service to the interrupts generated by attached devices, such as the keyboard, serial ports,sensor interfaces, etc. This service consists of the execution of a dedicated routine (device
driver) that will transfer data from the device to the main memory or vice versa.
In classical operating systems, application tasks can always be preempted by drivers, at any
time. In real-time systems, however, this approach may cause some hard task deadlines to be
missed. Hence, in real-time systems, the interrupt handling mechanisms must allow the most
critical tasks to execute without interference. This can be done by using one of the following
techniques:
Disable all external interrupts This is the most radical approach, where all peripheral
devices must be handled by the application tasks, which have direct access to the registers ofthe interfacing boards. Since no interrupt is generated, data transfer takes place through
pollingi.e., periodically checking if any new event occurred. The main disadvantage of this
approach is low processor efficiency on I/O operations, due to the polling.
Manage external devices by dedicated kernel routines All external interrupts are disabled,
but the devices are handled by dedicated kernel routines rather than application tasks. The
advantage of this approach with respect to the previous one is that all hardware details of the
peripheral devices can be encapsulated into kernel procedures and do not need to be known to
application tasks. A major problem of this approach is that the kernel has to be modified when
some device is replaced or added, since the device handling routines are part of the kernel.
Allow all external interrupts, but reduce the drivers to the least possible size According to
this approach, the only purpose of each driver is to activate a proper task that will take care of
the device management, see Figure 11. Interrupt handling is integrated with the scheduling
mechanism, so that the task that handles an interrupt event can be scheduled as any other task
in the system. This way, we control the execution order by assigning priorities to tasks, rather
than allowing random interrupts to preempt high priority tasks. Thus, an application task can
have a higher priority than a device handling task. Besides, this approach has high CPU
efficiency on I/O operations, since the interrupts are handled when they occur (no polling).
Figure11:
A
technique
for
handling
interrupts.
External
interrupt
Hardware HAL Application
Interrupt routine
Task that
handles the
interrupt
7/28/2019 RTbook Chapter 2
19/38
Almost every system allows you to disable interrupts, usually in a variety of ways. For
example, most I/O chips allow a program to tell them not to interrupt, or, microprocessors
allow your program to tell them to ignore incoming signals on their interrupt request pins, by
either writing a value in a special register in processor, or with a single assembly language
instruction.
As mentioned before, disabling interrupts in real-time systems might lead to serious
consequences. In this type of system the question how fast the system responds to each
interrupt is crucial. In other words, we must know the longest time the RTOS can disable
interrupts, which is known as interrupt latency.
Low interrupt latency is not only necessary for hard real-time systems. Even in soft real-time
systems, it is needed for reasonable overall performance, particularly when working with
processing of audio and video. In order to have reasonable soft real-time performance (for
example, performance of multimedia applications), the interrupt latency caused by every
device driver must be both small and bounded.
Interrupt latency is one of the most important factors when choosing RTOS for an application.
If there is an interrupt in the system that must be served faster than the length of time the
interrupt is disabled in the RTOS, then we cannot use that RTOS. This because the handling
of the interrupt can be delayed for the amount of time that, in worst case, is equal to the
interrupt latency. This is illustrated in the example of Figure 12. A task that handles an
interrupt gets preempted by the RTOS kernel with an interrupt disable just before the interrupt
occurs. In that case, the task will not be able to handle the interrupt until the kernel has
enabled interrupts again.
Figure12: Interruptlatency.
time
Task
RTOS kernel
Event that
causes
interrup
occurs
Interrupt handling time
RTOS
interrupt
latency
Interrupt routine
7/28/2019 RTbook Chapter 2
20/38
Task execution mechanisms
Most real-time tasks are periodic, i.e., they perform the same computation again and again,
with a specified period time interval between two consecutive invocation (e.g., reading of a
sensor value each 100 milliseconds). An individual occurrence of a periodic task is called
task instance (also known asjob), see Figure 13.
Figure13:Periodictaskinstances.
It is clearly inefficient if the task is created and destroyed repeatedly every period. In an
operating system that supports periodic tasks, the kernel itself re-initializes such a task and
puts it to sleep when the task completes. We just need to assign period to tasks before run-
time, and the kernel keeps track of the passage of time and releases (i.e., moves to the ready
queue) the task again at the beginning of the next period. It is very similar to regular function
calls that are called repeatedly. The task can be re-invoked infinitely, i.e., under entire lifetime
of the system, or it can be terminated after a finite number or instances. Here is an example:
int period_time = 50;
...
void task_(){
.../ * do t ask wor k */
...
/* kernel takes over when the task is done */
}
Most commercial RTOSs, however, do nothave implicit mechanism for periodic tasks at the
kernel level. Since many real-time tasks are of periodic nature (e.g., sampling), there must
exist some other mechanisms available in those RTOSs to implement periodic tasks explicitly,
at user level.
Tasktime
Instance k
is invoked
period
Instance k+1
is invoked
Instance k+2
is invoked
(and so on)
instance k instance k+1 instance k+2
7/28/2019 RTbook Chapter 2
21/38
We can implement a periodic task at user level as a thread that alternately executes the code
of the task and sleeps until the beginning of the next period. In other words, the task does its
own re-initialization and keeps track of the time for its own next release, e.g.:
void task_1{
int period_time = 50;
whi l e(1){ / * do f or ever */
/ * do t ask work * /
...
/ * wai t some t i me and r e- i nvoke */
sl eep( per i od_t i me) ;
}
}
The infinite while-loop ensures that the task instances will be invoked repeatedly, and sleep-
function makes sure that there will be some time interval between consecutive invocation. In
other words, at the end of its execution a task suspends itself for some time (goes from
executing into waiting state), allowing lower priority tasks to use the processor. Without
sleep, the task would run all the time, consuming all CPU time, and no lower priority tasks
can execute (higher priority tasks can preempt it).
The sleep-function can be implemented in different ways, providing relative or absolute
delays. Relative delay means that the next instance will be released when specified time,
relative to the call time, has elapsed. The sleep-function in the code example above provides
a relative delay, i.e., it will put the task into waiting state for a number of clock ticks specified
by period_time, i.e., 50 in this example. However, an alert mind will notice directly that the
implementation above will not really achieve the desired period time, since it does not take
into the consideration the execution time of the task. If, for example, it takes 10 clock ticks to
execute the task, then the next instance will be released at 10+50 ticks, which in not what we
want. We can solve this by subtracting the execution time from desired period time, e.g,:
...
/ * do t ask wor k f or 10 cl ock t i cks */
...
sleep(per i od_t i me- 10);
...
7/28/2019 RTbook Chapter 2
22/38
The solution above is not general; the period will be 50 only if the task is either the only one
in the system, or if it has the highest priority. Otherwise, if there are other higher priority tasks
in the system, they might preempt the task just before calling the sleep-function. Assume, for
example, such a higher-priority task 2 that has an execution time of 20 clock ticks.
Preemption after 10 ticks of execution will cause 1
to invoke the next instance after 70 ticks,
not desired 50 ticks, as illustrated in Figure 14.
Figure14:Relativedelay.
Hence, we need to include the preemption time in the execution time of1, which can be done
by using timestamps, as follows:
void task_1(){
...
while(1) {
st ar t _t i me = getTime();
/ * do t ask work * /
...
st op_t i me = getTime();
sleep(period_time-(stop_time-start_time));
}
}
An absolute delay will suspend the task execution until the system clock has reached
specified time (counted from start of the system). The system call used to provide absolute
delay is usually called sleepUntil, delayUntil orwaitUntil. Here is an example how we
can implement the task above by using absolute instead of relative delay.
Task1
Task2
timet t+30 t+70
wait_time=40
Next instance
of1 released
7/28/2019 RTbook Chapter 2
23/38
void task_1(){
...
period_time = 50;
next_time = getTime();while(1) {
/ * do t ask work * /
...
next_time = next_time + period_time;
sl eepUnt i l (next_time);
}
}
If the tasks starts to invoke its instances at time t, i.e., initial next_time is equal to t, then all
consecutive values ofnext_time will be t+50, t+100, t+150, etc, regardless if the task gets
preempted or not.
J itter
We have showed above how to implement periodic tasks with help of relative and absolute
delay. Correctly calculated period, however, does not necessarily mean that the distance
between execution of consecutive task invocations will be constant. There can be variations in
the actual execution, caused by high priority tasks. Those variations are called jitter.
Consider the following example; assume two periodic tasks, 1 and 2, with the execution time
2 and 1, and the period time 4 and 10 respectively. Assume also 1 has higher priority than 2.
Figure 15 shows what happens if both tasks are released at the same time. Although we have
defined the period of2 to be 10, (i.e., 2 is released at times 0,10,20,etc), the time between
executions of its instances will vary between 8 and 12 clock ticks, based on if preemption
from 2 occurs or not.
Figure15:Jitterinperiodicexecution.
period= 8 period =12
0 4 82 6 10 12 14 16 18 20 22 24
2
1
7/28/2019 RTbook Chapter 2
24/38
The objective is to minimize jitter for each task (ideally jitter=0). The smaller the jitter, the
better periodicity of a task's execution. But that is not easy; the only task for which we can
guarantee jitter free execution is the highest priority one, still under condition that no external
interrupts take place. In all other cases, tasks can get jitter. We will show later in the
scheduling chapter how we can calculate the effect of jitter when predicting the system
behavior.
Communication and synchronization mechanisms
Often, tasks execute asynchronously, i.e., at different speeds, but may need to interact with
each other, e.g., to communicate data to each other, or to access shared resources. Most real-
time operating systems offer a variety of mechanisms for handling task interactions. These
mechanisms are necessary in a preemptive environment of many tasks, because without them
tasks might communicate corrupted information or otherwise interfere with each other. For
instance, we could see in the swap-example above that the preemption in the middle of theoperation can cause wrong values to be assigned to variables that are to be swapped. Another
example is two tasks sharing the same display, where one task measure the current
temperature of the air and displays it as e.g. "10o C", while the second one displays the current
time e.g., "23:15". If we do not protect the access to the display device, it might result in
strange display caused by one task preempting another one. For example, the temperature-task
writes "10" and, before writing the rest of the display text, the second task preempts it and
writes its own text "23:15", which would result with the display output being: "1023:15". We
can, for example, use semaphores to protect the access to the shared resource and solve the
problem.
The simplest way of the communication between tasks is through a shared memory, where
each communicating task may update pieces of shared information/data, as illustrated in
Figure 16.
Figure16:Communicationthroughsharedvariables.
Communication through shared memory is easy and efficient way of communication. It
provides a low-level, high bandwidth and low-latency means of inter-task communication. It
is commonly used for communication among tasks that run on one processor, as well as
among tasks that run on tightly coupled multiprocessors.
The disadvantage with this approach is data overwrite, i.e., the old values are overwritten by
the new ones since no buffering is provided. Another difficulty is to synchronize accesses toshared memory. The application developer must make sure that the data access is atomic, i.e.,
Task 1 Task 2Shared
variable v
write read
7/28/2019 RTbook Chapter 2
25/38
tasks must not be interrupted while updating the shared memory space. This can be achieved
by protecting the shared variable with a semaphore, as shown in the code example below:
An alternative is to use Wait- and lockfree communication, which is a method to accomplish
non-blocking communication between tasks. Non-blocking means if two or more tasks want
to read from a wait- and lockfree channel, WLFC, no one of the readers is delayed by another
task (compare it to the shared variables approach, where tasks get blocked if some other task
is updating the variable). This is done by assigning one buffer to each reader of the WLFC.
One extra buffer is added to assure there always exists one free buffer in the WLFC, see
Figure 17.
Figure17:Wait andlockfreecommunication.
Both tasks get their own buffer slot. Task1 starts by writing some data to buffer slot 1. When
task2 starts to read the data from slot 1, task1 continues writing to slot 3. This way, there is
always one free buffer for writing. The formula for calculating the number of needed buffers
is:
nbuffers = nwriters + nreaders + 1
Task 1 Task 2
Buffer slot1 Buffer slot 2 Buffer slot 3
(free slot)
(producer/writer) (consumer/reader)
void sender_taks(){
...
while(1){
...
lockSemaphore(S);
/ * ent er cr i t i cal r egi on */
v = get Val ue( ) ;
/ * exi t c r i t i cal regi on * /
unlockSemaphore(S);
...
}
}
void receiver_task(){
while(1) {
...
lockSemaphore(S);
/ * ent er cr i t i cal regi on */
l ocal = v;
/ * exi t c r i t i cal regi on * /
unlockSemaphore(S);
...
}
}
7/28/2019 RTbook Chapter 2
26/38
Since writers do not share buffer slots, there is no need for atomic write operation. This makes
it good for communication large amount of data that continuously changes. The disadvantage
is that it wait- and lockfree communication requires more memory than shared variables. Here
is an example how wait- and lockfree communication can be used in task code:
Both read- and write functions return a pointer to the buffer to operate on. Since the buffers
are user-defined, it is the user who is responsible for filling the buffer with data. A WLFC
contains an array of buffers, pointers to the oldest and the newest values in the buffer, and a
list of all tasks that can use the buffers. Every time a task becomes READY (due to a new
period), it is assigned (by the kernel) a pointer to a buffer within the WLFC. If the task is a
reader, it will get the most recently written buffer, and if the task is a writer, it will get thepointer to the first free buffer with the oldest value.
Message passingis the most popular technique for transferring data between tasks in a multi-
tasking software environment. Most real-time operating systems use "indirect" message
passing. In this approach, messages are not sent straight from task to task, but rather through
message queues. The idea is that one task will send messages into the queue; and then,
perhaps later on, another task will fetch the messages from the queue, see Figure 18.
Figure18:Communicationthroughamessagequeue.
Before a task can send a message to another task, the message queue needs to be created. Any
task can ask to create the queue; it doesn't have to be the message sender task or the message
receiver task. But, both the message sender and message receiver tasks need to be informed of
Task 1 Task 2
Message queue
void task_Producer(){...while(1){
.../ * get t he poi nt er t he buf f er *// * t o wr i t e t o */buf f _pt r =wr i t eWLFC( buf _I D) ;...
}
}
void task_Consumer(){...while(1){
.../ * get t he poi nt er t he buf f er *// * t o r ead f r om */buf f _pt r =r eadWLFC( buf _I D) ;...
}
}
7/28/2019 RTbook Chapter 2
27/38
the identity of the queue, called a queue identifier, in order for them to communicate through
the queue.
Here is an example code for inter-task communication via message queues:
A message queue can either be global or local. Global means that all tasks in the system can
read messages for the message queue, while local is connected to a specific pair of tasks
(sender and receiver). A message queue is usually implemented as a first-in-fist-out (FIFO)
queue. However, some RTOSs use priority queues instead, which is a better choice. The
sending task can specify the priority of its message, which results in faster de-queuing, i.e., itwill be received faster than lower priority messages.
When creating message queues for inter-task communication, we need to allocate memory to
store the messages in the queue. A common problem when building embedded systems is the
lack of memory. Embedded systems usually have very limited memory and CPU resources
that should be used in the most efficient way. So, when allocating memory for the message
queues we should be careful not to waste more memory than necessary. For example, there is
no point in allocating memory for 50 messages if the queue will maximally contain one
message at a time, i.e., as soon a message arrives, the receiver task removes it from the queue.
Here is an example.
Assume two tasks that communicate through a message queue. The sender task, 1, has a
period time 500, and the execution time 1200. The receiver task, 2, has the period time 300
and the execution time 100. The sender task sends three messages to the receiver task in each
instance. The receiver task reads two messages in each instance. How must the message
queue be dimensioned?
To be able to answer this question, we need first to look how the sender and the receiver task
interleave during their execution. Since the receiver task has higher priority it will preempt the
sender task whenever both are ready at the same time.
void task_Sender(){...while(1){
.../* send message */if(send( MSGQ, msg) )
/* message sent */else
/* something is wrong *//* e.g., queue full */
...}
}
void task_Receiver(){...while(1){
.../* receive message */r ecei ve( MSGQ, &msg) )...
}}
7/28/2019 RTbook Chapter 2
28/38
The execution trace is shown in Figure 19 . We need to analyze the trace for worst-case
scenario, in which both tasks are released simultaneously at time 0. How long should we
analyze the trace? The answer is until the next point in time when the tasks are released at the
same time, which is easily obtained by calculating the least common multiple (lcm) of the task
periods, i.e., it is lcm(300,500) = 1500. After time 1500, the execution pattern of the tasks will
be exactly the same as the one between 0 and 1500, hence we just need to consider the trace
up to the lcm of the task periods. This interval is also known as hyperperiod.
Figure19:Examplecommunicationviamessagequeues.
We can use the following table to illustrate what happens during the execution:
TimeMessage queue
before executionTask execution
Message queue
after execution
02 starts (high priority).
No messages to read yet
100 1 starts and sends 3 messages m1, m2, m3
300 m1, m2, m3Second instance of2 runs.
It reads two messagesm3
500 m3Second instance of1 executes.
It sends additional three messagesm3, m4, m5, m6
600 m3, m4, m5, m6Third instance of2 preempts.
It reads two messagesm5, m6
900 m5, m6Fourth instance of2 runs.
It reads two messages
1000Third instance of1 runs.
It sends three new messages.m7, m8, m9
1200 m7, m8, m9Fifth instance of2 runs.
It reads two messagesm9
1500 m9 Star of new hyperperiod.2 runs and reads one message
both ready
again
0
300
2
1
100 500
600
700
900
15001000
1200
both
ready
7/28/2019 RTbook Chapter 2
29/38
We see that the maximum number of messages contained in the queue at any given time is
four messages; hence it is enough to dimension the queue to be able to contain four messages.
Tasks synchronize in order to ensure that their exchanges occur at the right times and under
the right conditions. In other words, in order to carry out the required activities, a task may
need to have the ability to say "stop" or "go" or "wait a moment" to itself, or to another task.
Synchronization between two tasks can be implemented by the following service calls:
sendSignal(event) sends the fact that an event has occurred. Its action is to place
event information in a channel or pool. This in turn may enable a waiting task to
continue.
waitSignal(event) - causes the task to suspend activity as soon as the wait
operation is executed, and it will remain suspended until notification of an event is
received.
Signals can be sent directly to a specific task, or they can be sent as a broadcast to all tasks in
the system. If sent directly, then we need to include the receiver task in the sendSignal(..)
call. Another way of implementing synchronization is to use semaphores, which we already
discussed.
Memory management
Many general-purpose operating systems offer memory allocation services from what is
called a heap. The famous malloc and free services, known to C-language programmers, use
heap; tasks can temporarily borrow some memory from the operating systems heap bycalling malloc, and free it when done by calling free.
Heaps suffer from external memory fragmentation that may cause the heap services to
degrade. This fragmentation is caused by the fact that when a buffer is returned to the heap, it
may in the future be broken into smaller buffers when malloc requests for smaller buffer
sizes occur. This will result in small fragments of memory appearing between memory buffers
that are being used by tasks. These fragments are so small that they are useless to tasks, but
they cannot be merged into bigger, useful buffer sizes. This will eventually result in situations
where tasks will ask for memory buffers of a certain size, and they will be refused by the
operating system, even though the operating system has enough available memory in its heap.
This fragmentation problem can be solved by so-called garbage collection (defragmentation)
software. Unfortunately, garbage collection causes random, non-deterministic delays in the
heap service, making it unsuitable for real-time system (where we want to be able to predict
all delays).
So, what to do in real-time systems? Real-time operating systems offer non-fragmenting
memory allocation techniques instead of heaps. They do this by limiting the variety of
memory chunk sizes they make available to application tasks. For example, thepools memory
allocation mechanism allows application tasks to allocate chunks of memory of perhaps 4 or 8different buffer sizes per pool, see Figure 20.
7/28/2019 RTbook Chapter 2
30/38
Figure20:Memoryallocationthroughpools.
Pools avoid external memory fragmentation, by not permitting a buffer that is returned to the
pool to be broken into smaller buffers in the future. Instead, when a buffer is returned the
pool, it is put onto a free buffer list of buffers of its own size that are available for future re-
use at their original buffer size. Memory is allocated and de-allocated from a pool with
deterministic, often constant, timing.
Device drivers
When constructing larger systems, where the probability of replacing a hardware componentin the future is high, it is a good idea to encapsulate all hardware-depended software into
device drivers. Device drivers provide an interface between software and hardware, they
manage hardware devices and they have more privileges than regular tasks.
The interface to application software should not contain any specific details about the
underlying hardware device, because it should be possible to replace the device without
changing the application software. Figure 21 gives an example of a device driver for a circuit
that send and receives serial data (a UART).
Figure21:Exampledevicedriveinterface.
Pool 1
Same
block
size
Pool 3Pool 2
TaskDevice
driver
INIT
OPEN
READ
WRITE
CLOSE
7/28/2019 RTbook Chapter 2
31/38
When the system is started, INIT is called, typically with some input parameters that define
the speed of sending bits, the number of start and stop bits, 7- or 8-bits characters, and even or
odd parity. The device is exclusively reserved by calling OPEN, so that no other task can use
it at the same time. The characters are read and written by using calls READ and WRITE.
When we are done with the sending, CLOSE is called, which releases the device and makes it
available for other tasks.
A device driver can be implemented in several ways. If it is supposed to be able to do
buffering, a device driver is usually implemented in one or several tasks. If no buffering is
needed, it can be implemented only by using semaphores.
Figure22:Exampleimplementationofadevicedriver.
Figure 22 depicts a possible implementation of the device drive in the example above. As we
can see in the figure, the interrupt routine communicates with the driver via buffers. We will
show now what happens upon read and write operations.
Read When a character is received by the hardware circuit, an interrupt is generated (a1 inthe figure). Then, the interrupt routine reads the character (a2), and puts it into the input buffer
(a3). If there is any new incoming character in the input buffer, the driver will read it (b1) and
deliver it to the application task (b3). If there is no new character (b2) the driver will wait
until it arrives.
Write The driver receives a write request from the task (d1), and puts it into the output
buffer (d2). Driver activates the hardware circuit by sending a write request interrupt (d3).
The circuit then starts the interrupt routine (c1), which reads the character from the output
buffer (c2) and puts it into the hardware register (c3).
The interface to the device driver can be implemented something like this:
In-buffer
Out-buffer
Task
INIT
OPEN
READ
WRITE
CLOSE
Driver
Interrupt
routineHW-
circuit
Device driver
a1,c1
a2
a3 b1
b2
b3
c2
c3
d1
d2
d3
7/28/2019 RTbook Chapter 2
32/38
void user_task{
...
DD_i ni t (); / * i ni t i at e t he devi ce dr i ver ( DD) */...
while(1){
if( DD_open() ){ / * t r y al l ocat e t he dr i ver */
DD_wr i t e("Print this..."); / * send a st r i ng t o t he dr i ver */
DD_write("...and this...");
...
DD_cl ose(); /* r el ease dr i ver when done */
}
else
/ * Dr i ver i s cur r ent l y used by some ot her t ask */
...
}
}
int DD_open(){
if(lockSemaphore(S))
return OK;
return FAILED;
}
int DD_cl ose(){
if(unlockSemaphore(S))
return OK;
return FAILED;
}
void DD_wr i t e(char *str){
/ * A t ask sends i t s st r i ngs t o t he dr i ver vi a a message queue */
msg.text = str;
send(MSGQ,msg);
}
void t ask_DD(){
while(1){
/ * The dr i ver j ust r eads al l r ecei ved st r i ngs and pr i nt s t hem */
receive(MSGQ,msg)
printf("%s",msg.text);
}
}
7/28/2019 RTbook Chapter 2
33/38
2.8 Time-triggered RTOSsSo far we have mostly talked about event-triggered systems. As I mentioned before, this type
of RTOS is most common in the industry since most of the commercial RTOSs are event-
triggered. However, there is another type of RTOS: time-triggered real-time operatingsystems, where all activities are carried out at certain points in time which are known a priori.
One reason for introducing support for time triggered execution is design of safety critical
systems, for which it must be possible to prove, or at least show, that the system behavior is
correct. Verification of correctness is facilitated with the time triggered approach due to the
reproducible behavior (the execution order is static). Many control systems require timely
execution which can be guaranteed pre-run-time.
Task execution
Tasks in a time-triggered RTOS are activated according to a time table (schedule), at
predefined times e.g., at time t=5 run task A, at time t=12 run task B etc. A schedule is a table
that is created before the start of the system and during run-time it repeats itself after some
time (cycle time), see Figure 23.
Figure23:
Time
triggered
execution.
There are two ways of implementing time-triggered systems. The simplest approach is to
activate only one task at each clock tick, as illustrated in Figure 24-a. The problem is, if a task
has a very short execution time (shorter than the length of the clock tick), it will still allocate
an entire clock tick, since only one task is released per tick. This will result in poor utilization
(usage) of the system, since the rest of the tick will be unused. We could increase the clock
resolution, but that will also result in increased overhead to handle ticks (more clock
interrupts).
A better approach is to allow activation of several tasks per clock tick, see Figure 24-b. Tasksare defined as successors to each other, and we release a sequence of tasks (chain) instead of
just one task in a tick. So, when the first task in the chain has completed its execution, the
next task in the chain released at once, without waiting for the next clock tick. Task chains
make it possible for several tasks to execute within one system clock tick. Another advantage
of this approach is easy implementation of preemption. Whenever there are several tasks
chains ready to execute, the one with the latest start time gets the highest priority, while the
chain with the oldest start time gets the lowest priority.
1 2 3 4time
cycle
1 2 3 4
(Schedule is repeted)
7/28/2019 RTbook Chapter 2
34/38
Figure24:Exampletaskschedulingintimetriggeredsystems.
Task structure
From the user point of view, a task in a time-triggered system is just a function. All task
parameters are defined in the schedule (i.e., period, execution time), and the application
programmer needs only to write the task code. The user does not need to worry about mutual
exclusion and concurrency control since all conflicts are resolved in the schedule.
Usually, tasks share the same memory stack, which results in a very memory efficient system.
In order to make this work, there must not be any blocking primitives (like semaphore locks).
Furthermore, a task that preempts another task must terminate and clean its data from the
stack before the preempted task can be resumed again.
Communication and synchronization
With time-triggered scheduling there is no need to worry about concurrency control. Tasks
run sequentially one after the other and therefore mutual exclusion is guaranteed. All conflicts
between tasks are resolved in the schedule, before system starts to run. We simply separate
access to shared resources by time or we put tasks that share resources in the same chain. For
example, if two or more tasks are accessing the same resource, we simply construct the
schedule so that the execution of conflicting tasks is separated in time. This way, those tasks
cannot access a shared resource at the same time. We can make a parallel from day-to-day
life: time tables for trains Assuming no delays or break downs on trains can occur, we could
eliminate the whole signaling system for trains; since due to the construction of the time table,no two trains can reside at the same railway section.
Time-triggered scheduling is also easy to implement. For many simple systems this kind of
scheduling approach is perfect. On the other hand, while being good for cyclic tasks, sporadic
(non-periodic) activities really mess things up especially short-deadline ones. For example,
an event may occur at most once every ten seconds, but need to be handled within 2
milliseconds. This has to be handled in the system by polling, and to meet a 2 ms deadline in
the example system, a poll in each 1 ms slot is necessary. This wastes a lot of CPU time.
Another drawback of time-triggered approach in poor flexibility to include new activities
(tasks) in the system. Once a schedule is made, it is usually fixed and if we want to add
something we need to reschedule the entire system. We will talk more about scheduling ofboth time-triggered and event-triggered systems in the scheduling chapter.
0 1 2 3
unused
time0 1 2 50
a) One task per clock tick b) Several tasks per clock tick
Taskc chains
7/28/2019 RTbook Chapter 2
35/38
2.9 Example commercial RTOSsHere we provide a brief summary of the features of some of the popular real-time operating
systems. These are presented in 3 groups, viz., event-triggered commercial RTOSs, time-
triggered RTOSs and research RTOSs.
Event-triggered RTOSs
VxWorks This is one of the widely-used RTOSs in the market, which is developed by Wind
River systems. It supports many popular hardware platforms and had been used in several
diverse applications over past two decades. It is built around a large number of APIs and
customizability is one of its strong features. It supports multi tasking with 256 priority levels
and has deterministic context switching. Preemptive scheduling and priority inheritance are
supported. Support for multi-core processors, symmetric and asymmetric multi-processing
(SMP & AMP), IPv6 network stack, and special development platforms tuned for safety
critical domains, are the key features of latest versions.
Windows CE Windows CE is a small footprint kernel and supported on Intel x86 and
compatibles, MIPS, ARM, and Hitachi SuperH processors. It supports 256 priority levels and
priority inheritance. All threads are enabled to run in kernel mode and slices CPU time
between threads. Execution time for non-preemptable code is reduced by breaking the non-
preemptable parts of the kernel into small sections. Kernel objects like processes, threads,
semaphores etc. are dynamically allocated in virtual memory.
QNX This POSIX-compliant RTOS first appeared in 1982. Centered around a minimal
micro kernel and a host of user servers which can be shutdown as needed. Supports mostmodern CPUs such as MIPS, PowerPC, SH4, StrongArm, xScale, and x86. It is scalable from
constrained embedded to multiprocessor platforms. The architecture provides multitasking,
priority-driven pre-emptive scheduling, synchronization, and TCP/IP protocol. Synchronous
message passing, nested interrupts and fixed upper bound on interrupt latencies are some of
the other features. Adaptive partitioning technology helps system designers guarantee
responses to events; for example, guarantee a minimum CPU budget to the user interface of a
device. QNX Neutrino is the latest version and the source code is available from the 2007
onwards.
pSoS This OS is built around the concept of object orientation. Typical objects includetasks, semaphores and memory regions. It supports EDF as well as preemptive priority based
scheduling. Priority ceiling and priority inherence protocols are supported to avoid priority
inversion problem. Application-level control over interrupt handling, supervisory mode
execution of user tasks and dynamic loading of device drivers are some of the features of
pSoS.
OS-9 Originally developed for the Motorola processors during early 80s. Clear separation
of kernel mode and user mode and ability to run on 8, 16 and 32 bit processors.
RT Linux RT Linux (or RTCore) is a microkernel that runs the entire Linux operatingsystem as a fully pre-emptable process. This originated from the research at New Mexico
7/28/2019 RTbook Chapter 2
36/38
Institute of Mining and Technology and currently available in two versions- a free version and
a paid version from Wind River systems. This supports hard real-time operations through
interrupt control between the hardware and the operating system. Interrupts needed for
deterministic processing are processed by the real-time core, while other interrupts are
forwarded to Linux, which runs at a lower priority than real-time threads. First-In-First-Out
pipes (FIFOs) or shared memory can be used to share data between the operating system and
RTCore.
There are several other commercial RTOSs of this category such as RTEMS, Palm O/S, XP
Embedded, DSP/BIOS, RTX, Uc/OS, OSEK etc.
Time-triggered RTOSs
TTPOS Based on the time-triggered protocol, TTPOS combines a small footprint and fast
context switch. This supports priority based, cooperative preemptive scheduling,
synchronization to a global time and error detection features to support fault-tolerance.Deadline monitoring for tasks and interrupt service handlers for aperiodic requests are
provided. Multiple time bases such as global fault-tolerant TTP time and local time are
supported.
Rubus The Rubus RTOS has evolved from Basement, a distributed real-time architecture
developed in the automotive industry and research at Mlardalen University. The Rubus
methods and tools have been used by the Swedish automotive industry for more than a
decade.
The key constituents of the Basement concept are:
Resource sharing (multiplexing) of processing and communication resources,
A guaranteed real-time service for safety critical applications,
A best-effort service for non-safety critical applications,
A communication infrastructure providing efficient communication betweendistributed devices, and
A program development methodology and tools allowing resource independent andapplication oriented development of application software.
To guarantee the real-time behavior of the safety critical application static scheduling incombination with time-triggered execution is utilized. Dynamic scheduling is utilized for
safety critical application as well as for non-safety critical application.
Three categories of run-time services are provided by Rubus OS (each by a kernel with a
name matching the color of service):
Green Run-Time Services, external event triggered execution (interrupts).
Red Run-Time Services, time triggered execution, mainly to be used for applicationsthat have hard real-time requirements.
7/28/2019 RTbook Chapter 2
37/38
Blue Run-Time Services, internal event triggered execution; to be used forapplications that have hard real-time requirements as well as have soft real-time
requirements.
Research kernels
Spring The Spring kernel was developed at University of Massachusetts, Amherst with the
aim of providing scheduling support for distributed systems. This can dynamically schedule
tasks based upon execution time and resource constraints. The safety critical tasks are
scheduled using a static table. The kernel helps retain enough application semantics to
improve fault-tolerance and performance on overloads. It supports both application and
system level predictability. Spring supports abstraction for process groups, which provides a
high level of granularity and a real-time group communication mechanism. It supports both
synchronous and asynchronous multicasting groups and achieves predictable low-level
distributed communication via globally replicated memory. It provides abstractions for
reservation, planning and end- to- end timing support. Admission control, planning-based
scheduling and reflection are notable features of spring kernel.
MARTE This is a research kernel developed by University of Cantabria. It is written in Ada,
follows the minimal Real-Time POSIX.13 subset and supports Ada 2005 real-time features.
Concurrency at thread level (whole program is a single process), single memory space
(threads, driver and OS) and static linking (output is a single bootable image) are main
features. Tools for supporting waiting synchronization and mutual exclusion, measuring time,
efficient triggering of events, offline (e.g. MAST) and online (FRESCOR) scheduling are
provided.
2.10 Exercises1. Answer the following questions about Real-Time Operating systems:
a) What is a real-time operating system (RTOS)? Explain the difference between anRTOS and a general-purpose operating system?
b) Each RTOS has something that is called interrupt latency. What is that?c) Explain at least three different states for a real-time task. Also, explain which
transitions between given states can take place.d) Explain briefly the mechanisms provided by RTOS to support shared resources.e) What does re-entrant code means?f) Can Windows NT be used as an operating system for real-time applications? If no,
motivate why not. If yes, motivate why yes and give an example real-time application
that can run on Windows NT?
2. Assume two periodic tasks 1 and 2 that communicate to each other by sending messages.
Task 1 has an execution time 200 ms and a period 500 ms. 1 sends 3 messages to a
message queue during each period (i.e., it sends 3 messages in each instance). Task2 has an
execution time 100 ms and a period 300 ms. 2 manages to read 2 messages under its period
(i.e., reads 2 messages in each instance). Assume 2 has a higher priority than 1 and 1 is
7/28/2019 RTbook Chapter 2
38/38
allowed to send its messages at any point in time during its execution. Also, when a task reads
a message from the message queue, the message is removed from the queue.
Entire situation is depicted below:
Since we do not want to allocate more memory than necessary for the message queue, we
would like to minimize its size.
What is the minimum possible size of the message queue (counted in number of messages)
such that we are able to guarantee there will always be enough space in the queue for 1 to
insert its messages? Motivate your answer.
Hint: Think of the system behavior in the worst case, which is: both tasks are released
simultaneously and2 preempts 1. It helps a lot if you draw the execution trace for the tasks.
3.Assume following three periodic tasks:
a) Assign task priorities so that all tasks will be able to execute (i.e., no task must waitforever because of some other task). Motivate!
b) Give an example when a) is not fulfilled
c) If you remove sleep(24) from 2, is it possible to set priorities so that a) is fulfilled?
period =500 ms
execution time =200 ms
Sends 3 messages during its
period (i.e., in each instance)
1(low priority)
period =300 ms
execution time =100 ms
Reads 2 messages
during its period
2 (high priority)
Message queue
Task_ 1(void){
while(1){
/ *do somethi ng*/
...
sleep(42);
}
}
Task_ 2(void){
while(1){
/ *do somethi ng */
...
sleep(24);
}
}
Task_ 3(void){
while(1){
/ *do somethi ng*/
/ * no sl eep */
}