You are on page 1of 64

4.

3: The shared data problem
• Inconsistencies in data used by a task and updated by
an ISR; arises because ISR runs at just the wrong time.
• Data is often shared because it is undesirable to have ISRs
do all the work.
– ISRs should “hand off” some of the processing to task code.
– This implies shared variables or communication between the ISR
and the related task.
• Lab 3 simpler (in part) because we don’t have to worry
about this.

©J Archibald 425 F10 3:1

Figure 4.4: code example
static int iTemperatures[2];

void interrupt vReadTemperatures (void) What does this code do?
{
iTemperatures[0] = !! read in value from HW
iTemperatures[1] = !! read in value from HW
}

void main (void)
{
int iTemp0, iTemp1;
while (TRUE)
{
iTemp0 = iTemperatures[0];
iTemp1 = iTemperatures[1];
if (iTemp0 != iTemp1)
!! Set off howling alarm;
}
}

©J Archibald 425 F10 3:2

Fig 4.4: discussion
• Note keyword “interrupt” in first function. (It is an ISR
written in C; our tools don’t support this.)
– It is never called from task code; when will it run?
– How do we connect this ISR with its interrupt?
• The main routine is an infinite loop.
– Normally not a good idea, but common in embedded systems.
– Compares two temperatures and raises alarm if they ever differ.
• The ISR updates the temperature variables.
– Assume interrupt asserted at
• regular intervals, based on timer, or
• when either temperature changes

©J Archibald 425 F10 3:3

iTemp1. Figure 4. } } ©J Archibald 425 F10 3:4 .4: analysis static int iTemperatures[2]. Suppose interrupt iTemp1 = iTemperatures[1]. occurs here if (iTemp0 != iTemp1) !! Set off howling alarm. void interrupt vReadTemperatures (void) { What can go wrong? iTemperatures[0] = !! read in value from HW iTemperatures[1] = !! read in value from HW } void main (void) { int iTemp0. while (TRUE) { iTemp0 = iTemperatures[0].

81 at the next. both values identical at each reading. • General analysis required to detect and prevent: – Is there a point in code where an interrupt can mess things up? ©J Archibald 425 F10 3:5 . – Interrupt occurs between reading temps in task code. – Howling alarm set off. The shared-data problem • Problem scenario with example code: – Temperature rising. – Test in main() compares old value with new value. • Say. entire county evacuated. 80 at one reading.

) • Does this fix the problem? ©J Archibald 425 F10 3:6 . Fixing the problem: first try • Suppose you decide that the problem is copying the data to local variables. rather than copying them. (Modified code is on next slide. – Your code tests the values directly. • You change the main() function. but leave the ISR exactly the same.

} } ©J Archibald 425 F10 3:7 . Figure 4. void interrupt vReadTemperatures (void) { iTemperatures[0] = !! read in value from HW iTemperatures[1] = !! read in value from HW } void main (void) { while (TRUE) { if (iTemperatures[0] != iTemperatures[1]) !! Set off howling alarm.5: Is problem fixed? static int iTemperatures[2].

Consider 8086 instruction sequence
static int iTemperatures[2];

void interrupt vReadTemperatures (void)
{
iTemperatures[0] = !! read in value from HW
iTemperatures[1] = !! read in value from HW ...
mov ax,[iTemperatures+0]
}
cmp ax,[iTemperatures+2]
je okay
void main (void) ; set off alarm
{ okay: ...
while (TRUE)
{
if (iTemperatures[0] != iTemperatures[1])
!! Set off howling alarm;
}
}

©J Archibald 425 F10 3:8

Ensuring correctness
• Assembly code for other platforms will be similar.
• Key point: will the two values be accessed by a single
machine instruction?
• If not (as is almost always the case),
– Nothing prevents an interrupt between the two memory reads.
– The alarm may be set off incorrectly.
• If so, the code could work on this machine, but won’t on
others it is ported to.
– This makes code development tricky!
– Ideally we should write code that works everywhere.

©J Archibald 425 F10 3:9

The big picture
• When does this type of problem arise?
– When data is shared between an ISR and task code it interrupts,
and when the data can appear to be in an inconsistent state.
• Does the bug manifest itself consistently?
– No, appears randomly, often with low probability.
• Would you catch it through testing?
– Not necessarily; exhaustive testing generally impossible.
– Testing shows the presence of bugs, but cannot prove absence.
• Only real solution: write bug-free code.
– Think long and hard about correctness of code at all levels.
– Stick with basic principles that work.
– But still do lots of testing!

©J Archibald 425 F10 3:10

000 instructions). that the tick frequency is set to the default value (an interrupt every 10. and that the simulator executes 50. What is the probability of any given timer tick occurring while the keypress ISR is running? (The tick interrupt is higher priority than keypress. that the keypress ISR (including handler) requires 200 instructions to execute on average. Assume that you are pressing keys at a rate of 6 per second. Aside: catching errors Hypothetical midterm question: Suppose you are testing lab3 code using the class toolset.000 instructions per second.) ©J Archibald 425 F10 3:11 .

say. Choose one specific tick interrupt in steady state.4%. so frequency is 1200/50000 or 2. 200 Solution instructions (not to scale) keypress ISR time tick tick tick tick tick 10. The probability of it occurring during the keypress ISR equals the fraction of the overall time that the keypress ISR runs. In one second the keypress ISR runs for a total of 6*200 = 1. Thought experiment: extend to probability of interrupting between two specific instructions in keypress ISR. ©J Archibald 425 F10 3:12 .000 instructions one second Keypress and tick are independent.200 instructions. Pick a representative interval: one second.

[iTemperatures+2] iTemperatures[1] = !! read in value from HW sti } je okay .. } } ©J Archibald 425 F10 3:13 . { while (TRUE) { if (iTemperatures[0] != iTemperatures[1]) !! Set off howling alarm.. { cli mov ax.[iTemperatures+0] iTemperatures[0] = !! read in value from HW cmp ax. Figure 4.. void interrupt vReadTemperatures (void) .. set off alarm void main (void) okay: .5 One solution: disable interrupts static int iTemperatures[2].

Possibilities: • Inline assembly • Special macros • Function call (a short routine written in assembly) – Could it be done automatically by compiler? ©J Archibald 425 F10 3:14 . Disabling interrupts: discussion • How was code changed? – Interrupts are disabled briefly while reading the data. • How can we make this happen in C code? – Platform dependent. • Why does this work? – ISR is prevented from running (and changing data) during short interval while both values are read.

Options inline assembly function call _asm { disable().... enable().. cli iTemp0 = .... assembly code sti disable: cli } ret enable: sti ret What are tradeoffs? ©J Archibald 425 F10 3:15 .. iTemp0 = .. } iTemp1 = ... iTemp1 = . _asm { ..

• Which results in more portable code? ©J Archibald 425 F10 3:16 . cli. ret • What is the overhead with inline assembly? – cli • Difference is just two instructions. – Small difference in performance. Comparison • What is the overhead for the function call method? – call. – Essentially negligible for our purposes this semester.

– Just make sure they are disabled for short periods of time. ©J Archibald 425 F10 3:17 . – Increasing response time by a few instructions is not a big deal. • Disabling all interrupts is a simple. Discussion • Why lock out all interrupts. details are platform dependent. – The overhead of disabling a particular interrupt generally higher. one-size-fits-all solution. and not just mask the one that could cause a problem? – Isn’t performance (response time) degraded more this way? • Key points to consider: – Interrupts are disabled for a short period of time only.

• No existing tools are clever enough to determine when interrupts need to be disabled. – It requires a knowledge of the data state and how it can change dynamically. – Compilers work on static information only. and you are (presumably) lots smarter than the compiler. Role of compilers • Why can’t compilers do this automatically? – In general. – It’s hard for humans to do. ©J Archibald 425 F10 3:18 . compilers cannot identify (truly) shared data.

. ©J Archibald 425 F10 3:19 . i.e. • Critical Section: a section of code that must be atomic for correct operation. if it can be guaranteed to execute as an unbreakable (atomic) unit. Terminology • Atomic: a section of code is atomic if it cannot be interrupted.

• What are the natural atomic units of execution? – Single machine instructions only. you have a critical section. – If a line of C-code needs to be atomic. • Line of C-code rarely maps to single instruction and is therefore not atomic. then re-enabling interrupts. The shared-data problem • Arises when task code accesses shared data non-atomically. ©J Archibald 425 F10 3:20 . – We’ll see other methods later. • How can we make portion of code atomic? – Preferred approach: disabling.

iHours. if (iHours >= 24) iHours = 0. if (iSeconds >= 60) { iSeconds = 0. if (iMinutes >= 60) { iMinutes = 0. } ©J Archibald 425 F10 3:21 . } } !! Do whatever needs to be done to the HW How far off can } long lSecondsSinceMidnight(void) return value be? { return (((iHours * 60) + iMinutes) * 60) + iSeconds. void interrupt vUpdateTime (void) { ++iSeconds.9: What can go wrong here? static int iSeconds. iMinutes. ++iMinutes. ++iHours. Figure 4.

Figure 4. void interrupt vUpdateTime (void) { ++iSeconds. ++iHours. ++iMinutes. return (((iHours * 60) + iMinutes) * 60) + iSeconds. if (iHours >= 24) enable().} } } !! Do whatever needs to be done to the HW A WRONG SOLUTION! } long lSecondsSinceMidnight(void) { return (((iHours * 60) + iMinutes) * 60) + iSeconds. if (iSeconds >= 60) { iSeconds = 0.9: Making it atomic static int iSeconds. iMinutes. iHours. disable(). iHours = 0. if (iMinutes >= 60) long lSecondsSinceMidnight(void) { { iMinutes = 0. } ©J Archibald 425 F10 3:22 .

iHours. lReturnVal = (((iHours*60)+iMinutes)*60)+iSeconds. } ©J Archibald 425 F10 3:23 . iHours = }0. void interrupt vUpdateTime (void) { ++iSeconds. enable(). ++iHours. long lSecondsSinceMidnight(void) ++iMinutes. { disable().9: Making it atomic static int iSeconds. } } A BETTER SOLUTION !! Do whatever needs to be done to the HW } long lSecondsSinceMidnight(void) { return (((iHours * 60) + iMinutes) * 60) + iSeconds. if (iSeconds >= 60) { iSeconds = 0. if (iHours >= 24) return lReturnVal. iMinutes = 0. { if (iMinutes >= 60) long lReturnVal. iMinutes. Figure 4.

iMinutes. ++iHours. iHours =} 0. iMinutes = 0.9: Making it atomic static int iSeconds. long lReturnVal. BOOL fInterruptStateOld. if (iHours >= 24) return lReturnVal. iHours. } ©J Archibald 425 F10 3:24 . lReturnVal = (((iHours*60)+iMinutes)*60)+iSeconds. Figure 4. ++iMinutes. if (fInterruptStateOld) enable(). if (iSeconds >= 60) long lSecondsSinceMidnight(void) { { iSeconds = 0. } } THE BEST SOLUTION !! Do whatever needs to be done to the HW } long lSecondsSinceMidnight(void) { return (((iHours * 60) + iMinutes) * 60) + iSeconds. if (iMinutes >= 60) { fInterruptStateOld = disable(). void interrupt vUpdateTime (void) { ++iSeconds.

– This will bite some of you this semester. – Allows function with critical section to be called from normal code and from other critical sections. guaranteed! • How is “best” solution different? – Re-enables interrupts only if they were on in first place. ©J Archibald 425 F10 3:25 . A subtle point • What can go wrong with “better” solution? – What if subroutine is called from inside a critical section of another part of the program? • Interrupts will be re-enabled – not what you want at that point.

void interrupt vUpdateTime (void) { . ++lSecondsToday. } ©J Archibald 425 F10 3:26 ... if (lSecondsToday == 60 * 60 * 24) lSecondsToday = 0L.11: Another approach: Does it work? static long int lSecondsToday. Figure 4... } long lSecondsSinceMidnight (void) { return lSecondsToday. .

– ISR. only one shared variable. • Bottom line: even with code accessing a single shared variable. (How far off can it be?) • Obviously platform specific. Fig 4. – Code will work on all target platforms (assuming enable/disable functions are used). ©J Archibald 425 F10 3:27 . just more subtle: accessing a single variable is not necessarily atomic.11: Discussion • Just counts seconds. • Example: accessing a long on 8086 takes multiple instructions. can be interrupted between 16-bit accesses. • Does the problem go away? – No. you’re usually better off disabling interrupts. task functions do not share multiple data values.

while (lReturn != lSecondsToday) lReturn = lSecondsToday. return lReturn. Figure 4.. if (lSecondsToday == 60 * 60 * 24) lSecondsToday = 0L. ++lSecondsToday.. .12: Yet another approach static long int lSecondsToday... lReturn = lSecondsToday. } ©J Archibald 425 F10 3:28 . void interrupt vUpdateTime (void) { . } long lSecondsSinceMidnight(void) { long lReturn.

– Justification: no intervening write took place between the two reads. – “volatile” tells compiler that variable is subject to change from something it is not aware of. • Problem: a good optimizing compiler is smart enough to read memory just once. ©J Archibald 425 F10 3:29 .12: Discussion • Basic idea: read value repeatedly until you get two identical readings. • Solution: use “volatile” keyword to inform the compiler that special handling is required. Fig 4. so value in register still okay. – Forces compiler to read memory every time the variable is accessed and not to make “obvious” optimizations. and keep the value in a register.

return lReturn. } long lSecondsSinceMidnight(void) { long lReturn. ++lSecondsToday.12: Modified version static volatile long int lSecondsToday.. } ©J Archibald 425 F10 3:30 . while (lReturn != lSecondsToday) lReturn = lSecondsToday. lReturn = lSecondsToday. if (lSecondsToday == 60 * 60 * 24) lSecondsToday = 0L. void interrupt vUpdateTime (void) { ... . Figure 4..

Response time revisited • How long does it take for the system to respond to an interrupt? IRQ2 asserted IRQ1 asserted Task ISR 1 interrupts disabled Handler 1 ISR 2 interrupts disabled Handler 2 actual response ©J Archibald 425 F10 3:31 .

The time for hardware to respond to the interrupt when enabled. (What is this on 8086?) 4. The total time required to execute all interrupt service routines of higher priority. Worst-case interrupt latency: components 1. 2. ©J Archibald 425 F10 3:32 . The time for the ISR+handler to save the context and then do the work required to be the “response”. 3. The longest period of time that interrupts are disabled.

What can designer control? 1. 3. run handler? • Saving context: depends on number of registers • Handler efficiency: good coding ©J Archibald 425 F10 3:33 . Max length of critical sections? • Keep them short! 2. Overhead of hardware response? • Fixed when you select the processor. • Keep all ISRs lean and mean. Time to save context. Execution time of higher-priority ISRs? • Assign priorities carefully. 4.

©J Archibald 425 F10 3:34 . Measuring time • In simulator. basic time unit is one instruction cycle. enabled interrupt before starting next instruction. but added realism would buy us little. • CPU will respond to asserted. • Overhead of hardware response on 8086: – Finish current instruction • May involve multiple memory accesses – Push 3 words on stack. read 2 words from jump vector table. the time it takes to execute one instruction. – Unlikely to be equal for all instructions in hardware.

Meeting design specifications • Can we guarantee that response time is less than. then disable interrupts without having hardware respond to pending interrupts – Hardware priority level assigned to relevant interrupt – Run length of other ISRs + handlers • Just one time through each. or multiple runs? – Run length of this ISR + handler to point of “response” • How important is such a guarantee? – Big in real world. 625 s? What do we need to know? – Number of critical sections. less critical in our labs ©J Archibald 425 F10 3:35 . length of each • Only the longest critical section is considered • No way for software to enable. say.

4. void interrupt vReadTemperatures (void) { if (fTaskCodeUsingTempsB) { iTemperaturesA[0] = !! read in value from HW iTemperaturesA[1] = !! read in value from HW } else { iTemperaturesB[0] = !! read in value from HW iTemperaturesB[1] = !! read in value from HW } } void main (void) { while (TRUE) { if (fTaskCodeUsingTempsB) if (iTemperaturesB[0] != iTemperaturesB[1]) !! Set off howling alarm. fTaskCodeUsingTempsB = !fTaskCodeUsingTempsB. Fig. static BOOL fTaskCodeUsingTempsB = FALSE. else if (iTemperaturesA[0] != iTemperaturesA[1]) !! Set off howling alarm. iTemperaturesB[2].15: An alternative to disabling interrupts static int iTemperaturesA[2]. } } ©J Archibald 425 F10 3:36 .

Fig. • Why does this work? – Global flag does not change while temperatures are being read in task code. 4. especially at critical point between the two reads.15: Discussion • Key idea: use double buffering with a global flag to ensure that the reader and writer access separate arrays. – Values tested in task code are always corresponding pair – no way for ISR to change them at wrong time while reading. • Is there a down side? ©J Archibald 425 F10 3:37 .

{ int iHead = 0. Figure 4. iTemperatureQ[iHead] = iTail += 2. while (TRUE) void interrupt vReadTemperatures (void) { { if (iTail != iHead) if ( !(( ihead+2==iTail) || { (iHead== Q_SIZE-2 && iTail==0))) iTemp1 = iTemperatureQ[iTail]. !! Compare values if (iHead== Q_SIZE) } iHead = 0. int iTail = 0. iTemp2. int iTemp1. } } } else !! throw away next value } ©J Archibald 425 F10 3:38 .16: Another alternative #define Q_SIZE 100 void main (void) int iTemperatureQ[Q_SIZE]. iHead += 2. !! read one temperature if (iTail == Q_SIZE) iTemperatureQ[iHead+1] = !! read other temperature iTail = 0. { iTemp2 = iTemperatureQ[iTail+1].

©J Archibald 425 F10 3:39 . – Queue buffers data between ISR and task that processes it. – Buffering with queues is a commonly used technique. – Processing rate must be at least as great as the average arrival rate. • Queue management: – Queue full: head+2=tail (2 slots used/sample) – Queue empty: head=tail • Advantage: queue decouples the data arrival rate (possibly bursty) from the data processing rate.16: Discussion • Key idea: use circular queues. Figure 4.

©J Archibald 425 F10 3:40 . • Reversing the operation would allow ISR to overwrite the data before it is read. – When tail is incremented. • The operation is generally atomic. • Overall assessment: approach makes sense only if disabling interrupts is really not an option. but not on all platforms. reader and writer could see different pictures of shared array. Figure 4. • If it is not. the write (not necessarily the increment) to tail must be atomic.16: Discussion • How fragile is this code? How easy to get it wrong? – Task must read the data first and revise pointers second.

disable(). !! adjust for daylight savings time also if (iHours >= 24) iHours = 0. int iHoursTemp. Problem 4. if (iMinutes >= 60) disable(). ++iMinutes. !! adjust iHoursTemp for new time zone ++iHours. void interrupt vUpdateTime (void) int iZoneNew) { { ++iSeconds.1: Does this approach avoid a shared data problem? static int iSeconds. } } Code based on Figure 4. iMinutes. { iMinutes = 0. if (iSeconds >= 60) { /* Get current hours */ iSeconds = 0. void vSetTimeZone (int iZoneOld. } iHours = iHoursTemp. iHoursTemp = iHours. /* save the new hours value */ } disable(). iHours. !! Deal with HW enable().17 ©J Archibald 425 F10 3:41 .

} ©J Archibald 425 F10 3:42 . ++lSecondsToday.. static long int lSecondsToday.. (a) How far off can results of function void interrupt vUpdateTime (void) call be if sizeof(long) is 32 and word { size is 16 bits? .Problem 4... if (lSecondsToday == 60 * 60 * 24) lSecondsToday = 0L. .2: The code below has a shared data bug. } (b) How far off can results of function long lSecondsSinceMidnight(void) call be if sizeof(long) is 32 and word { size is 8 bits? return (lSecondsToday).

. ++lSecondsToday.. even if registers are 32 bits in length? static long int lSecondsToday.Problem 4.3: What additional bug lurks in this code.. than timer interrupt for vUpdateTime .. } ©J Archibald 425 F10 3:43 . void interrupt vUpdateTime (void) { . What can happen if system has if (lSecondsToday == 60 * 60 * 24) another interrupt that is higher priority lSecondsToday = 0L. and that calls lSecondsSinceMidnight? } long lSecondsSinceMidnight(void) { return (lSecondsToday).

void interrupt vReadTemperatures (void) { if (fTaskCodeUsingTempsB) { iTemperaturesA[0] = !! read in value from HW iTemperaturesA[1] = !! read in value from HW } Is the task’s use of this variable else atomic? { iTemperaturesB[0] = !! read in value from HW Does it need to be atomic for the iTemperaturesB[1] = !! read in value from HW } code to work correctly? } void main (void) { while (TRUE) { if (fTaskCodeUsingTempsB) if (iTemperaturesB[0] != iTemperaturesB[1]) !! Set off howling alarm. iTemperaturesB[2]. fTaskCodeUsingTempsB = !fTaskCodeUsingTempsB. static BOOL fTaskCodeUsingTempsB = FALSE. } } ©J Archibald 425 F10 3:44 .Problem 4. static int iTemperaturesA[2]. else if (iTemperaturesA[0] != iTemperaturesA[1]) !! Set off howling alarm.5: The task and interrupt code below share the fTaskCodeUsingTempsB variable.

} void SinkTask(void) { int iValue.18 ©J Archibald 425 F10 3:45 .6: where is “very nasty bug”? int iQueue[100]. ++iTail. !! Do something with iValue. if (iHead==100) iHead==0. if (iTail == 100) iTail = 0. ++iHead. } iQueue[iHead]= !!next value. while(TRUE) if (iTail != iHead) { /* if queue has entry. process it */ iValue = iQueue[iTail]. overwrite oldest */ ++iTail. Problem 4. } } Code from Figure 4. /* place to add next item */ int iTail = 0. if (iTail == 100) iTail = 0. /* place to read next item */ void interrupt SourceInterrupt(void) { if ((iHead+1 == Tail) || (iHead == 99 && iTail == 0)) { /* if queue is full. int iHead = 0.

} Scenario 1. iHead=20. process it */ iValue = iQueue[iTail].6: where is “very nasty bug”? int iQueue[100]. } iQueue[iHead]= !!next value. overwrite oldest */ ++iTail. value while(TRUE) 21 already in register if (iTail != iHead) Interrupt occurs: iHead=21.iTail=21 { int iValue. Task reads iQueue[21] which is newest ++iTail. !! Do something with iValue. /* place to read next item */ void interrupt SourceInterrupt(void) { if ((iHead+1 == Tail) || (iHead == 99 && iTail == 0)) { /* if queue is full. /* place to add next item */ int iTail = 0. Task about to read iQueue[iTail]. Problem 4.18 ©J Archibald 425 F10 3:46 . } } Code from Figure 4. if (iTail == 100) iTail = 0. if (iHead==100) iHead==0. say.iTail=22 { /* if queue has entry. int iHead = 0. ++iHead. (rather than oldest) entry if (iTail == 100) iTail = 0. void SinkTask(void) Queue is full.

iTail=100 { /* if queue has entry. } iQueue[iHead]= !!next value. iHead=98. } Scenario 2.18 ©J Archibald 425 F10 3:47 .6: where is “very nasty bug”? int iQueue[100]. End of second: iHead=0. overwrite oldest */ ++iTail. if (iTail == 100) iTail = 0. ++iHead.iTail=99 { int iValue. if (iHead==100) iHead==0. Task executes ++iTail (so iTail=100) while(TRUE) Back-to-back interrupts are executed. increases w/o limit } } Code from Figure 4. if (iTail != iHead) Start of first: iHead=98. void SinkTask(void) Queue is full. /* place to read next item */ void interrupt SourceInterrupt(void) { if ((iHead+1 == Tail) || (iHead == 99 && iTail == 0)) { /* if queue is full. process it */ iValue = iQueue[iTail]. End of first: iHead=99. !! Do something with iValue. /* place to add next item */ int iTail = 0. iTail=100. int iHead = 0. Problem 4. ++iTail. iTail=101 if (iTail == 100) iTail = 0. iTail is never reset.

Chapter 5: Software architectures • Recap: important ideas in real-time code – ISRs: scheduled by hardware – Task code: scheduled by software • Similar to Linux “process” in this regard – Response time constraints – Simplicity vs. how should code be organized? • What alternatives exist? ©J Archibald 425 F10 3:48 . complexity • For any given application.

Key factors in choosing a software architecture • How much control you need over system response time – Absolute response time requirements – Other processing requirements. including lengthy computations • How many different events you must respond to – Each with possibly different • Deadlines • Priorities • In short: what does the system need to do? ©J Archibald 425 F10 3:49 .

how the event is detected. and 2. Events Architecture Handlers ©J Archibald 425 F10 3:50 . • The software architecture determines 1. how the event handler is called. Software architectures • Event handlers are procedures (typically written in C) that do the “work” to respond to events.

. } if (event2) handle_event2(). } This approach is typically called polling.. Architecture 1: Round-robin No interrupts involved One Event Multiple Events while(1) while(1) { { if (event) if (event1) handle_event().. ©J Archibald 425 F10 3:51 . if (eventn) handle_eventn(). handle_event1().

• Advantage: – Simplicity: really just a single task. no shared data. each handler must wait its turn. Characteristics of round-robin • Priorities available: – None: actions are all equal. no ISRs ©J Archibald 425 F10 3:52 . – Worst-case response time for every event is bad if any single event requires lengthy processing. • Disadvantages: – Worst-case response time one full iteration of loop (possibly handling all other events first). – System is fragile: adding a single new event handler may cause deadlines to be missed for other events.

handle_eventA(). response time for event A? } ©J Archibald 425 F10 3:53 . if (eventD) if (eventC) handle_eventD(). How to decrease response time? while(1) while(1) { { if (eventA) if (eventA) handle_eventA(). } if (eventA) handle_eventA(). handle_eventA(). if (eventC) if (eventA) handle_eventC(). if (eventD) How can I reduce the handle_eventD(). handle_eventC(). if (eventB) if (eventB) handle_eventB(). handle_eventB().

a round-robin architecture is probably suitable only for very simple devices such as digital watches and microwave ovens and possibly not even for these. Applicability of round-robin • Example from text: digital multimeter – Few input devices. few events to respond to – Response time constraints not demanding – No lengthy processing required • Author’s conclusion (page 119): “Because of these shortcomings.” ©J Archibald 425 F10 3:54 .

– ISRs complete initial response. Architecture 2: Round-robin with interrupts • To single polling loop. • Offers greater flexibility: – Time-critical response can be addressed in ISR. ©J Archibald 425 F10 3:55 . add interrupts. – Longer-running code can be placed in handlers. – ISR sets flag to indicate that processing is required. – Remainder done by functions called in loop.

handle_eventC(). flagC = 1. handle_eventB(). } } } Work split between ISR and task code. } ISR_C { if (flagC){ !! do some C stuff flagC = 0. } ISR_B { if (flagB){ !! do some B stuff flagB = 0. } handle_eventA(). } flagB = 1. Round-robin with interrupts while(1) ISR_A { { !! do some A stuff if (flagA) { flagA = 1. ©J Archibald 425 F10 3:56 . flagA = 0.

Example: communications bridge What is time critical? • Not losing data Constraints • Maintaining good throughput Assume interrupts occur: • When data arrives • When link clear to send decrypt Communication Link B (encrypted) Communication encrypt Link A ISR actions: • Buffer data on arrival • Set flag when clear to send Design Operations within main loop: • Encrypt buffered data from Link A • Decrypt buffered data from Link B • Send data on Link A • Send data on Link B ©J Archibald 425 F10 3:57 .

• Disadvantages: – ISRs and handlers will share data. – ISR response time stable through most code changes. • Worst-case response time – For ISR: execution time of higher priority ISRs – For handler: sum of execution of all other handlers + interrupts • Advantages: – Work performed in ISRs has higher priority. – All handlers have equal priority: none more important than rest. Characteristics of round-robin with interrupts • Priorities available: – Interrupts are serviced in priority order. shared data problems will appear! – Handler response time not stable when code changes. ©J Archibald 425 F10 3:58 .

ISR_B Order of tasks { !! do some work relating to B is dynamic. (*task). /* = task() */ } ©J Archibald 425 F10 3:59 . while(queue_empty()) /* wait */. } Queue can be while(1) FIFO or sorted { by priority. queue_put(handle_eventB). between ISR } and task code. task = get_queue(). Architecture 3: Function-queue scheduling ISR_A { !! do some work relating to A Work split queue_put(handle_eventA).

have to wait – Delay = longest task time + execution time for ISRs • Advantages: – Improved response-time stability when code changes • Disadvantages: – Increased complexity: must implement a function queue ©J Archibald 425 F10 3:60 . Characteristics of Function-queue scheduling • Priorities available: – Interrupts are serviced in priority order – Tasks can be placed in queue and run in priority order • Worst-case response time for highest-priority task – Scenario: just started executing another task.

• Tasks block when waiting for events. • RTOS contains code to – Create tasks. – ISRs can cause tasks to become unblocked. – Highest-priority task is always the “running” task. – Tasks can delay themselves for fixed time intervals. – If higher-priority task becomes ready. resources. Architecture 4: Real-time operating system (RTOS) • Work is split between ISRs and tasks. ©J Archibald 425 F10 3:61 . schedule tasks. • Tasks are prioritized and run by a scheduler. lower-priority task is preempted. allow tasks and ISRs to communicate. block and unblock tasks. etc.

. ©J Archibald 425 F10 3:62 . . . RTOS architecture ISR for TaskA Event 1 ISR for TaskB RTOS Event 2 ISR for TaskC Event 3 . . .

other tasks preempted • Advantages: – Stability when code changes: adding a lower-priority task will not affect response time of tasks with higher priorities. some in using it correctly) – Runtime overhead of RTOS ©J Archibald 425 F10 3:63 . • Disadvantages: – Software complexity (much of it is in RTOS. – Many choices of commercial RTOS available. RTOS characteristics • Priorities available – Interrupts are serviced in priority order – Tasks are scheduled in priority order – Lower priority tasks are preempted • Worst-case response time for highest-priority task – Sum of ISR execution times.

debugging support. lean toward using an RTOS: • Many to choose from. 3. Consider constructing hybrid architecture – examples: • RTOS where one task does polling • Round robin with interrupts: main loop polls slower HW directly ©J Archibald 425 F10 3:64 . Selecting an architecture 1. 2. Select the simplest architecture that will meet current and future response time requirements. etc. If application has difficult response-time requirements.