Rtos Unit 5 Es
Rtos Unit 5 Es
Rtos Unit 5 Es
Introduction
Real-Time Operating System Architecture
• RTOS
• Others use the term real-time kernel, RTK.
• Usually the kernel means some subset of the most basic
services offered by a larger RTOS.
• In this case, things like network support, debugging tools,
memory management, etc are considered to be part of the
RTOS but not part of the kernel.
• In this text, we ignore such distinctions and use the term
RTOS indiscriminately.
Introduction to
Real-Time Operating Systems
• Most real-time systems are very different from Windows or Unix.
• In a desktop system, the O/S takes control of the machine as soon as it is
turned on and then it lets you start your applications. Your applications
are compiled and linked separately from the operating system.
• In an embedded system, your application and the RTOS are linked
together. At boot-up time, your application normally gets control first,
and then it starts the RTOS.
• Many RTOS products do not protect themselves as carefully as do
desktop operating systems.
• Most RTOS products save memory by including only those services that
you need.
• One can write there own RTOS, but you also can and probably should
buy one!
Examples of RTOS
• VxWorks, VRTX, pSOS, Nucleus, C Executive,
LynxOS, QNX, MultiTask!, AMX…..
• Standard for OS interface proposed by IEEE
(IEEE std no. 1003.4) is called – POSIX
(Portable OS Interface for uniX)
Tasks and Task States
• The basic building block of software written under an RTOS is the
task
• Under most RTOS schemes, the 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 what subroutine is
the starting point for the task, the priority of the task, and some
general parameters, where the stack for the task should be located,
etc.
Tasks and Task States
• Running – the microprocessor is executing the instructions
that make up this task. Only one task can be in this state.
• Ready – some other task is in the running state but this
task has things to do if the microprocessor becomes
available. Any number of tasks can be in this state.
• Blocked – this task hasn’t anything to do right now, even if
the microprocessor becomes available. Tasks get into this
state because they are waiting for some external event.
Any number of tasks can be in this state.
Tasks and Task States
• Most RTOS’s seem to offer several other task states like
suspended, pended, waiting, dormant, delayed, etc.
• These generally amount to fine distinctions among various
categories of the blocked and ready states discussed earlier.
• For example, MicroC/OS-II has five states, namely DORMANT,
READY, RUNNING, WAITING (for an event), or ISR
(interrupted).
Tasks and Task States –
MICROC/OS II
WAITING
OSMBoxPend()
OSMBoxPost()
OSQPend()
OSQPost()
OSSemPend()
OSQPostFront()
OSTaskSuspend()
OSSemPost()
OSTimeDly()
OSTaskResume()
OSTaskDel() OSTimeDlyHMSM()
OSTimeDlyResume()
OSTimeTick()
OSTaskCreate() OSStart()
Interrupt
OSTaskCreateExt() OSIntExit()
OS_TASK_SW()
RUNNING ISR
DORMANT READY
OSTaskDel() OSIntExit()
Task preempted
OSTaskDel()
The Scheduler
• The scheduler is a part of the RTOS that keeps track of the
state of each task and decides which one task should go into
the running state. It is sometimes also known as the
dispatcher.
• Unlike the schedulers in Unix or Windows, the scheduler is
entirely simpleminded about which task gets the processor:
it looks at priorities which you assign to the tasks and among
tasks that are not in the blocked state, the one with the
highest priority runs.
• If a higher priority task hogs the processor for a long time
while lower-priority tasks are waiting in the ready state, it is
unacceptable
The Scheduler (cont)
• A task will only block because it decides for itself that it has run out
of things to do. Other tasks in the system cannot decide for a task
that needs to wait for something. A consequence that a task must
be running just before it is blocked so that it can execute the
instructions that determine that it has nothing else to do.
• While a task is blocked, it never gets the microprocessor.
Therefore, and interrupt routine or some other task 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 the running states is
entirely the work of the scheduler.
Task States – figure 6.1
Running
Common Questions…
• How does the scheduler know when a task has
become blocked or unblocked?
• What happens if all of the tasks are blocked?
• What if two tasks with the same priority are
ready?
A simple example
• The next slide, code shows a classic example where an RTOS can make a
difficult system easy to build
• In this case, the vLevelsTask uses up a lot of computer time figuring out
how much gasoline is in each tank, and will use up as much computing
time as it can get.
• However, when the user pushes a button, it is required that the system
responds quickly. When the button is pressed, the vButtonTask
unblocks.
• The RTOS will stop the low-priority vLevelsTask in its tracks, move it to
the READY state, and run the high-priority vButtonTask to let it respond
quickly to the user.
Uses for Tasks
/* “Button Task” */
void vButtonTask(void) /* High priority */
{
while(TRUE)
{
!! Block until user presses a button
!! Quick: respond to the user
}
}
/* “Levels Task” */
void vLevelsTask(void) /* Low priority */
{
while(TRUE)
{
!! Read levels of floats in tank
!! Calculate average float level
!! Do some interminable calculation
!! Do more interminable calculation
!! Figure out which tank to do next
}
}
Button Response
vButtonTask does
everything it needs to do to
User presses button; RTOS respond to the button
switches to vButtonTask;
vLevelsTask is ready.
vButtonTask finishes its
work and blocks again;
RTOS switches
vLevelsTask is busy microprocessor back to
calculating while vLevelsTask
vButtonTask is blocked
vButtonTask
vLevelsTask
Time
RTOS initialization
• It is common to have some code which runs
first that tells the RTOS which of the functions
are tasks and that (for example) the
calculation task has a lower priority than the
button task.
• A HUGE feature of using a RTOS is that one
can write a task without being terribly
concerned about someone else writing
another task and generally the system will
respond well.
Tasks and Data
• Each task has its own private context, which includes…
– register values
– program counter
– stack
• However, all other data---global, static initialized, un-
initialized, and everything else is shared. Thus, tasks in
an RTOS are really more like threads than processes if
you are familiar with Unix or Windows.
Task Data in an RTOS
RTOS
data RTOS Task 1 Task 1
structures Regs Stack
All
Task 1
Task 2 other
data
Task 3
Task 2 Task 2
Stack Regs Task 3 Task 3
Regs Stack
Shared-Data Problems
Question: Which of these variables are stored on the stack and which
in a fixed location in memory?
C Variable Storage review - ans
static int static_int; /* fixed location – shared by any task */
int public_int; /* same as above except fcns in other C
files can access */
int initialized = 4; /* same – initial value makes no
difference */
char *string = “Where does this string go?”; /*same as above*/
void *vPointer; /* pointer itself in fixed location and
shared */
• The RTOS offers a new service, called a semaphore to deal with the
shared data problem.
• railroad barons discovered that it was a bad thing for trains to run into
each other
• The word semaphore is one of the most slippery in the embedded
systems world.
• Many RTOS’s have multiple types of semaphores. These might be
binary semaphores, counting semaphores, mutual exclusion
semaphores, synchronization semaphores, etc…
Semaphores and shared data
• Also, RTOS vendors are not consistent about the terms. They may
use raise and lower, get and give, take and release, pend and post, p
and v, wait and signal, or any combination…
• The author uses take for lower and release for raise. The MicroC
author uses pend and post.
• A typical RTOS semaphore works like this: tasks can call two RTOS
functions say TakeSemaphore and ReleaseSemaphore. If one task
has called TakeSemaphore and has not called ReleaseSemaphore any
other task that calls TakeSemaphore will block until the first task calls
ReleaseSemaphore.
• Only one task can have the semaphore at a time.
Semaphores
• Semaphores can be used to protect data by
guarding access to the data. This effectively
protects the shared data problem in which a
task can have half-modified data which of
course is corrupted data.
struct
{
long lTankLevel;
long lTimeUpdated;
} tankdata[MAX_TANKS];
Before the levels task (vCalculateTankLevels) updates the data in the structure, it calls
TakeSemaphore() to take (lower) the semaphore. If the user presses a button while the
levels task is still modifying the data and still has the semaphore, the the following
sequences occurs:
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, even though it is a lower priority than the button task.
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.
Semaphores
• Review the execution flow on page 157 to
make sure you understand the previous 4
steps…
• Note that the MicroC/OS-II calls to take and
release a semaphore are called OSSemPend()
and OSSemPost(), respectively
Nuclear reactor example
Task A
Task B
Task C
Priority Inversion
• No matter how carefully you code Task C, Task
B can prevent Task C from releasing the
semaphore and thereby can hold up Task A
indefinitely.
• Some RTOS’s solve this problem with priority
inheritance…they temporarily boost the
priority of the priority of Task C to that of Task
A whenever Task C holds the semaphore that
Task A is waiting for.
Deadly Embrace – Figure 6.18
int a;
int b;
AMXID hSemaphoreA;
AMXID hSemaphoreB;
void vTask1(void)
{
ajsmrsv(hSemaphoreA, 0, 0); /* “reserve” the semaphore */
ajsmrsv(hSemaphoreB, 0, 0);
a = b;
ajsmrls(hSemaphoreB); /* “release the semaphore” */
ajsmrls(hSemaphoreA);
}
void vTask2(void)
{
ajsmrsv(hSemaphoreB, 0, 0); /* “reserve” the semaphore */
ajsmrsv(hSemaphoreA, 0, 0);
a = b;
ajsmrls(hSemaphoreA); /* “release the semaphore” */
ajsmrls(hSemaphoreB);
}
Deadly Embrace (cont)
• Of course, these problems would be easy if
they were as illustrated in this example
• However, they virtually never appear on one
page of code and are typically distributed over
many functions/files/pages/etc.
Semaphore Variants
• Counting semaphores – original type!
• Resource semaphores – only can be released
by the task that took them…not useful for
signaling between tasks
• Wind River Systems RTOS (VxWorks) offers
various kinds of semaphores, optionally ones
that deal with priority inversion (or not)
• Synchronization semaphores…
Ways to protect shared data
• Disable interrupts
– Drastic – affects the response time of all isrs
– Also disables task switches
– It is the only method that works if you have shared data between tasks and
isrs…(excepting a queue with head and tail done correctly)
– It is fast
• Taking a semaphore
– The most targeted way to protect data
– Response times of isrs are unchanged
– Generally don’t work for isr routines
• Disabling task switches
– Somewhat in between the other two
– No effect on interrupt routines
– Stops response for all other tasks cold
Chapter 6 summary
• A typical RTOS is smaller and offers fewer services than a
standard operating system and is more closely linked to the
application.
• RTOS’s are widely available for sale, and it generally makes
sense to buy one rather than write another one for yourself.
• The task is the main building block for software written in an
RTOS environment.
• Each task is always in one of three states: running, ready,
blocked. The scheduler runs the highest priority ready task.
• Each task has its own stack; however, other data in the system
is shared by all tasks. Therefore, shared data problems can
occur.
Chapter 6 Summary(cont)
• A function that works properly even if it called by more than one task is
called a reentrant function
• Semaphores can solve the shared-data problem. Since only one task can
take a semaphore at a time, semaphores can prevent shared data from
causing bugs. Semaphores have two associated operations – take and
release
• Your tasks can use semaphores to signal one another
• You can introduce any number of ornery bugs with semaphores. Priority
inversion and deadly embrace are two of the more obscure. Forgetting to
take or release or using the wrong ones are common ways to cause
problems.
• The mutex, the binary semaphore, and the counting semaphores are all
common.
• Three methods to protect data…disabling interrupts, taking semaphores, and
disabling task switches.
More RTOS Services
• Inter-task communication
• Timer services
• Memory management
• Events
• Interactions between isr’s and RTOS
Other inter-task communication
• We have already discussed shared data and
semaphores
• It this chapter (chapter 7) we discuss some
additional methods offered by various RTOS’s
… namely queues, mailboxes, and pipes.
Discussion of Figure 7.1-simple queue
use
/* RTOS queue function prototypes */
void AddToQueue(int iData);
void ReadFromQueue(int *p_iData);
void ErrorsTask(void) /* this task is responsible for reporting errors onto the network */
{
int iErrorType;
while(FOREVER)
{
ReadFromQueue(&iErrorType); /* note…task blocks if queue is empty…a good thing */
++cErrors;
!! Send cErrors and iErrorType out on network
}
}
Real queue complications
• Most RTOS’s require that you initialize your queues before you use them. They
provide a function to call for that purpose.
• It makes sense to initialize these via code which is guaranteed to run before ANY task
tries to use them.
• Since most RTOS’s allow you to have as many queues as you like, you must pass an
additional parameter to every queue function which identifies which queue.
• If your code tries to write to a queue when the queue is full, the RTOS must either
return an error to let you know that the write operation failed or it must block the task
until some other task reads data from the queue to make it non-full and creates some
space. Your code must deal which whichever behavior your RTOS exhibits.
• Many RTOS’s include a function that will read from a queue if there is any data and
return an error code if not. This is a different function from the one that will block if
the queue is empty.
• The amount of data that the RTOS allows you to write to the queue in one call may not
be exactly the amount of data that you wish to write.
Figure 7.2 More realistic use-1
/* RTOS queue function prototypes */
OS_EVENT *OSQCreate(void **ppStart, BYTE bySize);
unsigned char OSQPost(OS_EVENT *P0se, void *pvMsg);
void *OSQPend(OS_EVENT *p0se, WORD wTimeout, BYTE *PByErr);
#define WAIT_FOREVER 0
void Task2(void)
{
…
if(!!problem arises)
vLogError(ERROR_TYPE_Y);
/* Write to the queue. Cast the error type as a void pointer to keep the compiler happy */
byReturn = OSQPost(p0seQueue, (void *) iErrorType);
if(byReturn != OS_NO_ERR)
!! Handle the situation that arises when the queue is full
}
static int cErrors;
void ErrorsTask(void)
{
int iErrorType;
BYTE byErr;
while(FOREVER) /* cast the value received back from the queue to an int */
{ /* Note that there is no possible error from this, so ignore byErr */
iErrorType = (int) OSQPend(p0seQueue, WAIT_FOREVER, &byErr);
++cErrors;
!! Send cErrors and iErrorType out on the network
}
7.3 pointers on queues
• MicroC/OS-II allows you to write a void pointer to the
queue with each call.
• The “obvious” rationale behind this is that a task can pass
any amount of data by putting the data into some buffer
and then passing a pointer to that buffer in the queue.
• This makes the queue handling functions fast, since no
data is copied while still leaving excellent generality.
Mailboxes
• Mailboxes in general are much like queues.
• The typical RTOS has functions to create, to write to, and to
read from a mailbox, and possibly other functions to check
whether the mailbox contains any messages and possibly to
destroy the mailbox if no longer needed.
– Some RTOS versions allow a certain number of messages in a mailbox
which you get to choose upon creation of the mailbox, others only
allow one message
– In some RTOS versions, the number of messages is unlimited, in others
there is a limit to messages in the total number of mailboxes but these
messages are distributed into individual mailboxes as needed.
– In some RTOS versions, you can prioritize mailbox messages, so that
higher-priority messages will be read first before lower-priority
messages, irregardless of the order of appearance.
Pipes
• Pipes are also much like queues. The RTOS can create them,
write to them, read from them and so on. The details vary
from RTOS to RTOS.
– Some RTOSs allow you to write messages of varying lengths onto
pipes, unlike mailboxes and queues, which typically have fixed length
messages.
– Pipes in some RTOSs are entirely byte-oriented. If Task A writes 11
bytes to the pipe and then Task B writes 19 bytes to the pipe, then if
Task C reads 14 bytes from the pipe, it will get the 11 that Task A
wrote first plus the first 3 that Task B wrote. The other 16 bytes that
Task B wrote will remain in the pipe for whomever reads it next.
– Some RTOSs use the standard C library functions fread and fwrite to
read from and write to pipes.
Which should I use?
• The standard answer…since queues,
mailboxes, and pipes vary so much from one
RTOS to another, it is hard to give any
universal guidance about which to use in a
given situation.
• Read the manual of the RTOS to find out what
tradeoffs were made in the design of these
features.
Pitfalls
• Most RTOSs do not restrict which tasks can read from or write
to a given queue, mailbox, or pipe. Therefore, you must
ensure that tasks use the correct one each time.
• The RTOS cannot ensure that data written onto a queue,
mailbox, or pipe will be properly interpreted by the task that
reads it.
– Compilers can do some limited checking for this kind of error but not
completely…see following two examples
• Running out of space in queues, mailboxes, or pipes is usually a
disaster for embedded software. When one task needs to pass
data from one task to another, it is usually not optional! Often,
the only workable solution is to make queues, mailboxes, and
pipes large enough in the first place.
• Passing pointers from one task to another through a queue,
mailbox, or pipe is one of several ways to create shared data
inadvertently.
Most compilers balk at this code:
/* Declare a function that takes a pointer parameter */
void vFunc(char *p_ch);
void main(void)
{
int i;
…
/* call it with an int, and get a compiler error */
vFunc(i);
…
}
But not this code!
static OS_EVENT *p0seQueue;
void TaskA(void)
{
int i;
…
/* put an integer on the queue */
OSQPost(p0seQueue, (void *) i);
…
}
void TaskB(void)
{
char *p_ch;
BYTE byErr;
…
/* Expect to get a character pointer */
p_ch = (char *) OSQPend(p0seQueue, FOREVER, byErr);
…
}
Figure 7.4 – be careful when you pass pointers on queues! (part 1)
void vReadTemperaturesTask(void)
{
int iTemperatures[2];
while(TRUE)
{
!! Wait until it’s time to read the next temperature
iTemperatures[0] = !! read in value from hardware
iTemperatures[1] = !! read in value from hardware
/* Add to the queue a pointer to the temperatures we just read */
OSQPost(p0seQueueTemp, (void *) iTemperatures);
}
}
Figure 7.4 – be careful when you pass pointers on queues! (part 2)
void vMainTask(void)
{
int *pTemperatures;
BYTE byErr;
while(TRUE)
{
pTemperatures = (int *) OSQPend(p0seQueueTemp, WAIT_FOREVER, &byErr);
if(pTemperatures[0] != pTemperatures[1])
!! Set of howling alarm;
}
}
/*
This code contains a serious defect which is that when the main task gets a value for pTemperatures from the
queue, pTemperatures will point to the iTemperatures array in vReadTemperaturesTask. If the RTOS switches
from vMainTask to vReadTEmperaturesTask while vMainTask was comparing the temperatures
iTemperatures[0] to iTemperatures[1], and if vReadTemperaturesTask then changes the values in
iTemperatures, you will have essentially the same shared-data defects discussed in chapters 4 and 6.
Essentially, the code makes iTemperatures into unprotected, shared data. The code in 7.3 did not have this
problem.
*/
Timer functions
• Most embedded systems must somehow keep track
of the passage of time
• To extend battery life, for example, certain
machines must turn themselves off if they are not
being used.
• One simple service that most RTOSs offer is a
function that delays a task for a period of time, i.e.,
it blocks the task until the period of time expires.
Figure 7.5 Delaying a Task
void vMakePhoneCallTask(void)
{
#define MAX_PHONE_NUMBER 11
char a_chPhoneNumber[MAX_PHONE_NUMBER];
/* buffer for null-terminated ASCII number */
char *p_chPhoneNumber;
/* pointer into a_chPhoneNumber */
…
while(TRUE)
{
msgQreceive(queuePhoneCall, a_chPhoneNumber, MAX_PHONE_NUMBER, WAIT_FOREVER);
Time
1 System tick
Timer Interrupts
Questions
• How does the RTOS know how to set up the
timer hardware on my specific hardware?
– Many RTOS vendors provide a BSP or Board
Support Package which drive common hardware
components.
– In our Rabbit microprocessor case, Z-World has
provided a BSP as well as a port of MicroC to the
Rabbit hardware
Questions
• What is a “normal” length for a system tick?
– This varies all over depending on lots of factors.
– Typical values might be 60 hz or 16.667 ms for
example. Our Rabbit microprocessor uses a divide by
64 or ~15 ms rather than a divide by 60.
– Smaller values can decrease system throughput by
quite a lot because the system spends so much more
time in the timer routines…but it allows finer timing
– Larger values decrease overhead but timing becomes
sloppier
Questions
• What if your system needs accurate timing?
– You have two choices…make the system tick short
enough so that the RTOS task timing meets your
definition of accurate.
– The second is to use a separate hardware timer
for those timings that must be accurate.
– Also, you may consider ISR rather than task level
code for accurate timing
Other Timing Services
• Most RTOS’s offer a number of other timing services
based upon the system tick.
– Limit how long a task will wait for a message from a
message queue or a mailbox
– Limit how long a task will wait for a semaphore
• This may be useful, but use extreme caution. For
example, if a high-priority task exceeds a time limit
trying to get a semaphore, then your task still does
not have the semaphore and cannot access the
shared data. You will have to write task code to
somehow recover which is likely to be difficult since
the task needs to use the data but cannot. It might
be more useful to think of another approach.
Other Timing Services
• A very useful service offered my many RTOS’s is to
call a one of your functions after a certain number
of ticks has expired. This is called a callback
function.
void vTaskTestTemperatures(void)
{
int iTemp0, iTemp1;
while(TRUE)
{
GetSemaphore(SEMAPHORE_TEMPERATURE);
iTemp0 = iTemperatures[0];
iTemp1 = iTemperatures[1];
GiveSemaphore(SEMAPHORE_TEMPERATURE);
if(iTemp0 != iTemp1)
!! Set off howling alarm
}
}
No Blocking
• Some RTOS’s have functions that never block.
For example, many have a function that
returns the status of a semaphore without
blocking.
7.14 How Interrupt routines should work
Figure 7.14 How interrupt routines should work
ISR
RTOS
Send message
to mailbox
Task High
Task Low
Time
7.15 What would really happen
Send message
to mailbox
Time
In this case, if the high-priority task is blocked on the mailbox, then as soon as the interrupt routing
writes to the mailbox, the RTOS unblocks the higher-priority task. Then the RTOS (not knowing
about the isr) notices that the task that it it thinks is running is no longer the highest-priority task that
is ready to run. Therefore, instead of returning to the isr(which it thinks is part of the lower-priority
task), the RTOS switches to the higher priority task and doesn’t get back to the isr.
Figure 7.16 How interrupt routines do work
(1st scheme)
In this scheme, the RTOS
ISR
intercepts all interrupts.
(VxWorks works like this)
Task High
Task Low
Time
Figure 7.17 How interrupt routines do work
(2nd scheme)
In this scheme, the RTOS
provides a function to let
ISR
the RTOS know that an
ISR is running. MicroC
works like this)
Task Low
Time
Rule 2 and Nested Interrupts
If your system allows interrupt routines to nest, i.e., a higher-
priority interrupt can interrupt a lower-priority interrupt,
another consideration comes into play…
If the higher-priority interrupt makes makes any calls to RTOS
functions, then the lower-priority interrupt must let the RTOS
know when the lower-priority interrupt occurs. Otherwise,
when the higher-priority isr ends, the RTOS scheduler might
run some other task rather than let the lower-priority isr
complete. In other words, duh, the scheduler should not run
until all isr’s are complete.
Chapter summary
• Tasks must be able to communicate with one another to coordinate activities and to
share data. Most RTOS’s offer services such as message queues, mailboxes, and pipes
for this purpose.
• Passing a pointer to a buffer from one task to another through a queue of a pipe or
mailbox is a common way to pass a block of data.
• Most RTOS’s maintain a heartbeat timer that interrupts periodically and that is used for
all RTOS timing services. The interval between timer interrupts is called the system
tick. The most common services timing services are:
– A task can block itself for a specified number of system ticks
– A task can limit how many system ticks it will wait for a semaphore, a queue, etc.
– Your code can tell the RTOS to call a specified function after a specified number of system
ticks. (callback function)
• Events are one-bit flags with which tasks can signal one another. They can be formed
into groups, and a task can wait for a combination of events within a group.
• Even though many RTOSs offer the standard malloc and free, engineers often avoid
them in favor of memory allocation based on a pool of fixed-size buffers
• Interrupt routines in an RTOS must adhere to two rules:
– They must not call any RTOS functions that block
– They must not call any RTOS function unless the RTOS knows that an interrupt routine is
running.
Basic Design Using a Real-Time Operating System
while(FOREVER)
{
!! Wait for a system signal(event, queue, mailbox, event, etc)