3
\$\begingroup\$

Are there any available guidelines for the amount of code to have in a critical section of interrupt driven processing?

My personal rule of thumb is that the critical portion, (i.e. that between disable interrupts and enable interrupts), of any interrupt driven processing should be no more than about a dozen lines of code, (including those in any function/library/macro called), and that the processing should be as linear as possible.

I would expect something along the lines of:

disable_interrupts
if error_condition:
   set_error_flag
else:
   small_data_transfer
   set_ready_flag
enable_interrupts

However I have been asked to look into an existing project where some of the interrupts have handlers where the call charts will not fit on a sheet of A1 (all in the critical section).

To clarify this is a "bare metal" project with no OS/Scheduler implemented - and all of the interrupt handlers start by disabling all interrupts - i.e. the original authors regarded every operation in every ISR as critical, (even the housekeeping is all inside of a disable/enable section). There are a multitude of interrupts and hardware dependencies but as far as I can see some of the ISRs will be executing thousands of lines within the "critical" section.

I know that this is a problem and really needs to be completely rewritten but I can not find any standards, including MISRA, that I can point to rather than just saying "in my experience" - to convince the project management I need to point to some accepted standards.

So hence my question - Can anybody point me towards any standards or guidelines that I can use to back up my experience? (Or of course am I totally wrong).

\$\endgroup\$
12
  • \$\begingroup\$ Do you only have a single interrupt priority? I know I've done a few projects where the entire application lived in the low-priority interrupt handler. It basically was acting like a interrupt-triggered main-loop. Assuming you have sufficient interrupt priority levels, I don't see why that type of structure is an issue. \$\endgroup\$ Apr 10, 2014 at 10:53
  • \$\begingroup\$ Another comment would be that I've written some protocol decoders that were quite large, but the actual execution path through the decoder per-interrupt was short. A call-path chart for that would be disproportionately huge, I think, and rather mis-represent the actual interrupt efficiency. \$\endgroup\$ Apr 10, 2014 at 10:56
  • 1
    \$\begingroup\$ Your terminology is confusing. On one hand, you seem to be talking primarily about the ISR (interrupt service routine). But you also keep mentioning "critical section", which is normally a term reserved for those sections of non-ISR code during which interrupts are disabled, preventing the ISR from running. Can you clarify your question a bit? \$\endgroup\$
    – Dave Tweed
    Apr 10, 2014 at 11:04
  • 1
    \$\begingroup\$ So you have a system that allows nested interrupts, and it sounds like the designers of the existing code did not plan this out very carefully. Sounds like a mess. If the critical sections in low-priority interrupts are long, then the response of the system to the presumably more important high-priority interrupts will be delayed. I would be tempted to set the existing code aside and come up with my own high-level architecture for the overall system based only on the external requirements. Only then would I go back to the existing code to see which parts might be reusable. \$\endgroup\$
    – Dave Tweed
    Apr 10, 2014 at 11:53
  • 2
    \$\begingroup\$ Anything you get off the web, including this site, is going to be viewed as "anecdotal" by management. The only way to get the hard data that they need is to stress-test the existing implementation to demonstrate the failures. \$\endgroup\$
    – Dave Tweed
    Apr 10, 2014 at 12:47

4 Answers 4

3
\$\begingroup\$

In general, the distribution of CPU time between ISR code and non-ISR code is very application-dependent.

If most of the work is being done in non-ISR code, then yes, you generally want to keep the ISRs as short as possible (but no shorter, to paraphrase a famous quote).

However, I have seen (and built) hard real-time DSP applications in which 90% of the work is done in the ISR, and the non-ISR code is only handling tasks that are not time-critical such as configuration, status and user interface.

Either approach is perfectly valid in deeply-embedded systems.

\$\endgroup\$
2
\$\begingroup\$

The general guideline is that critical sections should be as short as possible.

What you really need to do is account for the length of the critical section when you verify the schedulability of the task set. Doing that depends on how you are scheduling the tasks, but I think it could be as simple as adding the execution time of the critical section to the execution time of every lower-priority task.

\$\endgroup\$
5
  • \$\begingroup\$ Unfortunately this is a bare metal project, no OS/Scheduler and no priority handling system - everything is in critical sections - even housekeeping! \$\endgroup\$ Apr 10, 2014 at 10:51
  • \$\begingroup\$ You don't need an OS or scheduler or priority handling system to perform schedulability analysis, it can be done on any set of tasks that use preemption. Maybe the question you need to ask about the length of the critical sections is...who cares? Does anything break if the critical sections are too long? \$\endgroup\$
    – Joe Hass
    Apr 10, 2014 at 10:59
  • \$\begingroup\$ Yes Joe - long lockouts to break things since the other interrupts are getting locked out, (even during housekeeping), they are being ignored and messages are getting corrupted/lost. \$\endgroup\$ Apr 10, 2014 at 16:50
  • \$\begingroup\$ Then what you need is a schedulability analysis! You need to make sure that all tasks will respond in the desired time. \$\endgroup\$
    – Joe Hass
    Apr 10, 2014 at 17:27
  • \$\begingroup\$ And that of course is one of the problems with long complex ISRs - that it is often impractical to perform such analysis in a reasonable time scale because of the complexity. \$\endgroup\$ Apr 10, 2014 at 17:49
2
\$\begingroup\$

I think you have to consider the execution time of the uninterruptible code wrt the fastest response time (latency) and variability of that time (jitter) you need from the microcontroller. It may not be intuitive what that is-- it may be something that is 'urgent' but not 'important'.

For example, a controller I designed some years back needed to deal with RS-232 inputs, to update the output using the control algorithm, to control the dual-slope ADC, to scan an LED display and keyboard (including debouncing), write to a slow EEPROM, and to control an electromagnetic beeper (and probably I couple of other things I've forgotten). Turned out the most critical response time was the beeper periodic interrupt (every 250usec), followed by the LED display, mostly for cosmetic reasons. So if 10% jitter in the beeper toggle was acceptable, that's 25usec of uninterruptable code (which wasn't many instructions, but enough to make certain things atomic). That particular processor supported nested interrupts, so IIRC I ended up making the periodic interrupt code re-entrant after the first section.

If you're deferring pending interrupts during long sections of code, it may be just fine, if your requirements for latency and jitter are fairly relaxed in relation to the execution time of the "critical portions".

My usual approach is to minimize the code execution time in ISRs and to get back as quickly as possible (perhaps stuffing things like debounced input event tokens into circular queues, set flags and get out). For example, if a complex control algorithm needs to be executed every 200msec with maximum 20msec jitter, I'd set a flag every 200msec in the timer interrupt and poll that flag outside the ISR.

Aside from meeting design requirements, reliability is enhanced by this approach.

As far as finding some kind of guideline that states this.. I suggest looking at some of the safety-critical software guidelines. You may not find it stated directly, but you may be able to infer it from other statements. UL1998 IEC 60730 etc. This FAA document states:

Interrupts and their effect on data must receive special attention in safety-critical areas. Analysis should verify that interrupts and interrupt handling routines do not alter critical data items used by other routines.

and

Of particular concern to safety is ensuring the integrity of safety critical data against being inadvertently altered or overwritten. For example, check to see if interrupt processing is interfering with safety critical data.

Both of which become more difficult as the size of the interrupt code increases.

Short (in relation to maximum jitter and latency) frequent interrupts can be analyzed as an effective decrease in processor speed rather than having to deal with worst-case stack-ups of long interrupts at the most critical times, which argues for fast execution as well as simple code within ISRs.

\$\endgroup\$
1
\$\begingroup\$

To me, avoiding nesting interrupts does not seem too bad or even absurd. In fact, nested interrupts may cause more problems than they solve.

I'm specifically referring to memory used to save/restore context when entering/exiting the ISR. Commonly on simple architectures each ISR will save all required context of the code it interrupted, for example on the processor's stack. When developing the software one needs to account for this memory.

If, for example, ISR A requires 10 bytes of context to be saved/restored, ISR B needs 15 and ISR C needs 12 bytes the calculation/estimation simply is:

Anywhere during the execution of non-critical sections any of the ISRs may be triggered. Now, if the ISRs are not allowed to be nested, the maximum memory required for the context is max( 10, 15, 12 ) = 15 bytes. If each of the ISRs may interrupt any other of those 3 ISRs, the worst case will be 10 + 15 + 12 = 37 bytes of context which need to be stored at the same time. So at every instant during execution (of non-atomic code), there must be at least enough memory available for those 37 bytes of context, vs. 15 in the non-nesting case.

Of course, the more ISRs join the "nesting game" the more the issue becomes visible.

If you have multiple tasks running concurrently and ISRs save context to the current stack of whatever code they're interrupting, you have to account for one worst-case context size per task.

This argument of course depends on the HW architecture you're using. It may not be applicable if the HW/Interrupt Controller is more advanced and has special features to handle context switching to/from ISRs.

Apart from that, complex ISRs tend to require more context data to be saved/restored than simple ones, so that small, concise ISRs are usually recommended because they provide multiple benefits:

  1. quick execution time, possibly yielding
    1. shorter critical sections and thus
    2. less jitter
  2. smaller context, resulting in
    1. less worst-case memory usage and (architecture-dependent)
    2. even less execution time (by not having to transfer as much state information from A to B to A)

In the end, if all ISRs are reduced to their minimum, like when only setting a flag, the question of whether or not to allow nesting ISRs may be answered clearly in favour of non-nesting because the benefit of nesting ISRs vanishes.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.