ES Unit 3
ES Unit 3
UNIT-3
Introduction to Real-Time Operating Systems
EMBEDDED SYSTEMSJP
2
❖ The basic building block of software written under an RTOS is the task.
❖ Tasks are very simple to write: under most RTOSs a task is simply a subroutine.
❖ At some point in your program, you make one or more calls to a function in the RTOS
that starts tasks, telling it which subroutine is the starting point for each task and some
other parameters.
❖ Most RTOSs allow you to have as many tasks as you could reasonably want.
❖ Each task in an RTOS is always in one of three states:
❖ Most RTOSs seem to proffer a double handful of other task states. Included among
the offerings are suspended, pended, waiting, dormant, and delayed.
The Scheduler:
❖ A part of the RTOS called the scheduler keeps track of the state of each task and
decides which one task should go into the running state.
❖ The schedulers in most RTOSs are entirely simpleminded about which task should get
the processor: they look at priorities you assign to the tasks, and among the tasks that
are not in the blocked state, the one with the highest priority runs, and the rest of them
wait in the ready state.
❖ The lower-priority tasks just have to wait; the scheduler assumes that you knew what
you were doing when you set the task priorities.
EMBEDDED SYSTEMSJP
3
❖ A task will only block because it decides for itself that it has run out of things to do.
Other tasks in the system or the scheduler cannot decide for a task that it needs to wait
for something.
❖ While a task is blocked, it never gets the microprocessor. Therefore, an interrupt
routine or some other t ask in the system must be able to signal that whatever the task
was waiting for has happened. Otherwise, the task will be blocked forever.
❖ The shuffling of tasks between the ready and running states is entirely the work of the
scheduler. Tasks can block themselves, and tasks and interrupt routines can move
other tasks from the blocked state to the ready state, but the scheduler has control over
the running state.
EMBEDDED SYSTEMSJP
4
he
❖ How does the scheduler know when a task has become blocked or unblocked? T
RTOS provides a collection of functions that tasks can call to tell the scheduler what
events they want to wait for and to signal that events have happened.
❖ What happens if all the tasks are blocked? I f all the tasks are blocked, then the
scheduler will spin in some tight loop somewhere inside of the RTOS, waiting for
something to happen. If nothing ever happens, then that's your fault. You must make
sure that something happens sooner or later by having an interrupt routine that calls
some RTOS function that unblocks a task. Otherwise, your software will not be doing
very much.
❖ What if two tasks with the same priority are ready? The answer is depending upon
which RTOS you use. At least one system solves this problem by making it illegal to
have two tasks with the same priority. Some other RTOSs will time-slice between two
such tasks. Some will run one of them until it blocks and then run the other.
❖ If one task is running and another, higher-priority task unblocks, does the task that is
running get stopped and moved to the ready state right away? A preemptive RTOS
will stop a lower-priority task as soon as the higher-priority task unblocks. A
nonpreemptive RTOS will only take the microprocessor away from the lower-priority
task when that task blocks.
A Simple Example:
❖ An RTOS can make a difficult system easy to build. This pseudo-code is from the
underground tank monitoring system
❖ vLevelsTask task uses up a lot of computing time figuring out how much gasoline is
in the tanks
❖ Low-priority vLevelsTask task
❖ High-priority vButtonTask task
❖ One convenient feature of the RTOS is that the two tasks can be written
independently of one another, and the system will still respond well.
EMBEDDED SYSTEMSJP
5
EMBEDDED SYSTEMSJP
6
/* "Button Task" */
while (TRUE)
/* "Levels Task" */
while (TRUE) {
EMBEDDED SYSTEMSJP
7
EMBEDDED SYSTEMSJP
8
InitRTOS ();
StartRTOS ();
}
Tasks and Data
❖ Each task has its own private context, which includes the register values, a program
counter, and a stack.
❖ However, all other data—global, static, initialized, uninitialized, and everything
else—is shared among all of the tasks in the system.
❖ As shown in Figure, Task 1, Task 2, and Task 3 can access any of the data in the
system.
❖ The RTOS typically has its own private data structures, which are not available to any
of the tasks.
EMBEDDED SYSTEMSJP
9
❖ Since you can share data variables among tasks, it is easy to move data from one task
to another: the two tasks need only have access to the same variables.
❖ You can easily accomplish this by having the two tasks in the same module in which
the variables are declared, or you can make the variables public in one of the tasks
and declare them extern in the other.
❖ vRespondToButton task prints out some data that is maintained by the
vCalculateTankLevels task.
❖ Both tasks can access the tankData array of structures just as they could if this system
were written without an RTOS.
EMBEDDED SYSTEMSJP
10
EMBEDDED SYSTEMSJP
11
struct {
long ITankLevel;
long lTimellpdated;
} tankdata[MAX_TANKS];
/* "Button Task" */
int i;
while (TRUE){
tankdata[i].ITimeUpdated,
tankdata[i].ITankLevel);
}}
/* "Levels Task" */
int i =0;
while (TRUE) {
EMBEDDED SYSTEMSJP
12
i = !! something new
}}
❖ Shared-Data Problems:
❖ Bugs cropped up because an interrupt routine shared data with task code in the
system.
❖ Two tasks sharing data, and unfortunately all of the same kinds of bugs can come
right back to haunt us.
❖ The RTOS might stop vCalculateTankLevels at any time and run vRespondToButton.
❖ However, the RTOS might stop vCalculateTankLevels right in the middle of setting
data in the tankdata array (which is not an atomic operation), and vRespondToButton
might then read that half-changed data.
❖ In it, both Task1 and Task2 call vCountErrors. This is a perfectly valid thing to do in
an RTOS: any or all of the tasks can share as many subroutines as is convenient.
❖ The difficulty with the program in below task code is that because both Task1 and
Task2 call vCountErrors, and since vCountErrors uses the variable cErrors, the
variable cErrors is now shared by the two tasks (and again used in a nonatomic way).
❖ If Task1 calls vCountErrors, and if the RTOS then stops Task1 and runs Task2, which
then calls vCountErrors, the variable cErrors may get corrupted in just the same way
as it would if Task2 were an interrupt routine that had interrupted Task1.
EMBEDDED SYSTEMSJP
13
vCountErrors (9);
vCountErrors (11); .
cErrors += cNewErrors;
❖ The assembly code for vCountErrors is at the top of that figure; below it is a potential
sequence of events that causes a bug.
❖ Suppose that the value 5 is stored in cErrors. Suppose that Task1 calls
vCountErrors(9), and suppose that vCountErrors does the MOVE and ADD
instructions, leaving the result in register R1(14).
❖ Suppose now that the RTOS stops Task1 and runs Task2 and that Task2 calls
vCountErrors (11). The code in vCountErrors fetches the old value of cErrors(5), adds
11 to it, and stores the result(16).
❖ Eventually, the RTOS switches back to Task1, which then executes the next
instruction in vCountErrors, saving whatever is in register R1(14) to cErrors and
overwriting the value written by Task2(16).
EMBEDDED SYSTEMSJP
14
❖ Instead of cErrors ending up as 25 (the original 5, plus 11 plus 9), as it should, it ends
up as 14.
EMBEDDED SYSTEMSJP
15
❖ Reentrancy:
❖ Reentrant functions are functions that can be called by more than one task and that
will always work correctly, even if the RTOS switches from one task to another in the
middle of executing the function. The function vCountErrors does not qualify.
❖ You apply three rules to decide if a function is reentrant:
- A reentrant function may not u se variables in a nonatomic way unless they are
stored on the stack of the task that called the function or are otherwise the
private variables of that task.
- A reentrant function may not c all any other functions that are not themselves
reentrant.
❖ To better understand reentrancy, and in particular rule 1 above, you must first
understand where the C compiler will store variables.
❖ Review your knowledge of C by examining below task code and answering these
questions:
- Which of the variables in below task code are stored on the stack and which in
a fixed location in memory?
- What about the string literal "Where does this string go?"
EMBEDDED SYSTEMSJP
16
int public_int;
int initialized = 4;
void *vPointer;
int local;
❖ static_int—is in a fixed location in memory and is therefore shared by any task that
happens to call function.
❖ public_int—Ditto. The only difference between static_int and public_int is that
functions in other C files can access public_int, but they cannot access static_int.
❖ initialized—The same. The initial value makes no difference to where the variable is
stored.
❖ string—The same.
❖ "Where does this string go?"—Also the same.
❖ vPointer—The pointer itself is in a fixed location in memory and is therefore a shared
variable. If function uses or changes the data values pointed to b y vPointer, then those
data values are also s hared among any tasks that happen to call function.
❖ parm—is on the stack. If more than one task calls function, parm will be in a different
location for each, because each task has its own stack.
❖ parm_ptr—is on the stack. Therefore, function can do anything to the value of
EMBEDDED SYSTEMSJP
17
parm_ptr without causing trouble. However, if function uses or changes the values of
whatever is pointed to b y parm_ptr, then we have to ask where that d ata is stored
before we know whether we have a problem.
❖ static_local—is in a fixed location in memory. The only difference between this and
static_int is that static_int can be used by other functions in the same C file, whereas
static_local can only be used by function.
❖ local—is on the stack.
EMBEDDED SYSTEMSJP
18
if (!fError)
j = 0;
fError = TRUE;
else
fError = FALSE;
}}
EMBEDDED SYSTEMSJP
19
MOV DPTR,#cErrors+01H
MOVX A,@DPTR
INC A
MOVX ©DPTR,A
JNZ noCarry
MOVX A,@DPTR
MOVX ©DPTR,A
noCarry:
RET
❖ which doesn't look very atomic and indeed isn't anywhere close to atomic, since it
takes nine instructions to do the real work, and an interrupt might occur anywhere
among them.
❖ INC (cErrors)
❖ RET
❖ If you really need the performance of the one-instruction function and you're using an
80x86 and you put in lots of comments, perhaps you can get away with writing
vCountErrors this way.
❖ However, there's no way to know that it will work with the next version of the
compiler or with some other microprocessor to which you later have to port it.
EMBEDDED SYSTEMSJP
20
RTOS Semaphores
❖ No RTOS uses the terms raise and lower; they use get and give, take and release, pend
and post, p and v, wait and signal, and any number of other combinations. This book
use take (for lower) and release (for raise).
❖ A typical RTOS binary semaphore: tasks can call two RTOS functions, Take
Semaphore and Release Semaphore. If one task has called Take-Semaphore to take
the semaphore and has not called Release Semaphore to release it, then any other task
that calls Take Semaphore will block until the first task calls ReleaseSemaphore. Only
EMBEDDED SYSTEMSJP
21
EMBEDDED SYSTEMSJP
22
struct
long ITankLevel;
long ITimeUpdated;
} tankdata[MAX_TANKS];
/* "Button Task" */
int i;
while (TRUE)
TakeSemaphore ();
tankdata[i].ITimeUpdated,
tankdata[i].ITankLevel);
ReleaseSemaphore ();
/* "Levels Task" */
EMBEDDED SYSTEMSJP
23
int i = 0;
while (TRUE)
TakeSemaphore ();
!! Set tankdata[i].ITankLevel
ReleaseSemaphore ();
❖ If the user presses a button while the levels task is still modifying the data and still has
the semaphore, then the following sequence of events occurs:
EMBEDDED SYSTEMSJP
24
1. The RTOS will switch to the "button task," just as before, moving the levels task to
the ready state.
2. When the button task tries to get the semaphore by calling TakeSemaphore, it will
block because the levels task already has the semaphore.
3. The RTOS will then look around for another task to run and will notice that the levels
task is still ready. With the button task blocked, the levels task will get to run until it
releases the semaphore.
4. When the levels task releases the semaphore by calling ReleaseSemaphore, the
button task will no longer be blocked, and the RTOS will switch back to it.
EMBEDDED SYSTEMSJP
25
EMBEDDED SYSTEMSJP
26
#define TASK_PRIORITY_READ 11
#define TASK_PRIORITY_CONTROL 12
OS_EVENT *p_semTemp;
Figure 6.14 Semaphores Protect Data in the Nuclear Reactor
EMBEDDED SYSTEMSJP
27
OSInit ();
OSStart ();
while (TRUE)
!! read in iTemperatures[1];
OSSemPost (p_semTemp);
EMBEDDED SYSTEMSJP
28
while (TRUE)
if (iTemperatures[0] != iTernperatures[1])
OSSemPost (p_semTemp);
Initializing Semaphores:
❖ The bug arises with the call to OSSemCreate, which must happen before
vReadTemperatureTask calls OSSemPend to use the semaphore.
❖ How do you know that this really happens? You don't.
❖ Since vReadTemperatureTask calls OSTimeDly at the beginning before calling
OSSemPend, vControlTask should have enough time to call OSSemCreate.
❖ How do you know that there isn't some higher-priority task that takes up all of the
delay time in vReadTemperatureTask?
❖ You can make it work for sure by giving vControlTask a higher priority than
vReadTemperatureTask. Yes, that's true, too . . . until some compelling (and probably
more valid) reason comes up to make vReadTemperatureTask a higher priority than
vControlTask and someone makes the change without realizing that you put this time
bomb into the code.
EMBEDDED SYSTEMSJP
29
❖ Put the semaphore initialization call to OSSemCreate in some start-up code that's
guaranteed to run first. The main function shown in above task code, somewhere
before the call to OSStart, would be a good place for the call to OSSemlnit.
❖ However, the code that modifies the static variable cErrors is surrounded by calls to
semaphore routines. In the language of data sharing, we have protected cErrors with a
semaphore.
❖ Whichever task calls vCountErrors second will be blocked when it tries to take the
semaphore.
❖ In the language of reentrancy, we have made the use of cErrors atomic and therefore
have made the function vCountErrors reentrant.
EMBEDDED SYSTEMSJP
30
vCountErrors (9);
vCountErrors (11);
NU_Obtain_Semaphore (&semErrors,NU_SUSPEND);
cErrors += cNewErrors;
NU_Release_Semaphore (&semErrors);
}
Multiple Semaphores:
❖ The semaphore functions all take a parameter that identifies the semaphore that is
being initialized, lowered, or raised.
EMBEDDED SYSTEMSJP
31
❖ The semaphores are all independent of one another: if one task takes semaphore A,
another task can take semaphore B without blocking. Similarly, if one task is waiting
for semaphore C, that task will still be blocked even if some other task releases
semaphore D.
❖ Another common use of semaphores is as a simple way to communicate from one task
to another or from an interrupt routine to a task.
❖ The task waits for a semaphore after it has formatted each report. The interrupt
routine signals the task when the report has been fed to the printer by releasing the
semaphore; when the task gets the semaphore and unblocks, it knows that it can
format the next report.
❖ The interrupt routine will release the semaphore and thereby unblock the task when
the report is printed.
EMBEDDED SYSTEMSJP
32
EMBEDDED SYSTEMSJP
33
void vPrinterTask(void)
Int wMsg;
semPrinter = OSSemlnit(O);
while (TRUE)
i LinesPrinted = 0;
EMBEDDED SYSTEMSJP
34
}}
if (iLinesPrinted == iLinesTotal)
OSSemPost (semPrinter);
else
vHardwarePrinterOutputLine (a__chPrint[iLinesPrinted++]);
}
Semaphore Problems:
❖ When first reading about semaphores, it is very tempting to conclude that they
represent the solutions to all of our shared-data problems. This is not true. In fact,
your systems will probably work better, the fewer times you have to use semaphores.
❖ The problem is that semaphores work only if you use them perfectly, and there are no
guarantees that you (or your co-workers) will do that.
emaphores only work if every task that accesses
❖ Forgetting to take the semaphore. S
the shared data, for read or for write, uses the semaphore.
❖ Forgetting to release the semaphore. I f any task fails to release the semaphore, then
every other task that ever uses the semaphore will sooner or later block waiting to
take that semaphore.
❖ Taking the wrong semaphore. I f you are using multiple semaphores, then taking the
wrong one is as bad as forgetting to take one.
EMBEDDED SYSTEMSJP
35
Priority inversion:
❖ A particularly perverse instance of this problem can arise if the RTOS switches from a
low-priority task (Task C) to a medium-priority task (Task B) after Task C has taken a
semaphore.
❖ A high-priority task (Task A) that wants the semaphore then has to wait until Task B
gives up the microprocessor: Task C can't release the semaphore until it gets the
microprocessor back.
❖ No matter how carefully you code Task C, Task B can prevent Task C from releasing
the semaphore and can thereby hold up Task A indefinitely. This problem is called
priority inversion; some RTOSs resolve this problem with priority inheritance — they
temporarily boost the priority of Task C to that of Task A whenever Task C holds the
semaphore and Task A is waiting for it.
EMBEDDED SYSTEMSJP
36
❖ Causing a deadly embrace. The below task code illustrates the problem called deadly
embrace. The functions ajsmrsv and ajsmrls in that figure are from an RTOS called
AMX.
❖ The function ajsmrsv "reserves" a semaphore, and the function ajsmrls "releases" the
semaphore.
❖ The two additional parameters to ajsmrsv are time-out and priority information and
are not relevant here.
❖ In the below code, both Taskl and Task2 operate on variables a and b after getting
permission to use them by getting semaphores hSemaphoreA and hSemaphoreB.
EMBEDDED SYSTEMSJP
37
int a;
int b;
AMXID hSemaphoreA;
AMXID hSemaphoreB;
a = b;
ajsmrls (hSemaphoreB);
ajsmrls (hSemaphoreA);
b = a;
ajsmrls (hSemaphoreA);
ajsmrls (hSemaphoreB);
❖ if vTaskl calls ajsmrsv to get hSemaphoreA, but before it can call ajsmrsv to get
hSemaphoreB, the RTOS stops it and runs vTask2.
❖ The task vTask2 now calls ajsmrsv and gets hSemaphoreB.
EMBEDDED SYSTEMSJP
38
❖ When vTask2 then calls ajsmrsv to get hSemaphoreA, it blocks, because another task
(vTaskl) already has that semaphore.
❖ The RTOS will now switch back to vTaskl, which now calls ajsmrsv to get
hSemaphoreB.
❖ Since vTask2 has hSemaphoreB, however, vTaskl now also blocks.
❖ deadly-embrace problems would be easy to find and fix if they always appeared on
one page of code.
❖ However, deadly embrace is just as deadly if vTaskl takes the first semaphore and
then calls a subroutine that later takes a second one while vTask2 takes the second
semaphore and then calls a subroutine that takes the first.
Types of Semaphores:
Binary Semaphores:
❖ A binary semaphore is a synchronization object that can have only two states:
1. Not taken.
2. Taken.
Binary semaphores have no ownership attribute and can be released by any thread or
interrupt handler regardless of who performed the last take operation. Because of these binary
EMBEDDED SYSTEMSJP
39
semaphores are often used to synchronize threads with external events implemented as ISRs,
for example waiting for a packet from a network or waiting that a button is pressed.
Counting Semaphores:
Zero, no waiting threads, a wait operation would put in queue the invoking thread.
Positive, no waiting threads, a wait operation would not put in queue the invoking thread.
EMBEDDED SYSTEMSJP
40
Counting semaphores have no ownership attribute and can be signaled by any thread
or interrupt handler regardless of who performed the last wait operation.
Mutex semaphore:
1. Not owned.
2. Owned.
EMBEDDED SYSTEMSJP
41
A mutex can be unlocked only by the thread that owns it, this precludes the use of
mutexes from interrupt handles but enables the implementation of the Priority Inheritance
protocol, most RTOSs implement this protocol in order to address the Priority Inversion
problem. It must be said that few RTOSs implement this protocol fully (any number of
threads and mutexes involved) and even less do that efficiently.
Mutexes have one single use, Mutual Exclusion, and are optimized for that.
Semaphores can also handle mutual exclusion scenarios but are best used as a communication
mechanism between threads or between ISRs and threads.
Binary Counter
Feature Mutexes
Semaphores Semaphores
Internal states 2 N 2
Use from ISRs yes yes No
Ownership no no yes
Priority Inheritance no no yes
either taken or always not
Initialization counter >= 0
not taken owned
Queue organization FIFO or priority FIFO or priority Priority
EMBEDDED SYSTEMSJP