By Jack Ganssle

Disabling Interrupts

Summary: Accessing shared resources is harder than one thinks.

I wish we lived in an atomic world.

No, I am not yearning for an Iranian bomb. Rather, unavoidable non-atomic accesses to shared resources causes much grief and poor code.

I read a lot of code, and find much that handles non-atomic accesses in this manner:

long global_var;
void do_something(void)
{
//  Handle a non-atomic access to "global_var"
#pragma disable interrupts somehow
// Do something non-atomic to global_var
#pragma enable interrupts somehow
}

This construct suffers from a number of problems, not the least of which is that it's not generally reuseable. If the function is called from some place with interrupts off, it returns with them on, disrupting the system's context.

Usual solutions involve saving and restoring the interrupt state. But that, too, is fraught with peril. Optimizers are very aggressive today, and in some cases can reorder statement execution to the point where interrupts aren't disabled at the right point. The result: all that atomic-work may not work.

I've asked several compiler vendors for their take, since they understand the optimizations the compilers do better than anyone. The most interesting and complete response came from Greg Davis of Green Hills Software, and he has graciously allowed me to reprint it here: What we recommend for Green Hills customers is to use intrinsic functions for disabling and restoring interrupts. What this looks like is:

#include 
int global_var;
void foo(void)
{
    //  Disable interrupts and return "key"
    // that expresses current interrupt state
    unsigned int key = __DIR();

    // Code that handles global_var in a non-atomic way

    // Restore interrupts to state expressed by "key"
    __RIR(key);
}
These Green Hills intrinsics for __DIR() and __RIR() generate different assembly code depending on the architecture and CPU that you are compiling for, but their interface is the same. The compiler considers the system-instructions that these intrinsics generate to be non-swappable, so the code that manipulates global_var will not be swapped across them. With GNU, people tend to prefer to use inline assembly. These assembly statements are typically embedded in inline functions or macros with GNU statement expressions. An implementation of something like this on an ARM7TDMI might look like:
static inline unsigned int disable_interrupts_reentrant(void)
{
    unsigned int ret;
    asm volatile(
        "mrs %0,cpsr\n"
        "orr r1,%0,192\n"
        "msr cpsr_cxsf,r1\n"
        : /* output */ "=r" (ret)
        : /* input */
        : /* clobbers */ "r1", "memory"
        );
    return ret;

 
 
 


static inline void restore_interrupts(unsigned int state)
{
    asm volatile(
        "and r1,%0,192\n"
        "mrs r0,cpsr\n"
        "bic r0,r0,192\n"
        "orr r0,r0,r1\n"
        "msr cpsr_cxsf,r0\n"
        : /* output */
        : /* input */ "r" (state)
        : /* clobbers */ "r0", "r1", "memory"
        );
}

int global_var;
void foo(void)
{
    unsigned int key = disable_interrupts_reentrant();

    // Code that handles global_var in a non-atomic way

    restore_interrupts(key);
}
At least to my understanding, the combination of the declaring the assembly to be volatile and putting the "memory" in the clobbers list should ensure that memory accesses in the critical section stay in the critical section. Both of the above approaches involve compiler-specific extensions. The best approach I'm aware of that isn't compiler specific is to move the code into another file so it just looks like a function call to the compiler:
extern unsigned int disable_interrupts_reentrant(void);
extern void restore_interrupts(unsigned int state);
int global_var;
void foo(void)
{
    unsigned int key = disable_interrupts_reentrant();

    // Code that handles global_var in a non-atomic way

    restore_interrupts(key);
}
.and then to define the functions in a separate assembly file. The exact assembly syntax may vary between implementations, but it may look something like this on a traditional UNIX-style assembler.
    .text
    .globl disable_interrupts_reentrant
disable_interrupts_reentrant:
    ; Inputs: none
    ; Outputs: r0 (return register) contains a key for the
    ; current interrupt state
    mrs r0, cpsr
    orr r1, r0, 192
    msr cpsr_cxsf, r1
    bx lr
    .type disable_interrupts_reentrant,@function
    .size disable_interrupts_reentrant, .-disable_interrupts_reentrant

    .globl restore_interrupts
restore_interrupts:
    ; Inputs: r0: prior interrupt state
    ; Outputs: None
    and r1, r0, 192
    mrs r0, cpsr
    bic r0, r0, 192
    orr r0, r0, r1
    msr cpsr_cxsf,r0
    bx lr
    .type restore_interrupts,@function
    .size restore_interrupts,.-restore_interrupts
Since compilers need to assume that external functions read and write all global variables, there's no chance for the code that handles global_var to fall outside of the critical section.

Thanks, Greg, for the insight. I hope this information is useful to folks.

Published March 16, 2010