8
\$\begingroup\$

Inside the driver I have got a function to copy the data from the internal struct into a struct from the application.

Can this process be interrupted by a microcontroller interrupt trigger?

uint16_t getRawData(struct Data *Data_external)
{
  if(Data_external == NULL)
  {
    return ERR_PARA;
  }
  else
  {
    *Data_external = Data_internal;            // the copy process. Could this be interrupted? 
  }
  return ERR_NONE;
}
\$\endgroup\$
  • 17
    \$\begingroup\$ There isn't really a "copy process", there will be instructions copying bytes around and if they are not atomic they can be interrupted, depends on your architecture, compiler, settings and alignment \$\endgroup\$ – PlasmaHH Sep 6 '18 at 9:28
  • 10
    \$\begingroup\$ There is no guarantee in C that a structure copying process cannot be interrupted. However, the interrupt is usually supposed to return to the point in the code where the copying process can complete. So it probably doesn't matter unless the interrupt code depends upon it for some reason (unlikely in most circumstances.) If these structures are part of a upper/lower interrupt driver pair, though, then this could be a problem if the upper level is in the process of removing or adding a "chunk" to a buffer when an interrupt takes place that must also cope with "chunks" in the same buffer. \$\endgroup\$ – jonk Sep 6 '18 at 9:35
  • 4
    \$\begingroup\$ @jonk: Please do not answer the question in comments, as this bypasses the normal review process for answers, as discussed in meta \$\endgroup\$ – Dave Tweed Sep 6 '18 at 18:50
  • 6
    \$\begingroup\$ @DaveTweed Then you and this site will lose my comments. I don't write answers unless I have the time to write fuller ones. It's my only way of operation and I won't change it. If I feel I have enough time for a "proper answer" by my definition of it, I will continue as before. But I'm working very hard on several different active projects and do NOT have the time for my usual level of answer context and content. So either I write short comments as comments, or the site loses any access to my time. Your call. My time is given on my terms, or not at all. \$\endgroup\$ – jonk Sep 6 '18 at 18:57
  • 4
    \$\begingroup\$ @jonk: There's nothing wrong with writing a short answer as long as it is complete and self-contained, like your comment is. Your commitment to lengthy answers is entirely on you. The site has rules and processes, and I'm just trying to nudge you in the right direction. Ultimately, it's entirely up to you -- you could have had 9 upvotes (+90 rep) on your answer... \$\endgroup\$ – Dave Tweed Sep 6 '18 at 20:08
25
\$\begingroup\$

Yes. Pretty much everything in an MCU can be interrupted by an interrupt request. When the interrupt handler completes the previous code will just continue so it is usually not a problem.

In a special case the interrupt handlers can be interrupted themselves by interrupts of higher priorities (nested interrupts).

If a series of instructions must not be interrupted then you need to implement a critical section (basically globally disable interrupts, do the job, enable again).

Remember that depending on architecture of the target CPU a single line of C can be compiled to many assembly instructions. A simple i++ on an AVR is compiled to multiple instructions if i is for example uint32_t.

\$\endgroup\$
  • 1
    \$\begingroup\$ Regarding your last paragraph, I assume that's only true on 16/8 bit systems, right? I know you're saying AVR.. just making sure. \$\endgroup\$ – Harry Svensson Sep 6 '18 at 11:34
  • 1
    \$\begingroup\$ memcpy of a struct (like in the question) is multiple instructions on 32-bit ARM too. Incrementation of uint64_t on 32-bit ARM is also not atomic. Bitwise operations are also read-modify-write (unless bit-banding is used). \$\endgroup\$ – filo Sep 6 '18 at 11:36
  • 4
    \$\begingroup\$ @HarrySvensson no - on any RMW architecture and most of the uCs are Read - Modify - Write ones (mostof the RISC procesoors are RMW) . So i++ will generate in many cases (except if the value is just kept in the register which is not the case here) three instructions on the modern 32/64 bits ARM uCs as well. \$\endgroup\$ – P__J__ Sep 6 '18 at 11:38
  • \$\begingroup\$ Ah, there's been a slight misunderstanding here. I was thinking about taking care of the carry when performing increments (overflow), and both of you are talking about retrieving from memory and writing back. - Oh well. \$\endgroup\$ – Harry Svensson Sep 6 '18 at 11:48
  • 1
    \$\begingroup\$ In general: all operations on types wider than the data bus width are "funky". \$\endgroup\$ – filo Sep 6 '18 at 11:50
11
\$\begingroup\$

Any operation that is not atomic can be interfered with by an interrupt. This kind of programming is often very different than most other programming and can be confusing to people who haven't studied processor design or computer architecture.

You may think to yourself "This will never actually happen, how long does this code take to copy and how likely is an interrupt?" But with most production embedded applications it will happen because the product is on for years without updates.

The other issue with struct copies like this is when they do happen they are extraordinarily difficult to debug because they only happen when the interrupt occurs at just the right time (which can be as little as one cycle).

\$\endgroup\$
10
\$\begingroup\$

The whole point of interrupts is that they can (and do) happen all the time, and are designed to have zero impact on any code that happens to be running when they occur. All the registers are saved, and depending on the CPU architecture, a completely different register set may be swapped in, the interrupt does its thing, and then the original registers are restored and the code continues to run as normal.

Problems can occur when the interrupt service routine itself tries to access memory that is being accessed by the running, interrupted code. Even more subtle errors can occur when a time-critical I/O process is interrupted. These problems are commonplace with older, simpler, less secure architectures where there may be little separation between "user" and "supervisor/kernel" mode code.

This kind of problem can be hard to identify, and often difficult to reproduce, but once identified they're often fairly trivial to fix using defensive programming, mutexes/semaphores or simply by disabling interrupts in critical sections of code.

The general class of problems has been studied extensively, and modern multi-core CPUs and even multi-tasking operating systems would not be possible if multiple solutions were not already tried and tested.

\$\endgroup\$
5
\$\begingroup\$

I'm just going to go ahead and assume you asked this for a very good reason.

*Data_external = Data_internal;

Can be split (barring some edge cases that are unlikely to be in play here).

I don't know your CPU but I haven't seen a CPU yet that can't do the moral equivalent of:

cli(); /* mask all interrupts */
*Data_external = Data_internal;
sti(); /* restore interrupt mask */

Now it can't be split on any single core CPU because nothing can interrupt while interrupts are off. Whether or not this is a good idea depends on a lot of things that I'm simply not qualified to evaluate.

If you are multi-core (and I finally remembered there is a multi-core embedded CPU on the market) don't do this. It is worthless. You would need to develop proper locking.

\$\endgroup\$
  • \$\begingroup\$ What are cli and sti? from google they appear to be x86 instructions? \$\endgroup\$ – Sam Sep 7 '18 at 2:41
  • \$\begingroup\$ @Sam: Well yes, those are the names of the instructions in x86. I think they have the same names in the PDP-11 because some code that originated there had the same function names for a logical equivalent in usermode (mask inter-process signals). Here's instructions for Arduino: tldp.org/LDP/tlk/dd/interrupts.html \$\endgroup\$ – Joshua Sep 7 '18 at 3:25
  • 3
    \$\begingroup\$ Normally you would disable interrupts, but only reenable them if they were actually enabled. Otherwise your code has the side-effrct of always enabling interrupts, even if they were disabled before the code was called. \$\endgroup\$ – Tom Carpenter Sep 7 '18 at 13:15
3
\$\begingroup\$

The code as you presented it indeed can be interrupted. However, before you start making critical sections all over the place, you should check a few things:

  • You say this function is "inside a driver". Are interrupts already disabled when this function is called? Or is it called inside an interrupt handler which prevents other interrupts from triggering? If yes, the operation cannot in fact be interrupted.

  • Is Data_internal ever accessed inside an interrupt handler? If not, there is no harm even if the operation can be interrupted.

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

[Not enough rep to comment]

Another gotchya with that kind of struct copy is that it is a shallow copy. You may need a deep copy instead.

A shallow copy could possibly but not likely be atomic, depending on machine architecture. A deep copy is almost certainly not atomic on any architecture.

\$\endgroup\$
  • \$\begingroup\$ A fair point; but a deep copy is more likely to be atomic because it's almost always put in place by a pointer swap. Yet in this case, if it should have been a deep copy it will definitely not be atomic. \$\endgroup\$ – Joshua Sep 7 '18 at 23:21
0
\$\begingroup\$

A quality implementation suitable for embedded-systems use will document how volatile-qualified reads or writes of various types will be performed in sufficient detail to indicate whether and how they may be "split" by interrupts, and also whether or not volatile reads and writes are sequenced with regard to non-qualified reads and writes. While some implementations may behave as though all reads and writes are volatile-qualified, implementations are generally expected to be free to process sequences of non-qualified reads and writes in whatever fashion would be most efficient when there are no intervening volatile accesses.

On a typical 32-bit microcontroller, reads and writes of volatile-qualified integer types that are 32 bits and smaller; an assignment will consist of an atomic read followed by an atomic write. If you want to ensure that a 32-bit structure is copied atomically, place it in a union with a uint32_t, and read or write that member to read or write the structure as a whole. Quality implementations that are configured to be suitable for embedded systems use will allow unions to be employed in such fashion without regard for whether the Standard would require implementations that aren't intended for such use to do likewise. Note that gcc and clang will not reliably behave as quality implementations suitable for embedded systems use unless various optimizations are disabled.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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