Real-Time Operating Systems: Part 1: Mars Pathfinder 1997
Real-Time Operating Systems: Part 1: Mars Pathfinder 1997
Real-Time Operating Systems: Part 1: Mars Pathfinder 1997
Mars
Pathfinder
1997
Aims Objectives
Learn how to write application Write, debug & test time-critical
code for time-critical embedded application code for ARM7
systems under an RTOS microprocessor under FreeRTOS
Understand concurrent Implement high-level concurrent
programming problems and system design using multiple
solutions tasks with semaphores, message
Choose appropriate RTOS for queues and event flags for inter-
given application task communication.
Understand different scheduling Determine feasible schedules
methods and how they enable using extended RMA
hard deadlines to be met. Analyse systems for livelock &
Understand RTOS deadlock issues.
implementation Contrast different solutions to
priority inversion.
Compare and contrast different
RTOS implementations (e.g.
FreeRTOS & MicroC/OS-II)
Textbooks:
Qing Li – Real-Time Concepts for Embedded systems 2003
Clearly written, excellent review of all the theory
No exercises
£25
Jean Labrosse - MicroC/OS II – The Real-Time Kernel (2nd edition)
Less good theory, but good introduction to RTOS
Excellent line-by-line description of MicroC/OS II
C Pocket Reference (Peter Prinz) £5.50. Good concise reference to C.
Not required.
Web resources:
www.freertos.org
Good introduction to practical RTOS principles and design – the FreeRTOS
source code is NOT well documented, but the API guide is good.
www.its.strath.ac.uk/courses/c/
One of many web references for the C language – better than most.
Course web pages: https://intranet.ee.ic.ac.uk/t.clarke/rtos/
The LPC2138 boards you will use for practical work have
keys, and LCD, and both digital-to-analog output and
analog-to-digital input.
One real-time task is to ensure that key-strokes are
processed and the LCD updated within 50ms. Anything
slower than 50ms is a noticeable delay and not acceptable.
Any time between 0 and 50ms is OK.
A Task is implemented by a function with an infinite loop.
In these lectures we will use C language with pseudocode
description of operations to illustrate code
Variables and functions will use Hungarian notation, where the type
is indicated by prefix characters in name.
This is difficult at first, but easy to get used to and makes understanding
code easier.
pv=>pointer to void
v=>void (no In C pointer to nothing means
return value) pointer which can point to anything
The way in which these two tasks behave can be analysed by looking at
an execution trace of the system
The idle task is added by the RTOS to ensure that something is executing even
when the application tasks are both suspended
The control task must be higher priority since has shorter deadline
500us vs 50ms
a) At the start neither of our two tasks are able to run - vControlTask is waiting for the
correct time to start a new control cycle and vKeyHandlerTask is waiting for a key to be
pressed. Processing time is given to the idle task.
b) At time t1, a key press occurs. vKeyHandlerTask is now able to execute—it has a higher
priority than the idle task so is given processing time.
c) At time t2 vKeyHandlerTask has completed processing the key and updating the LCD. It
cannot continue until another key has been pressed so suspends itself and the idle task is
again resumed.
d) At time t3 a timer event indicates that it is time to perform the next control cycle.
vControlTask can now execute and as the highest priority task is scheduled processing
time immediately.
e) Between time t3 and t4, while vControlTask is still executing, a key press occurs.
vKeyHandlerTask is now able to execute, but as it has a lower priority than
vControlTask it is not scheduled any processing time.
f) At t4 vControlTask completes processing the control cycle and cannot restart until the
next timer event—it suspends itself. vKeyHandlerTask is now the task with the highest
priority that is able to run so is scheduled processing time in order to process the previous
key press.
g) At t5 the key press has been processed, and vKeyHandlerTask suspends itself to wait
for the next key event. Again neither of our tasks are able to execute and the idle task is
scheduled processing time.
h) Between t5 and t6 a timer event is processed, but no further key presses occur.
i) The next key press occurs at time t6, but before vKeyHandlerTask has completed
processing the key a timer event occurs. Now both tasks are able to execute. As
vControlTask has the higher priority vKeyHandlerTask is suspended before it has
completed processing the key, and vControlTask is scheduled processing time.
j) At t8 vControlTask completes processing the control cycle and suspends itself to wait for
the next. vKeyHandlerTask is again the highest priority task that is able to run so is
scheduled processing time so the key press processing can be completed.
Now the earth was formless and empty. Darkness was on the surface of the deep.
Genesis 1:2
How to use FreeRTOS
Startup & task creation
Startup task
Stacks
Timing functions
Delaying the current task
Controlling the scheduler
Changing task priorities
Suspending and restarting the multi-tasking
x = xTaskCreate(
vLcdDriveTask, /*task function*/
"LCD", /* name for debugging */
Task handle optionally
mainLCD_STACK_SIZE,
returned in a variable. NULL, /*pointer to parameters, not used in this task so NULL*/
Note & makes pointer to mainLCD_TASK_PRIORITY,
allow call-by-reference &task1 /* replace by NULL if task handle not required */
);
Return value is either
pdPASS or an error code if (x!=pdPASS) hwLcdFatalError("Task creation failed");
(see projdefs.h)
#include "freertos.h" /*FreeRTOS type definitions etc */
Task Function definition
void vLcdDriveTask(void * pvParameters); Function prototype needed if function
definition is after its use
int main(void)
{
[create the task]
}
Task sleeping
vTaskDelay() Call to Call to
vTaskDelayUntil() vTaskSuspend() vTaskResume()
Kernel control
Scheduled
vTaskAllSuspend() READY RUNNING
vTaskAllResume() Preempted
portENTER_CRITICAL()
portEXIT_CRITICAL() Event or
Tick Call to
Task suspension blocking API
xTaskSuspend(xTaskHandle h) BLOCKED function:
xTaskResume(xTaskHandle h) waiting event vTaskDelay()
or timeout xSemaphoreTake
etc
tjwc - 10-Feb-09 Real-Time Operating Systems 1.33
Creation
Task()
{
for (;;) {
[ do something ]
vTaskDelay(2); /* wait 2 clock ticks */
}
}
tjwc - 10-Feb-09 Real-Time Operating Systems 1.35
Task delay in detail
Repeated calls to vTaskDelay(n) should produce delays of
n*TickPeriod if running time of task is small
Generally this can't be guaranteed due to preemption by other tasks
If exact timing is required, solutions are:
Increase task priority
Use the more complex vTaskDelayUntil()
System Ticks
Sleeping
D W D W D W D - TaskDelay(2);
Running W – Wakeup from tick
S
Ready S - Scheduled
Collecting data is only the first step toward wisdom, but sharing data is the
first step toward community.
Henry Louis Gates Jr.
Problem
Implement function RecordError() which can be called from multiple
tasks and counts the total number of times it is called.
Single-threaded solution
static int prvErrorCount = 0; /* initialise to 0 */
vRecordError()
{ /* this function assumes overflow will never happen */
int x;
x = prvErrorCount;
prvErrorCount = x + 1;
return x;
}
ErrorCount variable is shared data
Task 1 Task 2
The solution to this problem is to recognise that the read and write
operations on prvErrorCount must be atomic (executed without
interruption) for correct operation. This can be enforced in an
RTOS by making the code a critical section
Kernel will not allow task switch during critical section
static int prvErrorCount = 0; /* initialise to 0 */
Safe
called void vRecordError(void)
from { /* this function assumes overflow will never happen */
multiple int x;
tasks portENTER_CRITICAL;
x = prvErrorCount; Critical section creates
prvErrorCount := x + 1; atomic read-and-
portEXIT_CRITICAL; increment operation
}
/* the insertion is very fast so switch off interrupts for critical section */
enterCRITICAL_SECTION();
q->next = Freelist;
FreeList = q;
exitCRITICAL_SECTION();
3.1 In slide 1.55 explain why both the two reads AND the two writes
must be atomic by giving in each case an execution trace that
leads to an error otherwise.
3.2 In a priority-scheduled RTOS the highest-priority READY task will
always run. Suppose in the problem from slide 1.55 you may
assume that MonitorTask() is higher priority than ControlTask().
How does this change the necessary critical section code in either
task? Explain your reasoning.
3.3 Shared-data problems are important and a common source of
bugs in RTOS applications. Problem Sheet 1 contains further
examples.
Blocked
waiting Holds S1
S1 token
S1 S2 S3
SemaWait(Semaphore s) SemaSignal(Semaphore s)
{ {
if (s->state == 1) {Critical if ( [ s->waiters is empty ] ) {
s->state = 0; sections s->state = 1; /* give token back */
} else { } else {
[add current task to s->waiters] [wake up highest priority task in s->waiters]
[suspend current task] /* task we have woken up is implicitly given token */
} }
} /* if suspended another task will run */ /* the task woken up may preempt current task and */
} /* run immediately if higher priority than signal task */
WaitTask1
flush 0
SignalTask WaitTask2
B
WaitTask3
tjwc - 10-Feb-09 Real-Time Operating Systems 1.75
Mutual Exclusive Access Paradigm
AccessTask1
1 Shared
AccessTask2
Resource
B
AccessTask3
Shared
AccessTask1
Resource 1
2
AccessTask2
C
Shared
AccessTask3 Resource 2
Timeout Return from wait operation with error indication if blocked for
more than a given time – application task can then recover
from the error. (Zero timeout conventionally used to disable
this feature).
Message delivered
msgsmsgs-1
Startup() Task2()
{ {
[ Create queue q1, size = 1 pointer, for (;;) {
length = 1 ] [ Receive pointer to
[ Create binary semaphore s1, init 0 ] data from q1 ]
[ Create tasks Task1 & Task2 ] [ process data ]
[ start scheduler ] [ signal to sema s1 ];
} }
pcx:
pcBuffer:
Queue
length N
Task1 Task2
Barrier synchronisation
Definition
Solutions
Synchronisation Objects
Event Registers & Event Flags
0 Helper flush 0
Task2 Task2
Task
C B
Task3 Task3
tjwc - 10-Feb-09 Real-Time Operating Systems 1.103
Rather than have a separate helper task, the highest priority of the
synchronising tasks (TaskH below) can serve as helper while it is waiting
at the barrier. If SemA is a counting semaphore it does not matter if
other tasks arrive first – the semaphore signals will be remembered.
The two semaphores must be created somewhere before the barrier code
executes.
#define NSYNCH 3 /* number of tasks to
synchronise */ TaskX()
TaskH()
/*all other tasks to synchronise*/
/* highest priority task to synchronise */
{ {
……
/* barrier point A */ /* barrier point A */
for (count=0; count <NSYNCH-1; count++) { [ Signal to SemA ]
[ wait on SemA ] [ Wait on semB ]
} /* barrier point B */
for count=0; count < NSYNCH-1; count++) {
[signal to SemB ]
}
}
/* barrier point B */
……
tjwc - 10-Feb-09 Real-Time Operating Systems 1.104
Alternative solutions
Task1 0
Sem1
B
Task2 0
Sem2
B
Select
Flag n Set
Task( int n)
{ Flag
/* barrier point A */
EventFlagChange(FlagsB, (1<<n), 0xFF); Wait on all
EventFlagWaitAll(FlagsB,0xFF, 0xFF); flags 7..0 set
/* barrier point B */
/* still not easy to ensure that all tasks are waiting
before flags are reset */
}
Task( int n)
{
char flagValue=0x00; /* either 0x00 or 0xFF */
for (;;) {
/* barrier point A */
flagValue = flagValue ^ 0xFF; /* invert value of flag
EventFlagChange(FlagsB, (1<<n), flagValue); /*mark this task at A*/
EventFlagWaitAll(FlagsB,0xFF, flagValue);/*wait till all tasks at A*/
/* barrier point B */
}
}
/*Assume 8 tasks created with n = 7..0*/
All RTOS must schedule when the current running task calls a
blocking API function.
Otherwise the system would halt!
Other than this, we will examine three commonly used choices for
when to schedule:
Non-preemptive (coroutine) scheduling
All scheduling is explicitly allowed by the running task
At other times preemption is not allowed
Preemptive scheduling
The RTOS scheduler is called whenever something happens which may
change the scheduling decision:
Task priority changing
Task changing state
Preemptive scheduling with time-slicing
The scheduler is called from system clock tick even when no task has
changed state
The advantages on the previous slide mean that co-routine based systems
are very compact and efficient.
There are however big disadvantages which mean that most RTOS (and
nearly all large RTOS) use preemptive scheduling.
Disadvantages
All application code must be written correctly – TaskYield() must be called
within any loop that may last a long time.
A single "rogue" task will freeze the entire system
It is possible (but complex) to require compilers automatically to insert TaskYield()
within every loop.
Task-level response time is not easily guaranteed, since it depends on
maximum length of time between TaskYield() calls.
Calls to TaskYield() in inner loops can slow down execution
Note that co-routines and tasks can co-exist within one system – so
getting the advantages of both at the cost of some complexity
FreeRTOS allows mixed co-routines and tasks
Co-routines run within a single task
Deadline =
Fixed CPU
Next event
execution time: Ci
READY Deadline
Task 1
Task 2
Task Task
Task 2 runs because
ready running
of earlier deadline
tjwc - 10-Feb-09 Real-Time Operating Systems 1.124
Round-robin scheduling
Feature can be added to priority scheduling.
Allow tasks to have equal priority.
Run set of equal priority tasks in equal time-slices and strict rotation
When a task blocks before the end of its time-slice start the next one early
As pointed out earlier this strategy is not usually good for RTOS where
early completion is more important than fairness.
Round-robin scheduling simple. It has the merit that READY tasks are
guaranteed to be given a time-slice within a given time.
blocked
Why is it not good to
switch round-robin to
Task1 a new task every
Task2 time-slice?
Task3
Suppose all N tasks in an RTOS follow the job model (slide 1.123)
Task i executes with CPU time Ci
Task i waits on event with period Ti
Task i has no other blocking (ignore synchronisation with other tasks)
Schedule task i with fixed priority Pi so that faster deadlines have
higher priority
Ti < Tj Pi > Pj
Then the system is guaranteed to meet all deadlines providing the
total CPU Utilisation U is less than the RMA limit for N tasks U(N)
U = Ui = (Ci/Ti) < U(N) = N(21/N-1)
Note that variable times are allowed as long as they obey the given
inequalities
Task T C U Priority
Non-preemptability
Resource once allocated can't be released until task has finished.
Don't confuse this with task preemption
Exclusion
Resource can't be held simultaneously by two tasks
Hold-and-Wait
Holder blocks awaiting the next resource
Circular waiting
tasks acquire resources in different orders
There are cases where some known error condition (other than
deadlock) can be detected using a timeout.
In this case the timeout is part of normal operation
More often long delays in obtaining a resource are not expected
A timeout indicates an error condition
Defensive programming
Use long timeouts – should never happen
Stop system with error on timeout.
Don't rely on this to detect deadlock conditions
Deadlock may be possible but never happen due to scheduling
Such a system is unsafe and may deadlock in the future as the result
of any small change
Make sure deadlock is prevented by design, as on previous slide.
Symptoms
One (or more) tasks wait indefinitely on a shared resource held
by other, normally running, tasks.
Cause
Two or more other tasks are using the resource in turn, without
break, denying the starved task usage.
Total resource utilisation (analogous to CPU utilisation) is
100%
If higher priority tasks have 100% CPU utilisation so preventing
execution of lower priority task this is special case of starvation.
Starvation depends in general on details of scheduling,
it is trickier to diagnose than deadlock
In starvation, a task is prevented from execution by
other tasks, which themselves are executing normally
For example, Dining Philosophers are all doing "pickup left fork;
busy/wait loop until right fork is free" and pick up left forks at
same time.
This is really a hidden form of the classic deadlock - the busy/wait
polling loops make it seem that something is happening, when really
the tasks are all waiting on resources.
In this case, as in deadlock, the pseudo-livelock can't be broken by
any scheduling order.
More interestingly, livelock may be dependent on scheduling, so
that even after it occurs it could be broken by a different execution
order.
See next slide
Priority inversion
Running
DiskDriver()
Ready
Task2() Blocked
Waiting S
Kbdmanager() Holds S
The solutions to this problem all use dynamic priorities. The idea is that
KbdManager() should have its priority temporarily increased.
Priority Inheritance Protocol (PIP)
Any task T which claims the semaphore S has priority dynamically increased to
that of the highest priority task waiting on S
We say it inherits priority of this task.
Priority is increased to ensure this whenever a higher priority task waits on S
When T releases S it will drop back to its old priority
Priority inheritance is transitive.
In PIP, a task can be blocked by a lower priority task in two ways
Direct blocking – when LPT has resourced needed by task
Push-through blocking - when task is prevented from executing due to LPT
having inherited priority higher than task we call this
PIP limits max blocking to at most one critical section for each semaphore,
and also at most one critical section for each lower priority task
Schedulability (RMA etc) is determined by total blocking