+ All Categories
Home > Documents > RTbook Chapter 2

RTbook Chapter 2

Date post: 03-Apr-2018
Category:
Upload: bilal-shahid
View: 213 times
Download: 0 times
Share this document with a friend

of 38

Transcript
  • 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 */

    }


Recommended