0% found this document useful (0 votes)
124 views107 pages

Rtos Unit 5 Es

Download as ppt, pdf, or txt
Download as ppt, pdf, or txt
Download as ppt, pdf, or txt
You are on page 1/ 107

RTOS

Introduction
Real-Time Operating System Architecture

• In this architecture, the most urgent work is taken care of by


ISR’s as before. They then signal that there is work for task
code to do.
– The necessary signaling between the interrupt routines and the task
code is handled by the real-time operating system. You need not
use shared variables for this purpose.
– No loops decide what needs to be done next. Code inside the real
time operating system decides which of the code functions should
run. The real-time o/s knows which of the tasks is the most
important to run at a given time.
Real Time O/S
• A side-effect of using the RT O/S is that the
system response is relatively stable, even
when code is changed.
• The response times for a task function in this
architecture generally do not depend upon
changing an even lower-priority task. This is
not true for round-robin and function queue
architectures.
Suggestions for architectures…
• Select the simplest architecture that will meet your
requirements
• If your system has response requirements that might require
a real time O/S, you should lean toward using a real-time
operating system.
• It is possible to create hybrids of the various architectures if
it makes sense for your system.
– For example, a low-priority task could poll certain parts of the
hardware that do not need fast response.
– In a round robin with interrupts, the main loop could poll slower
pieces of hw directly rather than reading flags set by isr’s.
Introduction to
Real-Time Operating Systems

• 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

Whatever the task


needs, happens
Blocked Ready

Another ready task is


This is the highest higher priority
priority ready task
Task needs something
to happen before it can
continue

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

• Sharing data between tasks has the sharing data


problems (sharing data between task and isr
code).
• For ex: When data are shared, the higher priority
task may read wrong data value which is an
output of lower priority task.
Tasks sharing code
void Task1(void)
{

vCountErrors(9);

}
void Task2(void)
{

vCountErrors(11);

}
static int cErrors;
void vCountErrors(int cNewErrors)
{
cErrors += cNewErrors;
}
Reentrancy
• vCountErrors() is not reentrant
• Reentrant functions are functions that can be
called by more than one task and will always
work correctly.
• Also … reentrant functions will work correctly
if called by an isr
Reentrancy rules!
• A reentrant function may not use variables in
a non-atomic 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 call any other
functions that are not themselves reentrant.
• A reentrant function may not use the
hardware in a non-atomic way.
C Variable Storage review
static int static_int;
int public_int;
int initialized = 4;
char *string = “Where does this string go?”;
void *vPointer;

void function(int parm, int *parm_ptr)


{
static int static_local;
int local;

}

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

void function(int parm, int *parm_ptr) /* parm is on the stack */


{ /* parm_ptr is also on the stack */
static int static_local; /* fixed location in memory smallest
scope */
int local; /* is on the stack */

}
Reentrancy Ex.
BOOL fError; /* someone else sets this */
void display(int j)
{
if(!fError)
{
printf(“\nValue: %i”, j);
j = 0;
fError = TRUE;
}
else
{
printf(“\nCould not display value”);
fError = FALSE;
}
}
QUESTION: IS this re-entrant?
Reentrancy cont
• The answer is NO for two reasons:
– The boolean variable fError is in a fixed location in
memory and is shared by any task that might call
display(). The use of fError is not atomic, because
the RTOS might switch between the time that is
tested and the time that it is set. (Rule 1 violation)
– This function may violate Rule 2 as well…printf()
must be reentrant…don’t count on printf() being
reentrant unless so stated…
Gray Areas of reentrancy

static int cErrors;


void vCountErrors(void)
{
++cErrors;
}

The question is…is incrementing cErrors atomic(it is a non-


stack variable). The answer is maybe or “it depends”. On an
8-bit micro-processor it will not be while for a 32-bit
microprocessor it might be. Good advice is not depend on this
and use another technique(semaphores for example).
Semaphores and shared data

• 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];

void vRespondToButton(void) /* high priority Button Task */


{
int i;
while(TRUE)
{
!! Block until user pushes a button
i = !! Get ID of button pressed
TakeSemaphore();
printf(“\nTIME: %08li LEVEL: %08li”, tankdata[i].lTimeUpdated, tankdata[i].lTankLevel);
ReleaseSemaphore();
}
}
void vCalculateTankLevels(void) /* low priority Levels Task */
{
int i = 0;
while(TRUE)
{

TakeSemaphore();
!! Set tankdata[i].lTimeUpdated
!! Set tankdata[i].lTankLevel
ReleaseSemaphore();
}
}
Semaphore example 6.12

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

• There is too much code to put on a slide so review figure 6.14


in the text…the call to OSSemCreate() MUST be made before
vReadTemperatureTask() calls OSSemPend to use the
semaphore.
• The problem is that there is no guarantee that this happens…
• Do not fool around…put the calls to the semaphore
initialization in some start-up code that is guaranteed to run
first…
– Initialize
– Start
– Activate
Semaphores
• Semaphores can make a function reentrant.
• However, put the calls to take and release the semaphore
INSIDE the function so that a user cannot forget!
• See figure 6.15
• Consider using multiple semaphores to protect different
critical data.
• How does the RTOS know which semaphore protects
which data??? Duh, it doesn’t, that is the engineer’s job
to be consistent in the definition and use of a semaphore
Semaphores as signaling devices

• A typical use of a semaphore is to use it as a signaling


device from one task to another or from an isr to a task.
(Remember, isr’s cannot take (i.e., do a Pend on a
semaphore).
• Typically for this case, some initialization code will create
the semaphore as already taken. The isr will give the
semaphore, allowing the blocked task to perform its work
synchronized with the isr. See 6.16.
Semaphore problems…
• Tried and True ways to mess up with
semaphores…
– Forgetting to take the semaphore
– Forgetting to release the semaphore
– Taking the wrong semaphore
– Holding a semaphore for too long
– Perverse Priority Inversion shown on next slide…
Priority Inversion – Figure 6.17
Task A – high priority
Task A gets a message in Task B – med priority
its queue and unblocks;
RTOS switches to Task Task C – low priority
A.
Task B gets a message in
its queue and unblocks;
RTOS switches to task B Task A tries to take the Task B goes on running
semaphore that task C has and running and running
already taken never giving task C a
chance to release the
Task C takes a semaphore semaphore. Task A is
that it shares with task A blocked!

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 Task1(void) /* has lots of urgent high-


priority things to do */
{

if(!!problem arises) /* an error, but we can’t take
up too much time */
vLogError(ERROR_TYPE_X);

!! Other things that need to be done soon



}

void Task2(void) /* also has lots of urgent high-


priority things to do */
{

if(!!problem arises) /* also an error, but can’t take too
much time */
vLogError(ERROR_TYPE_Y);

!! Other things that need to be done soon



}
Discussion of Figure 7.1-simple queue
use(cont)
void vLogError(int iErrorType)
{
AddToQueue(iErrorType); /* RTOS guarantees that this function is reentrant */
}

static int cErrors;

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

/* our message queue */


static OS_EVENT *p0seQueue;
/* the data space for our queue The RTOS will manage this.*/
#define SIZEOF_QUEUE 25
void *apvQueue[SIZEOF_QUEUE];
void main(void)
{

/* The queue gets initialized before the tasks are started */
pOseQueue = OSQCreate(apvQueue, SIZEOF_QUEUE);

!! Start Task1
!! Start Task2

}
Figure 7.2 More realistic use-2
void Task1(void)
{

if(!!problem arises)
vLogError(ERROR_TYPE_X);

!! Other things that need to be done soon



}

void Task2(void)
{

if(!!problem arises)
vLogError(ERROR_TYPE_Y);

!! Other things that need to be done soon



}
Figure 7.2 More realistic use-3
void vLogError(int iErrorType)
{
BYTE byReturn; /* return code from writing to a queue */

/* 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)

/* 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
static OS_EVENT *p0seQueueTemp;

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

/* Message queue for phone numbers to dial (sample from vxWorks */


extern MS_Q_ID queuePhoneCall;

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);

/* Dial each of the digits… */


p_chPhoneNumber = a_chPhoneNumber;
while(*p_chPhoneNumber)
{
taskDelay(100); /* 1/10th second silence */
vDialingToneOn(*p_chPhoneNumaber – ‘0’);
taskDelay(100); /* 1/10th second with tone */
vDialingToneOff();

/* go to the next digit in the phone number */


++p_chPhoneNumber;
}

}
}
Questions
• How do I know that the taskDelay function takes a
number of milliseconds as in its parameter?
– You don’t and in fact, it doesn’t…The taskDelay function in
VxWorks,like most equivalent functions in most RTOS’s, takes
the number of ticks as a parameter.
– In the MicroC/OS-II case, there are versions…namely
OSTimeDly() which is in system ticks and OSTimeDlyHMSM()
which has arguments in hours,min,sec,milli-sec.
Questions(cont)
• How accurate are the delays produced by the
taskDelay function? They are accurate to the
nearest system tick. See the next figure 7.6.
Figure 7.6

vTaskDelay(3) Task delay ends at vTaskDelay(3) Task delay ends at


starts task delay timer interrupt starts task delay timer interrupt

2.93 ticks 2.16 ticks

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.

• Review figure 7.7, which illustrates a timer callback


function associated with the radio application…it is
too large to copy in here!
7.3 Events
• Many RTOS’s offer another service called an event.
• An event is essentially a Boolean flag that tasks can set or reset
and that other tasks can wait for.
• For example, an interrupt can cause an isr to execute, which in
turn sets an event which multiple tasks may be waiting on.
• More than one task can block waiting for the same event and
the RTOS will run each of them in priority order when the
event occurs.
• RTOS’s can have groups of events, and tasks can wait for any
subsets of of these groups.
• Different RTOS’s deal differently with the issue of resetting the
event after it has occurred and tasks that have been waiting for
it are unblocked. It is important to reset the event! Some
RTOS’s do this automatically and some require your task code
to do this.
Methods for Intertask Communication
• Semaphores are usually fastest and simplest
• Events are a little more complicated than semaphores and take
up a little more time. The advantage is that a task can wait for
any one of several events at the same time, whereas it it can
only wait for one semaphore
• Queues allow you to send a lot of information from one task to
another. Even though you can only wait on one queue(or
mailbox or pipe) at a time, the fact that you can send data
through it makes if flexible. The drawback is that (1) putting
messages into a queue and taking them out is more
microprocessor-intensive and (2) queues offer more
opportunities to place bugs into your code.
7.4 Memory Management
• Most RTOS’s have some kind of memory management service.
• Some offer the equivalent of malloc and free (for you C++
types out there new and delete)
• Most, however, avoid these because of they are typically slow
and because the execution times are unpredictable.
• They favor functions which allocate and free fixed-size buffer
which can be done in a fast, predictable way.
• In general, you must tell the RTOS where this memory is!
There will generally be some sort of initialization call like the
init_mem_pool() function to notify the RTOS what size of
buffers, how many, etc.
7.5 Interrupt routines
• Interrupt routines in most RTOS environments must follow two
rules that do not apply to task code.
• Rule 1: An interrupt routine must not call any RTOS function
that might block the caller
• Rule 2: An interrupt routine may not call any RTOS function
that might cause the RTOS to switch tasks unless the RTOS
knows that an interrupt routine, and not a task, is executing
– This means that the ISR may NOT write to mailboxes, queues, etc. on
which tasks may be waiting, set events, release semaphores, etc.
unless the RTOS knows that it is an isr that is doing these things. If this
rule is broken, the RTOS might switch control away from the interrupt
(because the RTOS thinks that it is a task) and the interrupt routine
might not now complete for a long time, blocking all lower-priority
interrupts and possible all interrupts.
Rule 1: NO blocking
static int iTemperatures[2];

void interrupt vReadTemperatures(void)


{
GetSemaphore(SEMAPHORE_TEMPERATURE); /* not allowed */
iTemperatures[0] = !! read in value from hardware;
iTemperatures[1] = !! read in value from hardware;
GiveSemaphore(SEMAPHORE_TEMPERATURE);
}

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)

RTOS Call Return


Send message
to mailbox

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)

RTOS Jump or Call


Enter
Send message
interrupt
to mailbox
routine
Task High

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

• Chapters 6 and 7 discussed the various features


and operating system services that most RTOSs
offer, appropriate use, and various pitfalls.
• This chapter assumes that you are using an RTOS.
• Chapter 5 discussed other architectures
• This chapter (chapter 8) will help you use the RTOS
effectively
• Be aware that embedded-system design is an
endeavor that has as many exceptions as it has
rules. This is an art as much as a science!
Overview
• Simply specifying a real-time system is more
difficult than a desktop application.
– “What the system must do” is the same for both
– “How fast must it do it” is in the realm of the real-time
system.
– It is necessary to know how critical timing is. If there
are hard real-time deadlines, the system is called a
hard real-time system. Systems that demand good
response but allow some fudging of the deadlines are
called soft real-time systems.
Overview(cont)
• It is also necessary to know something about
the hardware.
– Speed of I/O is important
– Speed of the micro-processor also is important
– Remember to design for well under 100% of
bandwidth, probably about 60% is a reasonable
maximum.
Principles
• Embedded systems typically have nothing to do until the passage of time or
some external event that requires a response.
– If no print data arrives, a printer has not much to do
– If the user does not pull the trigger of the bar code scanner, the
cordless bar-code scanner has little to do.
• External events generally cause interrupts and the passage of time generally
causes interrupts based upon a timer of some sort. Therefore, interrupts are
sort of the driving force of embedded software.
• A typical embedded system design technique is to have each task normally
blocked, waiting for an interrupt routine or another task to send a message or
cause an event or give a semaphore to tell the blocked task that it has
something to do.
Principles(continued)
• Write short interrupt routines
– Since even the lowest-priority interrupt routine is executed in
preference to even the highest priority task code, writing longer isr’s
translates into slower task –code response.
– Interrupt routines tend to be more bug-prone and considerably harder
to debug than task code
– As an example, the tools provided with VxWorks from Wind River (such
at the debugger and shell) work well with task level code but not really
at all with isr level code. There are only a few options for debugging
the isr, namely tracing to an external (say serial port), writing tracing
information to a RAM buffer to be retrieved by the host later, etc.
Principles(cont)
• Consider a system with the following requirements:
– The system must respond with commands coming from a serial port
– Commands always end with a carriage return
– Commands arrive one at a time; the next command will not arrive
until the system responds to the previous one.
– The serial port hardware can only store one received character at a
time, and characters may arrive quickly.
– The system can respond to commands relatively slowly. (Of course
“quickly” and “relatively slowly” are vague terms). We need actual
specifications for what this is in terms of time.
Principles(cont)
• A possible(but wretched) way to write this system is to do all of
the work in the interrupt routine that receives the characters
– The interrupt routine will be long, complex, difficult to debug, and will
slow response for any work that has to be done in task code.
• The other end of the spectrum is to write a brainless isr which
forwards every character received in an RTOS message to a
message parsing task. This in theory is good, because the isr is
short. However, the down side is that the interrupt will send
lots of short messages to the parsing task, one for each received
character. If characters arrive quickly enough, the isr might not
be able to keep up…
Principles(cont)…a compromise
• A compromise design uses an interrupt routine that
saves all of the received characters into a buffer
until a carriage return character is received, and
only then sends the single message to the parsing
task.
– In this case, the isr is still rather simple
– The system does not have to send so many messages
• Figure 8.2 in the text shows some code to
implement this scheme.
How many tasks?
• Are we better off with fewer or more tasks? Advantages:
– With more tasks, you have better control of the relative response times of the different parts of your system. If you
have 8 tasks, you can divide into 8 task priority levels. If you put all of the work into one task, it is more like the round-
robin architecture in chapter 5.
– With more tasks, your system can be somewhat more modular.
– With more tasks, you can sometimes encapsulate data more effectively. Disadvantages:
– With more tasks, you are more likely to have data shared among two or more tasks. This may translate into more
semaphores, and more semaphore-related bugs
– With more tasks you are likely to have more requirements to pass messages from one task to another through pipes,
queues, mailboxes, etc. This gives more chance for defects.
– Each task requires a stack; therefore, with more tasks, you probably need more memory.
– Each time the RTOS switches tasks, a certain amount of microprocessor time evaporates by saving the context of the
task which is stopping and restoring the context of the task which is about to run.
– More tasks probably mean more calls to the RTOS. The irony is that once you have chosen an RTOS, your system runs
faster by NOT calling the RTOS services.
• The perverse thing about these lists is that the disadvantages of having more tasks is visited upon you
automatically, but you reap the benefits only if you divide your system into tasks carefully.
• The moral is: Other things being equal, use as few tasks as you can get away with; add more tasks to your
design only for clear reasons.
Tasks are needed for priority
• The obvious advantage of the RTOS architecture is that
you can assign higher priorities to those parts of the
system with tighter response time requirements.
• Tasks are also needed for encapsulation
– Talk about example of various errors that can occur in different
parts of the system which must share the display hardware. It
makes sense for the display hardware to be controlled with a
single task.
vtaska.c
Recommended Task Structure
!! Private static data is declared here
void vTaskA(void)
{
!! More private data declared here, either static
!! or on the stack.

!! Initialization code, if needed

while(FOREVER)
{
!! Wait for a system signal(event, queue, mailbox, event, etc)

switch(!! type of signal)


{
case !! signal type 1

break;

case !! signal type 2



break;

}
}
}
Advantages of this structure:
• The task blocks in only one place
• When there is nothing for this task to do, its input
queue is empty and the task will block and use up
no microprocessor time.
• This task does not have any public data that other
tasks can share. Other tasks must interact by
writing private data requests into the queue and
then this task handles them.
Things to avoid…
• Avoid creating and destroying tasks
– These can be hazardous to your system throughput
– Task creation is relatively reliable but it is difficult to destroy a task
cleanly without leaving little pieces around to cause bugs.
– For example, what happens to what items are left in the input queue of
a task which is destroyed?
– Create all tasks at startup.
– Later, if a task has nothing to do it can block for however long it needs
on its input queue.
• Consider turning time slicing off—great for human interaction
but but “fair” is not a consideration in embedded systems…on-
time response is.
• Consider restricting your use of the RTOS
– RTOS shell code…levels of abstraction…
Encapsulating Semaphores & Queues
• Mention examples in 8.4, namely
encapsulating semaphores for the example of
reading lSecondsSinceMidnight()
• Encapsulating queues …flash memory
example
Hard Real-Time Scheduling
• Talk about a system in Embedded Systems
Programming…namely Rate Monotonic
Scheduling for Hard Real Time systems
• Note that the limit is ln 2 or 0.693. Or about
70% utilization.
Saving Memory space
• Embedded systems often have very limited memory
• You may be short of data space, or code space, or both
• Squeeze data into efficient structures, use no larger stacks
than necessary
• Try to eliminate RTOS services that you don’t need or
use…configure the RTOS to be as small as possible.
• Make sure your tools aren’t sabotaging you! If you call
memcpy, your tools might drag in memmove, memset,
memcmp, strcpy, and strncpy, and strset.
• Talk about the different methods of initializing in 8.6…look at
the assembly listing.
• Consider using static variables rather than variables on the
stack…however, remember reentrancy guidelines!
• If you are using an 8 bit microprocessor, try using char
variables instead of ints for certain small number operations.
Saving Power
• Battery life may be a big issue
• Many embedded-system microprocessors have at
least one power-saving mode; many have several.
• Software/firmware can usually put the system in
one of these modes by the execution of a certain
instruction or by writing to a control register within
the microprocessor. These might be called low-
power mode, sleep mode, idle mode, standby
mode, etc.
• The gotcha here is getting out of the mode…and
having your code recognize that it is just waking
up. We usually write some signature into RAM.
Chapter Summary
• Embedded-system software design is as much art as science
• You must know how fast your system must operate and how critical it is to meat each
deadline. If deadlines are absolute, then yours is a hard real-time system. Otherwise,
it is a soft real-time system.
• You must know what hardware you will have and how fast it is.
• General software concerns for structure, modularity, encapsulation, and maintainability
all still apply in the embedded-system world.
• In much of embedded software, real-world events cause interrupts, which then signal
tasks to do work. Systems do nothing without interrupts; tasks spend their time being
blocked unless real-world events give them something to do.
• Short interrupt routings are better, since interrupt routines preempt tasks and are bug-
prone. Move processing into tasks and have interrupt routines signal the tasks for all
but the most urgent processing.
• You are better off using fewer tasks when you can. More tasks tends to mean having
more bugs, spending more microprocessor time in the RTOS, and needing more
memory space.
• Processing that has different priority must go into different tasks
• It is often a good idea to encapsulate hardware with a task.
Chapter Summary(cont)
• The best task structure is one that blocks in only one place, waiting for a message telling it what
to do next. Tasks are often structured as state machines.
• It is usually not a good idea to create and destroy tasks as the system is running. Better to
create all tasks at the beginning
• Make sure that you really do need time-slicing before you enable it.
– VxWorks has it but we (HP) don’t use it – priority based execution only
– MicroC/OS-II does not have time-slicing
• Restricting the list of RTOS functions may allow you to make your system smaller (and possibly
cheaper). Building a shell (abstraction layer) can be used to restrict access to the RTOS and
make it easier to port to the next RTOS.
• You should encapsulate semaphores, queues, and so on, in single modules so that the interface
between modules is a single call.
• In order to guarantee that a hard real-time system meets its deadlines, you must ensure that
each task has a predictable worst-case execution time.
• One way to save data space is to make your tasks stack spaces only as large as they need to be.
• You can save code space by correctly configuring the RTOS correctly, by using a limited number
of library functions, by possibly using assembler, etc.
• Systems that run on batteries save power by turning off part or all of the system.

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy