csl.skku.edu
Download
Report
Transcript csl.skku.edu
System Software
1
Programming Embedded Systems
• An introduction to the software design aspects of embedded
systems development
• Assumptions:
- You are knowledgeable in C++ and can get by in C
- You have limited knowledge of assembly language
• I will not teach you how to write programs
- You’re probably better at it then I am
• I will teach you how embedded software is built, integrated with
the hardware, and tested
Introduction to Embedded Systems
2
OK, so what’s different about embedded?
• C or C++ Compiler compiles into the instruction set of the Target
microprocessor
- Code will not run natively on the host computer
- Can be run in an Instruction Set Simulator (ISS)
• Many host O/S-dependent include files must be created especially for
the target system
- printf(), cout(), malloc(), etc.
• O/S services must be hand crafted for the hardware
- Called the Board Support Package (BSP) or BIOS
- printf(), fopen(), fclose(), malloc(), microkernel functions
- Hardware initialization
•
•
•
•
Code location changes with target
Code image is created to be loaded in ROM
Code may be relocated to RAM
Hardware is memory-mapped
Introduction to Embedded Systems
3
Code can also be downloaded
• Many embedded systems contain only enough code in ROM to load
the actual code image from another source
- Called a Boot Loader
- Similar to the program running on the 5206eLITE evaluation board
• Satellites use this method
- Can reprogram the ROM when the satellite is past Saturn
• Method used in PC’s
- BIOS ROMs contain enough code to load the operating system from
disk
• Convenient way to initialize the hardware so that it is ready to
accept the application software
Introduction to Embedded Systems
4
Embedded software development process
Source
Listing
C or C++
Source File
C or C++
Compiler
Assembly
Source File
Source
Listing
Create User
Library
(optional)
Librarian
Assembler
Relocatable
Object
Module
User
Library
Library
Directory
Listing
Include
Files
Linker
Command
File
Device
Programmer
Target
Development
System
Absolute
Object
Module
Linker
Relocatable
Object
Module
Link Map
Introduction to Embedded Systems
5
Why do we use so many pointers?
• Parameters can be passed to subroutines ( functions ) in two ways
- Pass by value: A copy of the variable is passed to the function
- Actual value of function in memory remains unchanged
- Pass by reference: A pointer to the variable ( memory address) is passed
to the function
- Modifying the variable through the pointer changes the actual value of
the variable in memory
- For most embedded applications, the overhead incurred by passing by
value is too high
• Places a large penalty on using Global Variables
- Can make debugging programs difficult
impossible when different
functions can access the same variable
- Object-oriented methods can even be used in Assembly Language or C
Introduction to Embedded Systems
6
Booting up
• Many embedded systems follow this process:
- Power is applied, HW RESET circuit holds processor in reset until power
has stabilized at proper level
- RESET is released and the processor fetches first instruction
- Processor does a checksum calculation on the contents of the ROM(s)
- Checksum for ROM is stored in last location of ROM
- If Checksum passes, processor does a memory test of RAM
- If memory test passes processor initializes the hardware registers
- Does confidence tests of the hardware ( if any )
- Relocates C global variables from ROM to RAM
- Establishes Stack and Heap pointers
- Enables interrupts
- Jumps to main()
• How do we get from the source file to main()?
Introduction to Embedded Systems
7
More on getting started
• In general, the memory space of your embedded system will be
divided up as follows ( assuming 68K architecture)
- 0x00000000..0x000003FF : Vector tables (system space)
- 0x00000400..0xFFFFFFFF : Code and data space
- This could be different for different processors
- Example: Microcontrollers, Intel Architecture
• System space contains all the RESET, EXCEPTION and TRAP vectors
- What is a vector?
- An address in a memory location where the processor is hard-wired
to go to in response to certain external or internal conditions
- Example: RESET vector located at byte address
0x00000004..0x00000007
- Contents of these 4 bytes is the address of the first instruction
to execute after the RESET pin is brought low.
Introduction to Embedded Systems
8
What’s a vector table?
• Most modern microprocessors are not completely arbitrary in how
they deal with exceptions
• Exceptions are events which alter the normal program flow
- Generally more important than simple program branches
- Exceptions tend to be asynchronous to normal code execution
• Example of exceptions:
-
Processor RESET
Interrupts
Hardware failures
Divide by zero
• Most processors have well-defined processes for dealing with
exceptions when they occur
- Processor typically will finish executing the instruction it’s on and then
use the exception vector to cause an indirect jump to the exception
handler routine
Introduction to Embedded Systems
9
68000 exception vectors
• The 68000 family has an exception vector table occupying the first 256
long words of memory
- Byte address range 0x00000000..0x000003FF
• Some representative 68000 exception vectors ( each is a 32-bit address)
Vector number
0
2
3
4
5
8
25
31
32
47
64..255
Vector (Hex)
000
004
008
00C
010
014
020
064
07C
080
0BC
100..3FC
Exception type
RESET- Initial supervisor stack pointer
RESET- Initial program counter value
Bus error
Address error ( boundary violation )
Illegal instruction
Divide by zero
Priviledge violation
Level 1 Interrupt autovector
Level 7 Interrupt autovector
Trap #0 vector
Trap #15 vector
User interrupt vectors ( software interrupts)
Introduction to Embedded Systems
10
More on getting started-2
The processor comes alive ( powered-up or RESET ) in a well-defined
sequence
-
•
Cannot assume anything about the preserved state of the hardware unless it is
non-volatile
The processor RESET line must be held low for several thousand clock
cycles after power has stabilized
-
Resets all internal state machines
ROM
More ROM
0x00000000..0x00000003
Stack Pointer
First instruction
0x00000004..0x00000007
RESET Vector
RAM
Vector Table
Top of Stack
0x000003FF
Introduction to Embedded Systems
Stack grows to lower memory
•
11
Establishing your “C” environment-2
•
•
Let’s examine how a typical “C” compiler will partition memory
The code space and data space typically come after the system vectors in
low memory
The Stack Pointer is initialized to be at the top of physical memory and
grows down
How much memory is enough?
•
•
-
Size of your application?
Type of application ( stack utilization )
Dynamic memory requirements
Code
Space
System
Space
Data
Space
Heap
0xFFFFFFFF
0x00000400
0x00000000
Introduction to Embedded Systems
Stack
12
Establishing your “C” environment-3
•
Dynamic memory requirements
-
•
If your application allocates memory, then you will have to modify the getmem()
function to account for your system
malloc() and free() call getmem()
- RTOSs usually manage this, but still need hardware-level description of the
memory
Debug gotcha!
- It is a common problem for stack to grow
down and heap to grow up until they collide
and trash your system
- It is important to consider effects of malloc()
running out of memory
- Major Y2K meltdown!
Introduction to Embedded Systems
13
The Stack and Local Variables
• Subroutines (functions) require a local workspace to store their
temporary variables
- Private to the function: Cannot be accessed by the calling function or
other functions
• If functions are to be re-entrant or recursive then there must be
a way to dynamically allocate the local workspace
- Local workspace must be private to the function and to the instance
of the function
- Must create a local workspace each time the function is invoked
• Two concepts are very important, SP and FP:
- Stack frame: Region allocated on top* of the current stack position for
the local workspace
- Frame pointer: Processor register used to access local variables in a
frame (like a stack pointer allocated to one stack frame
Introduction to Embedded Systems
14
Establishing a stack frame
(a) State of the stack immediately
after the function call but before the
function entry
(b) State of the stack after establishing
a stack frame for d-bytes of local storage
Stack pointer
Data elements
Stack pointer
Return Address
Frame pointer
System reserves
d-bytes on the
stack for local
workspace
Return Address
Introduction to Embedded Systems
15
Nested Subroutines - Recursion
• Each time the next function is invoked, a new stack frame is created
State of stack while in function B
State of stack while in function A
Stack pointer
Stack Frame B
Stack pointer
A6
Frame pointer
Stack Frame A
Old Value of A6
Return Address
A6
Frame pointer
Old Value of A6
Stack pointer
Return Address
Stack Frame A
Old Value of A6
Stack pointer
Introduction to Embedded Systems
Return Address
16
Recursion ( continued )
State of stack after a second call to function A
•
The danger of using high-level
language constructs in coding
embedded systems is that memory
management of the HEAP becomes
difficult
Stack pointer
Stack Frame A
A6
Frame pointer
Return Address
Stack Frame B
Return Address
Stack Frame A
Stack pointer
Return Address
Introduction to Embedded Systems
17
Storage classes
• In embedded systems we are very concerned where the compiler
will attempt to place variables
- Would like to have some control over their physical placement in
memory, or somewhere else
• When variables are defined, their storage class can also be
defined
- auto
int a;
- Default storage class within a function
- Variable is destroyed ( deallocated ) when function is exited
- register int b;
- Request that the compiler try to keep this variable in an on-chip
register
- Faster to access
- Can we access a register variable with a pointer? Why?
Introduction to Embedded Systems
18
Storage classes
-
static
int c;
- Allows a local variable to keep its value every time the function is entered
- Variable’s lifetime is the same as the function’s lifetime
- Opposite effect of an automatic (local) variable
- Example: Count the number of time a function has been called
- A static variable is created on the heap!, it is not a local variable
- Compiler enforces scoping rules
- Code generated will initialize the variable the first time the function is called
-
extern int d;
- The variable is defined outside of the block
- Allows you to declare the same variable ( function ) in one or more modules
- Linker uses this information to resolve addresses
Introduction to Embedded Systems
19
Access modifiers
• There are two additional modifiers that are relevant:
- const int height = 20;
- The value of the variable may not be changed by the program
- Protects the variable from being unintentionally changed by the
programmer
- volatile char f;
- The variable may change its value on its own
- A memory-mapped I/O register
- Region of memory used as a buffer by an external device ( ie
DMA )
Introduction to Embedded Systems
20
More on volatile
• The standard practice in embedded systems is to declare the access to
hardware variable through a pointer variable;
- Example:
unsigned short int *hwPtr; //Declare a pointer to hardware
hwPtr = (unsigned short *)0xFFFF0100; //Assign address
*hwPtr = 0x5555; //Write to the hardware register
int foo = *hwPtr; // Read from the hardware register
• This could be problematic if the compiler is allowed to optimize
• The keyword, volatile, tells the compiler not to optimize the data
located at the address pointed to by the pointer
- Example:
volatile unsigned short int *hwPtr = (unsigned short *)0xFFFF0100;
Introduction to Embedded Systems
21
Typedef
•
•
The typedef keyword allows you create a new name for an
existing data type
Can make code more portable by isolating processor-dependent
data types in one location
-
•
•
Example:
1. typedef unsigned int uint16; //Integer on a 16-bit machine
2. typedef unsigned int uint32; //Integer on a 32-bit machine
Can make code more readable and compact
Nested typedefs are also possible
-
Example:
- typedef uint16 hwREG; //hwREG is data type for register
Introduction to Embedded Systems
22
Bit manipulation in C
•
•
•
Bit manipulation is easy in assembly language
Special operators are required in high level languages
Bit fields are common variables in embedded systems
Boolean
Operator
C Bitwise
Operator
C Logical
Operator
NOT
~
!
AND
&
&&
OR
|
||
XOR
^
none
Introduction to Embedded Systems
23
Bit manipulation examples
•
~ (bitwise-NOT) Example
typedef unsigned char Byte;
Byte foo;
Byte bar;
•
& (bitwise-AND) Example
typedef unsigned char Byte;
Byte foo;
Byte bar;
Byte arnie;
foo = 0x8E; // binary 10001110
bar = ~foo;
---------------------------------bar result: 0x71 binary
01110001
foo = 0xC3; // binary 11000011
bar = 0xA2; // binary 10100010
arnie = foo & bar;
---------------------------------arnie result: 0x82 binary 10000010
Introduction to Embedded Systems
24
Bit manipulation examples(2)
•
•
| (bitwise-OR) Example
typedef unsigned char Byte;
Byte foo;
Byte bar;
Byte arnie;
•
^ (bitwise-XOR) Example
typedef unsigned char Byte;
Byte foo;
Byte bar;
Byte arnie;
foo = 0xC3; // binary 11000011
bar = 0xA2; // binary 10100010
arnie = foo | bar;
---------------------------------arnie result: 0xE3 binary 11100011
foo = 0xC3;
binary 11000011
bar = 0xA2;
binary 10100010
arnie = foo ^ bar;
---------------------------------arnie result: 0x61 binary 01100001
Introduction to Embedded Systems
25
More on C programs*
• Because C and C++ were designed to be able to take advantage of
a host computer’s O/S services, adapting them to embedded
systems takes some getting use to
• First, we need to initialize the hardware
• Second, there may be C runtime services routines that will establish
and initialize variables
- Must move variables from ROM to RAM
• Third, need to get to MAIN()
- Usually, MAIN() runs all the time
• Linker/locater program builds the runtime image
- Need to get from the RESET vector to initialization to MAIN()
* I know it is out-of-sequence. Sigh...
Introduction to Embedded Systems
26
Getting the program started
foo()
bar()
Power-on
Get RESET vector
Set STACK pointer
Assembly
language
routine 1
RESET vector points to
Initialization routine
Assembly
language
Hardware
Initialization
Assembly language
routine JUMPS to
_main
main() is a
while(1) loop,
calling other C
functions or
assembly language
Assembly
language
routine 2
• _main is defined in the the assembly language source program as an EXTERN
Introduction to Embedded Systems
27
General rules
• Rule 1: Parameters are passed on the stack
• Rule 2: values are returned in registers
• Rule 3: C: Functions are called by assembly language routines as:
- JSR
_foo
;foo() is the C function
- Underscore is inserted by the compiler
- _foo is the starting address of the C function
• Rule 4: Assembly language functions are called like C functions
- void main(void)
extern _foo( void);
{
_foo() ;
}
• The assembly language routine must conform to parameter passing
convention of the compiler
Introduction to Embedded Systems
28
More general rules (2)
• C programs are created by building a run time image from separate
object files
• Typically use # include to declare external files
• Linker resolves addresses and creates executable code
main.c
/* This is the main test loop */
AddOne.h
AddTwo.h
#include "AddOne.h"
#include "AddTwo.h"
void main(void)
{
while ( 1 )/* Runs Forever */
{
int test = 0;
test = AddOne(test);
test = AddTwo(test);
}
}
Int
Int
AddOne( int );
AddTwo( int );
AddOne.c
AddTwo.c
#include "AddOne.h"
#include "AddTwo.h"
int AddOne( int ValueIn )
{
return ValueIn + 1 ;
}
int AddTwo( int ValueIn )
{
return ValueIn + 2 ;
}
Introduction to Embedded Systems
29
More general rules (3)
•
•
Make utility then builds the object file from the separate source files
Don’t ask me to explain how make works!
deliverables = prog1.abs
main.s: main.c addone.h addtwo.h
d:\hpcc68k\cc68k -S main.c
Make utility tracks the file dates and rebuilds
only those files that have changed
addone.s: addone.c addone.h
d:\hpcc68k\cc68k -S addone.c
Instructions to invoke the C complier, cc68K
addtwo.s: addtwo.c addtwo.h
d:\hpcc68k\cc68k -S addtwo.c
main.obj: main.s
d:\hpas68k\as68k main.s
addone.obj: addone.s
d:\hpas68k\as68k addone.s
addtwo.obj: addtwo.s
d:\hpas68k\as68k addtwo.s
Instructions to invoke the assembler, as68k
Instructions to invole the linker/loader
prog1.abs: main.obj addone.obj addtwo.obj
d:\hpas68k\ld68k -c prog1.cmd -L > prog1.txt
Introduction to Embedded Systems
30
Pointer and hardware
•
Consider the variable declaration:
-
•
•
int x;
/* Declare variable x */
int *p_x; /* Declare a pointer */
p_x = &x;
/* Bind p_x to x
*/
- If the variable x is stored in memory location 0x00400000, then p_x =
0x00400000
- MOVE.L #$400000,A3
;A3 becomes pointer to x
Why do we have to declare a type for the pointer?
When the asterisk is used outside of the declaration we are
dereferencing the pointer:
-
q = *p_x /* Assign to variable q, the value in the
memory location pointed to by p_x */
Assume that q is located in register D0
If <A3> = p_x, then the C statement is MOVE (A3),D0
Introduction to Embedded Systems
31
Pointer and hardware
• Remember, once a pointer variable is declared, it must be assigned
a variable to point to:
- p_x = &x/* Bind p_x to point to the memory location
where x is stored
*/
• But what do you do when you have hardware at a fixed
address in memory?
• Hardware devices are located at fixed addresses or address ranges
in the memory space of the processor
• Typically, you would communicate with a set of registers, or shared
memory
- Registers may be read only, write only, read/write, bit addressable,
auto-increment
• Need to bind the pointer variable to the memory address of the the
hardware
Introduction to Embedded Systems
32
Pointer and hardware-2
•
Consider the following code snippet:
#define
io_port
0xAA000100
/* define 8-bit I/O port */
void main (void)
{
int x ;
int *p_io_port;
/* Pointer to the I/O port */
p_io_port ( char *) io_port ; /* Must cast the pointer to a literal */
}
•
The pointer is bound to the fixed memory address of the hardware
register by casting it to the literal value, not by assigning it to the
variable
Introduction to Embedded Systems
33
More pointers and hardware
•
•
Using #define to assist readability
Assume that there are two I/O ports, port_A and port_B
-
•
•
•
•
•
•
port_A is located at address 0x80100
port_B is located at address 0x80104
#define port_base 0x80100
/* Base address of I/O device */
#define port_A (* (char *) ( port_base +0 ))
/* Huh? */
#define port_B (* (char *) ( port_base +4 ))
/* Huh +4 ? */
First: *0x80100 is just the contents of port_A
*0x80104 is the contents of port_B
(char *)( port_base + 0 ) tells the compiler that port_base +0 is a pointer
to a type char ( 8-bit value )
*(char *)( port_base + 0 ) assigns the value contained in port_A to the
variable, port_A
Introduction to Embedded Systems
34
Using mask bits
• Embedded devices will typically use a single bit to provide a status
value for an I/O device
- Example: Data bit 0, of a UART status port may have a value 1 if the
UART has data waiting and 0 if the UART is empty
• When we write in assembly language it is very easy to test the
value of a single bit
• Example:
- Suppose that an I/O port is located at 0x4000
- I/O port status is located at 0x4001
- Data available bit = DB0; DB0 = 1 when data is available
Introduction to Embedded Systems
35
C program to poll I/O port
void main(void)
{
int *p_status
;
/* Pointer to the status port */
int *p_data
;
/* Pointer to the data port */
p_status = (int*) 0x4001 ;/* Assign pointer to status port */
p_data = ( int* ) 0x4000 ; /* Assign pointer to data port */
do { } while (( *p_status & 0x01) == 0 ); /* Wait */
…..
…..
}
Introduction to Embedded Systems
36
Same program in 68000 assembly
language
status_port
data_port
EQU
EQU
$4001
$4000
* Assign status port value
* Assign data port
test_loop
MOVE.B status_port,D0 *Read status port into D0 reg
ANDI.B $01,D0
* Mask DB0
BEQ
test_loop
Introduction to Embedded Systems
37
Program Organization of a
Foreground/Background System
start
Interrupt
Interrupt
Interrupt
Initialize
ISR for
Task #1
ISR for
Task #2
ISR for
Task #3
IRET
IRET
IRET
Wait for
Interrupts
Introduction to Embedded Systems
38
Foreground/Background System
• Most of the actual work is performed in the "foreground" ISRs, with
each ISR processing a particular hardware event.
• Main program performs initialization and then enters a
"background" loop that waits for interrupts to occur.
• Allows the system to respond to external events with a predictable
amount of latency.
Introduction to Embedded Systems
39
Task State and Serialization
unsigned int byte_counter ;
void Send_Request_For_Data(void)
{
outportb(CMD_PORT, RQST_DATA_CMD) ;
byte_counter = 0 ;
}
void interrupt Process_One_Data_Byte(void)
{
BYTE8 data = inportb(DATA_PORT) ;
switch (++byte_counter)
{
case 1: Process_Temperature(data) ;
case 2: Process_Altitude(data) ;
case 3: Process_Humidity(data) ;
……
}
}
Introduction to Embedded Systems
break ;
break ;
break ;
40
Input Ready
STI
Input Data
ISR with Long Execution Time
Process Data
Output Device
Ready?
Yes
Output Data
Send EOI
Command to
PIC
IRET
Introduction to Embedded Systems
41
Input
Ready
Enter
Background
STI
Initialize
Data
Enqueued
?
Input Data
Yes
Output
Device
Ready?
Yes
Process
Data
Enqueue
Data
Send EOI
Command
to PIC
FIFO
Queue
Removing the Waiting
Loop from the ISR
Dequeue
Data
Output
Data
IRET
Introduction to Embedded Systems
42
Input
Ready
Output
Ready
STI
STI
Input Data
Data Enqueued?
Process
Data
Interrupt-Driven
Output
Yes
Enqueue
Data
FIFO
Queue
Dequeue
Data
Output Data
Send EOI
Command to
PIC
Send EOI
Command to
PIC
IRET
IRET
Introduction to Embedded Systems
43
Input
Ready
Kick Starting Output
STI
Input Data
SendData
Subroutine
Output
Ready
Process Data
Data Enqueued?
STI
FIFO
Queue
Enqueue Data
Output
Device
Busy?
No!
IRET
Dequeue Data
Clear Busy
Flag
Output Data
CALL SendData
(Kick Start)
Send EOI
Command to
PIC
Yes
Set Busy Flag
RET
Introduction to Embedded Systems
CALL SendData
Send EOI
Command to
PIC
IRET
44
Preventing Interrupt Overrun
Input
Ready
Input Data
Send EOI
Command to
PIC
ISR Busy Flag
Set?
Removes the interrupt request
that invoked this ISR.
When interupts get re-enabled (see STI below),
allow interrupts from lower priority devices (and
this device too).
Yes Ignore this Interrupt!
(Interrupts are re-enabled by the IRET)
Set ISR Busy
Flag
STI
Allow interrupts from any device.
Process data, write result to
output queue, & kick start.
Clear ISR
Busy Flag
Introduction to Embedded Systems
IRET
45
Preventing Interrupt Overrun
Input
Ready
Allow interrupts from higher
priority devices.
STI
Removes the interrupt
request that invoked this
ISR.
Input Data
Set the mask bit for this
device in the 8259 PIC
Disable future
interrupts from
this device.
Send EOI
Command to
PIC
Allow interrupts from
lower priority devices.
Process data, write result to output
queue, & kick start.
Clear the mask bit for this
device in the 8259 PIC
Enable future
interrupts from
this device.
Introduction to Embedded Systems
IRET
46
Moving Work into Background
• Move non-time-critical work (such as updating a display) into
background task.
• Foreground ISR writes data to queue, then background removes
and processes it.
• An alternative to ignoring one or more interrupts as the result of
input overrun.
Introduction to Embedded Systems
47
Limitations
• Best possible performance requires moving as much as possible into
the background.
• Background becomes collection of queues and associated routines
to process the data.
• Optimizes latency of the individual ISRs, but background begs for a
managed allocation of processor time.
Introduction to Embedded Systems
48
Multi-Threaded Architecture
ISR
Queue
Background
Thread
Queue
ISR
ISR
Queue
Background
Thread
Queue
ISR
Multi-threaded run-time function library (the real-time kernel)
Introduction to Embedded Systems
49
Thread Design
• Threads usually perform some initialization and then enter an
infinite processing loop.
• At the top of the loop, the thread relinquishes the processor while it
waits for data to become available, an external event to occur, or a
condition to become true.
Introduction to Embedded Systems
50
Concurrent Execution of Independent
Threads
• Each thread runs as if it had its own CPU separate from those of
the other threads.
• Threads are designed, programmed, and behave as if they are the
only thread running.
• Partitioning the background into a set of independent threads
simplifies each thread, and thus total program complexity.
Introduction to Embedded Systems
51
Each Thread Maintains Its Own Stack and
Register Contents
Context of Thread 1
Stack
Registers
Context of Thread N
Stack
Registers
CS:EIP
CS:EIP
SS:ESP
SS:ESP
EAX
EAX
EBX
EBX
EFlags
EFlags
Introduction to Embedded Systems
52
Concurrency
• Only one thread runs at a time while others are suspended.
• Processor switches from one thread to another so quickly that it
appears all threads are running simultaneously. Threads run
concurrently.
• Programmer assigns priority to each thread and the scheduler uses
this to determine which thread to run next
Introduction to Embedded Systems
53
Real-Time Kernel
• Threads call a library of run-time routines (known as the real-time
kernel) manages resources.
• Kernel provides mechanisms to switch between threads, for
coordination, synchronization, communications, and priority.
Introduction to Embedded Systems
54
Context Switching
• Each thread has its own stack and a special region of memory
referred to as its context.
• A context switch from thread "A" to thread "B" first saves all CPU
registers in context A, and then reloads all CPU registers from
context B.
• Since CPU registers includes SS:ESP and CS:EIP, reloading context B
reactivates thread B's stack and returns to where it left off when it
was last suspended.
Introduction to Embedded Systems
55
Context Switching
Thread A
Thread B
Executing
Suspended
Save context A
Restore context
B
Suspended
Executing
Executing
Restore context
A
Save context B
Suspended
Introduction to Embedded Systems
56
Non-Preemptive Multi-Tasking
• Threads call a kernel routine to perform the context switch.
• Thread relinquishes control of processor, thus allowing another
thread to run.
• The context switch call is often referred to as a yield, and this form
of multi-tasking is often referred to as cooperative multi-tasking.
Introduction to Embedded Systems
57
Non-Preemptive Multi-Tasking
• When external event occurs, processor may be executing a thread
other than one designed to process the event.
• The first opportunity to execute the needed thread will not occur
until current thread reaches next yield.
• When yield does occur, other threads may be scheduled to run first.
• In most cases, this makes it impossible or extremely difficult to
predict the maximum response time of non-preemptive multitasking systems.
Introduction to Embedded Systems
58
Non-Preemptive Multi-Tasking
• Programmer must call the yield routine frequently, or else system
response time may suffer.
• Yields must be inserted in any loop where a thread is waiting for
some external condition.
• Yield may also be needed inside other loops that take a long time to
complete (such as reading or writing a file), or distributed
periodically throughout a lengthy computation.
Introduction to Embedded Systems
59
Context Switching in a Non-Preemptive
System
Start
Scheduler selects highest priority
thread that is ready to run. If not the
current thread, the current thread is
suspended and the new thread
resumed.
Thread
Initialization
Wait?
Yes
Yield to other
threads
Data
Processing
Introduction to Embedded Systems
60
Preemptive Multi-Tasking
• Hardware interrupts trigger context switch.
• When external event occurs, a hardware ISR is invoked.
• After servicing the interrupt request the ISR raises the priority of
the thread that processes the associated data, then switches
context switch to the highest priority thread that is ready to run and
returns to it.
• Significantly improves system response time.
Introduction to Embedded Systems
61
Preemptive Multi-Tasking
• Eliminates the programmer's obligation to include explicit calls to
the kernel to perform context switches within the various
background threads.
• Programmer no longer needs to worry about how frequently the
context switch routine is called; it's called only when needed - i.e.,
in response to external events.
Introduction to Embedded Systems
62
Preemptive Context Switching
Thread A
Thread B
Thread A
Executing
Thread B
Suspended
ISR
Hardware
Interrupt
Process
Interrupt
Request
Context
Switch
Scheduler selects highest
priority thread that is ready
to run. If not the current
thread, the current thread
is suspended and the new
thread resumed.
IRET
Thread A
Suspended
Introduction to Embedded Systems
Thread B
Executing
63
Critical Sections
• Critical section: A code sequence whose proper execution is based
on the assumption that it has exclusive access to the shared
resources that it is using during the execution of the sequence.
• Critical sections must be protected against preemption, or else
integrity of the computation may be compromised.
Introduction to Embedded Systems
64
Atomic Operations
• Atomic operations are those that execute to completion without
preemption.
• Critical sections must be made atomic.
- Disable interrupts for their duration, or
- Acquire exclusive access to the shared resource through arbitration
before entering the critical section and release it on exit.
Introduction to Embedded Systems
65
Threads, ISRs, and Sharing
• Between a thread and an ISR:
- Data corruption may occur if the thread's critical section is interrupted
to execute the ISR.
• Between 2 ISRs:
- Data corruption may occur if the critical section of one ISR can be
interrupted to execute the other ISR.
• Between 2 threads:
- Data corruption may occur unless execution of their critical sections is
coordinated.
Introduction to Embedded Systems
66
Shared Resources
• A similar situation applies to other kinds of shared resources - not
just shared data.
• Consider two or more threads that want to simultaneously send
data to the same (shared) disk, printer, network card, or serial port.
If access is not arbitrated so that only one thread uses the resource
at a time, the data streams might get mixed together, producing
nonsense at the destination.
Introduction to Embedded Systems
67
Uncontrolled Access to a Shared Resource
(the Printer)
Shared Printer
Thread A
"HELLO\n"
Thread B
HgoELodLO
bye
Introduction to Embedded Systems
"goodbye"
68
Protecting Critical Sections
• Non-preemptive system: Programmer has explicit control over
where and when context switch occurs.
- Except for ISRs!
• Preemptive system: Programmer has no control over the time and
place of a context switch.
• Protection Options:
-
Disabling interrupts
Spin lock
mutex
semaphore
Introduction to Embedded Systems
69
Disabling Interrupts
• The overhead required to disable (and later re-enable)
interrupts is negligible.
- Good for short critical sections.
• Disabling interrupts during the execution of a long critical
section can significantly degrade system response time.
Introduction to Embedded Systems
70
Spin Locks
If the flag is set, another thread is currently using the
shared memory and will clear the flag when done.
Flag set?
No
Set Flag
Critical
Section
Clear Flag
do {
disable() ;
ok = !flag ;
flag = TRUE ;
enable() ;
} while (!ok) ;
L1:
AL,1
[_flag],AL
AL,AL
L1
Spin-lock in
assembly.
Spin-lock in C.
flag = FALSE ;
MOV
XCHG
OR
JNZ
MOV BYTE [_flag],0
Introduction to Embedded Systems
71
Spin Locks vs. Semaphores
• Non-preemptive system requires kernel call inside spin lock loop
to let other threads run.
• Context-switching during spin lock can be a significant overhead
(saving and restoring threads’ registers and stack).
• Semaphores eliminate the context-switch until flag is released.
Introduction to Embedded Systems
72
Semaphores
Semaphore “Pend”
Critical Section
Kernel suspends this thread if
another thread has possession of
the semaphore; this thread does not
get to run again until the other
thread releases the semaphore with
a “post” operation.
Semaphore “Post”
Introduction to Embedded Systems
73
Kernel Services
•
•
•
•
•
Initialization
Threads
Scheduling
Priorities
Interrupt Routines
•
•
•
•
Semaphores
Mailboxes
Queues
Time
Introduction to Embedded Systems
74
Initialization Services
Multi-C:
n/a
C/OS-II:
OSInit() ;
OSStart() ;
Introduction to Embedded Systems
75
Thread Services
Multi-C:
ECODE MtCCoroutine(void (*fn)(…)) ;
ECODE MtCSplit(THREAD **new, MTCBOOL *old) ;
ECODE MtCStop(THREAD *) ;
C/OS-II:
BYTE8 OSTaskCreate(void (*fn)(void *), void *data, void *stk, BYTE8 prio) ;
BYTE8 OSTaskDel(BYTE8 prio) ;
Introduction to Embedded Systems
76
Scheduling Services
Multi-C:
ECODE MtCYield(void) ;
C/OS-II:
void OSSchedLock(void) ;
void OSSchedUnlock(void) ;
BYTE8 OSTimeTick(BYTE8 old, BYTE8 new) ;
void OSTimeDly(WORD16) ;]
Introduction to Embedded Systems
77
Priority Services
Multi-C:
ECODE MtCGetPri(THREAD *, MTCPRI *) ;
ECODE MtCSetPri(THREAD *, MTCPRI) ;
C/OS-II:
BYTE8 OSTaskChangePrio(BYTE8 old, BYTE8 new) ;
Introduction to Embedded Systems
78
ISR Services
Multi-C:
n/a
C/OS-II:
OS_ENTER_CRITICAL() ;
OS_EXIT_CRITICAL() ;
void OSIntEnter(void) ;
void OSIntExit(void) ;
Introduction to Embedded Systems
79
Semaphore Services
Multi-C:
ECODE
ECODE
ECODE
ECODE
MtCSemaCreate(SEMA_INFO **) ;
MtCSemaWait(SEMA_INFO *, MTCBOOL *) ;
MtCSemaReset(SEMA_INFO *) ;
MtCSemaSet(SEMA_INFO *) ;
C/OS-II:
OS_EVENT *OSSemCreate(WORD16) ;
void OSSemPend(OS_EVENT *, WORD16, BYTE8 *) ;
BYTE8 OSSemPost(OS_EVENT *) ;
Introduction to Embedded Systems
80
Mailbox Services
Multi-C:
n/a
C/OS-II:
OS_EVENT *OSMboxCreate(void *msg) ;
void *OSMboxPend(OS_EVENT *, WORD16, BYTE8 *) ;
BYTE8 OSMboxPost(OS_EVENT *, void *) ;
Introduction to Embedded Systems
81
Queue Services
Multi-C:
ECODE MtCReceive(void *msgbfr, int *msgsize) ;
ECODE MtCSendTHREAD *, void *msg, int size, int pri) ;
ECODE MtCASendTHREAD *, void *msg, int size, int pri) ;
C/OS-II:
OS_EVENT *OSQCreate(void **start, BYTE8 size) ;
void *OSQPend(OS_EVENT *, WORD16, BYTE8 *) ;
BYTE8 OSQPost(OS_EVENT *, void *) ;
Introduction to Embedded Systems
82
Time Services
Multi-C:
n/a
C/OS-II:
DWORD32 OSTimeGet(void) ;
void OSTimeSet(DWORD32) ;
Introduction to Embedded Systems
83
Memory Allocation
Introduction to Embedded Systems
84
Multi-Tasking Provides Motivation
• Minimizing the amount of global data helps to reduce the
probability that the data of one task is accidentally being
modified by another.
• Although memory resource management is of concern in any
programming project, it becomes even more crucial for realtime embedded systems.
Introduction to Embedded Systems
85
How C Defines “Objects”
• An object is a region of memory that can be examined and
stored into.
• This definition predates object-oriented programming which
expands the concept.
• For the purposes of this course, simply think of objects as
variables.
Introduction to Embedded Systems
86
Object Attributes
Attribute Description
Type
char, int, unsigned int, etc.
(also implies size, range, and resolution)
Name
Value
Address
Scope
Lifetime
The identifier used to access the object.
The data held within the object.
The location in memory where the
object resides.
That part of the source code where
the object's name is recognized.
A notion of when the object is created
and destroyed, and thus when it is
available for use.
Introduction to Embedded Systems
87
Taking Advantage of Scope
• Same identifier may be used for more than one object, as long as
they are declared in different scopes.
• Access is minimized when object is declared in innermost scope.
• General Rule
- you can see outside, but not inside
• Globals must be declared outside of all functions.
- scope is from point of declaration to end of file.
• A variable/function declared with the static keyword is local to the
file
Introduction to Embedded Systems
88
Global Versus Local
int a ;
void f( )
Containing block
for ‘b’.
{
int b ;
….
b = …..
….
}
A global that can be used
by any function.
A temporary local only
used in function "f".
Introduction to Embedded Systems
89
Two Objects - Same Identifier
Scope of the
1st object
begins at the
point of
declaration
and ends at
the closing
brace of the
function.
#include <stdio.h>
void main()
{
int datum = 1 ;
printf("datum=%d\n", datum) ;
if ( … )
Scope of the 2nd
object. Impossible for
statements outside
this block to damage
this object. Hides the
1st object declared
with the same name
in outer block.
{
int datum = 2 ;
printf("datum=%d\n", datum) ;
}
printf("datum=%d\n", datum) ;
}
Introduction to Embedded Systems
90
Accessing External Variables (Defined in
Other Files)
/* Define and initialize in only one file */
int a = 123 ;
void f1( )
{
…..
}
/* Separate file requiring access … */
extern int a ; /* no initialization here! */
void f2( )
Move extern declaration inside
{
function
to minimize
a = …. /*
externalf2reference
*/ scope.
}
Introduction to Embedded Systems
91
Accessing External Functions (Defined in
Other Files)
double foo(char a, int x)
{
…..
}
Function prototype tells
compiler how to use foo.
extern double foo(char, int) ;
“extern” is not
required (default).
void bar( )
Moving extern declaration of
{
function inside function bar does
….
y = foo(c, 32)NOT
; reduce its scope!
….
}
Introduction to Embedded Systems
92
Misuse of Globals
Dangerous
unsigned total = 0 ; /* These objects are */
unsigned count = 0 ; /* global to the file! */
void EnterScore(unsigned score)
{
total += score ;
count++ ;
printf("average score = %u\n",
total / count) ;
}
Protected
void EnterScore(unsigned score)
{
static unsigned total = 0 ; /* These are local */
static unsigned count = 0 ; /* to this function */
total += score ;
count++ ;
printf("average score = %u\n",
total / count) ;
}
If a persistent object is needed by a single function, declare it local to
that function.
Introduction to Embedded Systems
93
Lifetime
• Objects are:
-
Created
Initialized
used, and
destroyed.
• Lifetime refers to the
duration of an object's
existence (i.e., when it is
accessible).
Introduction to Embedded Systems
94
Memory Allocation in C
Allocation Lifetime
Entire program
Static
execution.
From entry to exit of
Automatic
containing function.
Dynamic
From malloc() to free().
Location Memory reclaimed by
Operating system
Stack
Program (implicit)
Heap
Programmer
(explicit)
Introduction to Embedded Systems
95
Characteristics of Static Objects
• Objects are allocated (and possibly initialized) once when program
is first loaded into memory.
• Location in memory never changes during execution of the program
- objects not destroyed until the program terminates.
• the “static” keyword
- Inside a Function: Changes the allocation method from auto (default)
to static.
- Outside of Functions: Has no effect on allocation (remains static), but
makes the identifier inaccessible from other files.
Introduction to Embedded Systems
96
Pros and Cons of Static
• Advantage: Persistence of values.
- Unlike objects that use automatic allocation, a value stored in a static
object will remain from one execution of a function to another.
• Disadvantage: Inability to reclaim memory.
- Extensive use of static objects can result in programs that require lots
of memory.
• Every global object is a static object!
- Avoiding scope restrictions by declaring all objects global ultimately
produces very "fat" programs.
• Every static object is not necessarily global!
- Using global to get persistence isn’t necessary; Just add the keyword
static to the local declaration.
Introduction to Embedded Systems
97
Characteristics of Automatic Objects
•
Objects are allocated on every entry to their containing function.
•
Objects are initialized on every entry to their containing block.
•
Location is fixed during execution of the function, but released when the
function is exited.
-
•
Advantage: Memory conservation through reuse.
-
•
Functions may return the value of an automatic variable, but not its address.
The program manages the sharing of this memory resource automatically.
Disadvantage: Lack of persistence.
Introduction to Embedded Systems
98
Object Creation (Auto vs. Static)
#include <stdio.h>
void f1()
{
auto int a ;
static int s ;
printf(" &auto = %08X\n", &a) ;
printf("&static = %08X\n", &s) ;
}
Program Output:
&auto = 0008E8BC
&static = 0000BA28
&auto = 0008E89C
&static = 0000BA28
&auto = 0008E8BC
&static = 0000BA28
void f2() { auto int x; f1() ; }
int main() { f1() ; f2() ; f1() ; return 0 ;}
Introduction to Embedded Systems
99
Object Initialization (Auto vs. Static)
#include <stdio.h>
int f1() { auto int a ; a = 0; return ++a ; }
int f2() {static int s ; s = 0; return ++s ; }
int f3() { auto int a = 0 ; return ++a ; }
int f4() {static int s = 0 ; return ++s ; }
int main()
{
printf("f1():
printf("f2():
printf("f3():
printf("f4():
return 0 ;
}
%d
%d
%d
%d
%d
%d
%d
%d
%d\n",
%d\n",
%d\n",
%d\n",
f1(),
f2(),
f3(),
f4(),
f1(),
f2(),
f3(),
f4(),
Introduction to Embedded Systems
Program Output:
f1():
f2():
f3():
f4():
f1())
f2())
f3())
f4())
1
1
1
3
1
1
1
2
1
1
1
1
;
;
;
;
100
Object Destruction (Auto vs. Static)
int *f1(){static int s = 12345 ; return &s ;}
int *f2(){ auto int a = 12345 ; return &a ;}
void Demo(int *(*f)()) {
int *p = (*f)() ;
printf("Address=%08X, Contents=%d\n", p, *p) ;
printf("Address=%08X, Contents=%d\n", p, *p) ;
printf("\n") ;
}
int main() { Demo(f1) ; Demo(f2) ; return 0 ;}
Program Output:
Address=0008E88C, Contents=12345
Address=0008E88C, Contents=12345
Address=0000AECC, Contents=12345
Address=0000AECC, Contents=583820
Introduction to Embedded Systems
101
Pros and Cons of Register
• Advantage: Faster Access to Value
• Disadvantage: Knowing when to use register allocation is not
obvious.
- Register allocation is local within each function. When another function
is called all register variables must be preserved. This overhead often
offsets any performance gain.
• Example: register unsigned index ;
- Outside Functions: Illegal.
- Inside Functions: Attempts to change allocation from automatic
(default) to register for faster access; cannot apply “address-of”
operator.
Introduction to Embedded Systems
102
Allocating Dynamic Memory
• void *malloc(int bytes) ; void free(void *pointer2block) ;
• Allocates an un-initialized block of memory from the “heap” and
returns a pointer to the first byte.
• Advantage: Persistence of static allocation and memory
conservation through reuse.
• Disadvantage: Programmer’s responsibility to release memory
- failure to do results in a slow "memory leak" that can be very difficult
to debug.
- fragmentation
Introduction to Embedded Systems
103
Using Dynamic Memory
#include <stdlib.h>
#define NULL ((void ) 0)
…
typedef … ANYTYPE ;
….
ANYTYPE *p ;
/* required to use malloc and free. */
/* may not be defined in stdlib.h */
p = (ANYTYPE *) malloc( sizeof(ANYTYPE) ) ;
/* object is created */
if (p == NULL) Error("Insufficient heap space") ;
Object
….
Lifetime
…. The object is initialized and used here via the pointer.
….
free(p) ;
/* object is destoyed */
Introduction to Embedded Systems
104
Characteristics of alloca()
• Allocation: Programmer computes size and calls library function
alloca (like dynamic).
• Location: Memory is allocated from the stack (like automatic).
• Release: “Automatic” upon return from function that called alloca.
• Advantages:
- Very fast: simply adjusts the stack pointer register.
- Release is automatic (no “memory leaks”).
• Disadvantages:
- No indication of allocation failure.
- No persistence of values (just like automatic).
- Not a standard feature of C.
Introduction to Embedded Systems
105
Using alloca()
FILE *OpenFile(char *name, char *ext, char *mode)
{
int size = strlen(name) + strlen(ext) + 2 ;
char *filespec = (char *) alloca(size) ;
FILE *fp ;
sprintf(filespec, “%s.%s”, name, ext) ;
fp = fopen(filespec, mode) ;
if (fp != NULL) return fp ;
printf(“Can’t open file: %s!\n”, filespec) ;
return NULL ;
}
Introduction to Embedded Systems
Stack pointer is
decreased by “size”
bytes.
Stack pointer is
automatically
restored on any
return, releasing the
memory.
106
Using Variable Size Arrays
FILE *OpenFile(char *name, char *ext, char *mode)
{
char filespec[strlen(name) + strlen(ext) + 2] ;
FILE *fp ;
sprintf(filespec, “%s.%s”, name, ext) ;
fp = fopen(filespec, mode) ;
……
}
•
Allocation:
-
•
Not supported by all C
compilers!
VSA is allocated only once on entry to block.
Function alloca() allocates on every call.
Release:
-
VSA is released at end of scope.
Memory allocated with alloca() is released when function returns.
Introduction to Embedded Systems
107
Shared Memory
• Shared memory is data that is accessed by two or more
asynchronous instruction sequences.
• Asynchronous means that there is no predictable time relationship
among the various instruction sequences.
• Thread: A sequence of instructions; threads are usually
asynchronous relative to each other.
• All ISRs are threads, but not all threads are ISRs!
Introduction to Embedded Systems
108
Asynchronous Access Can Corrupt Shared
Data!
Thread A
Thread B
shared = 3 ;
Asynchronous Task
Switch or Interrupt
shared++ ;
x = 2 * shared ;
Introduction to Embedded Systems
109
Task Switch = Interrupt!
Thread A
C compiler output
MOV EAX,10
MUL DWORD [shared]
shared *= 10 ; <context switch here>
MOV [shared],EAX
Thread B
shared = 5 ;
Note that interrupts (and thus a context switch) can occur
between any two CPU instructions, not just between any two
C language statements!
Introduction to Embedded Systems
110
Program Complexity
• Threads are easier to design, understand, and debug when they are
as independent of other threads as possible.
• Shared memory reduces thread independence.
• Shared memory increases program complexity.
Introduction to Embedded Systems
111
How Sharing Can Occur
• Shared Global Data:
- A public (global) object is accessed by two or more threads
- Easiest cause of sharing to recognize.
- Minimize global objects, whether your program is multi-threaded or
not, because global objects allow linkages between functions and thus
contribute to program complexity.
• Shared Private Data:
- The address of a private object is given to another thread
• Shared Functions:
- A static object is accessed by a function that is called by more than one
thread (shared function)
- Any function called by a shared function is also a shared function.
- Any static object (local or global) referenced within a shared function is
shared data
Introduction to Embedded Systems
112
Thread-Safe Functions
• Thread-safe functions are shared functions that only modify
thread-specific data.
• Local automatic and dynamic objects are inherently threadspecific; local static objects are not.
Introduction to Embedded Systems
113
Re-Entrant Functions
Thread A
Thread B
Enters
printf
Printf suspended
Enters,
executes, and
exits printf
Exits
printf
Introduction to Embedded Systems
114
Re-Entrant Functions
• Re-entrant functions are those which may be safely re-entered
without data corruption.
• Re-entrant functions never modify local static objects.
• Re-entrant functions are inherently thread-safe.
Introduction to Embedded Systems
115
Read-Only Data
•
Data corruption only occurs when shared data is modified.
•
Shared data that is read but never written can never be corrupted.
•
Add the keyword “const” to have the compiler verify that all access to
the object is read-only:
static const char digits[] = “0123456789” ;
Introduction to Embedded Systems
116
Coding Practices to Avoid
• Functions that keep internal state in local static objects.
- E.g., strtok, rand
• Functions that return the address of a local static object.
- E.g., ctime, asctime, localtime, gmtime, getenv, strerror and tmpnam.
Introduction to Embedded Systems
117
Function with Internal State
char *strtok(char *string, char *delimeters)
{
Problem: Only one
static char *cursor ;
instance for all threads.
char *beg, *end ;
if (string != NULL) cursor = string ;
if (cursor == NULL) return NULL ;
beg = cursor + strspn(cursor, delimeters) ;
if (*beg == '\0') return NULL ;
end = strpbrk(beg, delimeters) ;
if (end == NULL) cursor = NULL ;
else { *end++ = '\0'; cursor = end; }
return beg ;
}
Introduction to Embedded Systems
118
Fixing Internal State
char *strtok_r(char *string, char *delimeters, char **cursor)
{
char *beg, *end ;
if (string != NULL) *cursor = string ;
...
return beg ;
}
Thread-specific version
supplied by caller.
char *my_cursor ;
…
p = strtok_r(…, …, &my_cursor) ;
…
Introduction to Embedded Systems
119
Function Returning Static Buffer
char *Make_Filename(char *name, int version)
{
static char fname_bfr[13] ;
...
return fname_bfr ;
}
Introduction to Embedded Systems
120
Fixing Static Buffer (Sol’n #1)
char *Make_Filename(char *name, int version, char *fname_bfr)
{
…
return fname_bfr ;
Let caller provide a
}
thread-specific instance
of a buffer.
Introduction to Embedded Systems
121
Fixing Static Buffer (Sol’n #2)
char *Make_Filename(char *name, int version)
{
char *fname_bfr = (char *) malloc(13) ;
if (fname_bfr == NULL) return NULL ;
…
return fname_bfr ;
}
Thread-specific instance of buffer
allocated from heap when function is
called; must be released by caller.
Introduction to Embedded Systems
122
Corruption of Data in Shared Memory
Main Program
MOV EBX,[_pointer2q]
MOV EAX,[EBX+_count]
ISR
pointer2q->count--
SUB EAX,1
MOV [EBX+_count],EAX
...
MOV EBX,[_pointer2q]
MOV EAX,[EBX+_count]
ADD EAX,1
MOV [EBX+_count],EAX
...
pointer2q->count++
Introduction to Embedded Systems
123
Effect of Processor Word Size
• Previous example is somewhat contrived:
-
Most compilers would use single increment (INC) and decrement
(DEC) instructions to update the count.
- Interrupts only occur between instructions, so the decrement of the
count would then complete before the interrupt is recognized.
• But what if count was 32 bits and processor is 16 bits?
- 8-bit processors: Anything other than a char
- 16-bit processors: longs, far pointers, and all floating-point data.
- 32-bit processors:
- 64-bit long long ints
- Double- and extended-precision floating-point data.
- ….
Introduction to Embedded Systems
124
Type Qualifier “volatile”
• Added to the declaration of any object in C to indicate that its
value may be modified by mechanisms other than the code in
which the declaration appears.
• Serves as a reminder that shared data may be modified by
another thread or ISR.
• Prevents certain compiler optimizations that would otherwise be
invalid.
• But does not prevent data corruption due to shared memory.
Introduction to Embedded Systems
125
Insert the keyword “volatile” here to
prevent the compiler optimizations.
long Get_Shared(void)
{
extern long shared ;
long validated ;
do
This code attempts to read a shared
variable without disabling interrupts or
using spin locks or semaphores.
{
validated = shared ;
} while (validated != shared) ;
return validated ;
}
Since “shared” is not modified within the loop, an
optimizing compiler might preload a CPU register with
the value of "shared" before entering the loop, and
access the register instead of the slower memory.
Introduction to Embedded Systems
126