Beyond volatile (C & C++)

作者: IAR Systems, The Code to Success(具体未知)

or How to save days of debugging time 

        When debugging embedded applications, intermittent problems are among the more difficult ones. This 
text discusses one source of such problems: variables that are accessed asynchronously, e.g., by 
interrupt routines or by code running in separate threads. 
        Such variables must be properly defined and have adequate protection. The definition must include the volatile keyword—this informs the compiler that the variable can be changed from other places than the currently executing thread. The compiler will then avoid certain optimizations on this variable, obeying the accesses to the variable as given in the source code and making sure the variable is stable at each "sequence point". Thus the compiler will not rely on knowledge of values from previous  accesses to reuse the same register, move operations across sequence points and so on. 
        To protect a shared variable, each access to the variable must also be made atomic from other accesses. Reading from or writing to a variable in C is not guaranteed to be an atomic operation as such. Accessing a 32-bit variable on an 8-bit architecture would typically be non-atomic, as would any object read or write that requires more than one instruction. Even if you inspect the compiler generated code and find it to be OK, it is not certain that the next compilation will give the same result if you have  made changes to the code or to the compiler settings. 
C Example 
         A typical example of a shared variable is when an interrupt routine updates a time-keeping variable that is read from main code. If the Interrupt Service Routine (ISR) runs with interrupts disabled (which often is the case), the interrupt function is atomic. In other words, it will not be interrupted and thus this access  should not need further protection.  Note that for the below ISR you will also need to enable and initialize the interrupt, but that is not of interest for this example. 
volatile long tick_count = 0; 
  
__interrupt void tick_timer () 
{ 
    tick_count++; 
} 
The main code on the other hand must prevent the interrupt from happening while the variable is being accessed. A simplified unsafe example would be: 
... 
if (tick_count >= next_stop) /* Unsafe! */ 
{ 
     next_stop += 100; 
     do_stuff(); 
}     
This code is unsafe as reading tick_count is not guaranteed to be an atomic operation. A tick_timerinterrupt may occur when we have read one or a few  bytes of  tick_count. Then  tick_count is modified in the ISR and when we resume reading tick_count, we read the rest of tick_count from the updated value. The value we compare could be wrong; it would be a mix of the previous and the new value. In many cases mixing old and new values does no real harm, you get either the old or the new value, but in others you get something completely different altogether. For example, if the previous value of  tick_count  is  0x00FF FFFF and the updated value is  0x0100 0000 it is easy to see how the resulting value can be completely wrong. To protect the access of tick_count, we can use a monitor function: 
__monitor long get_tick_count () 
{ 
    return tick_count; 
} 
... 
if (get_tick_count() >= next_stop) 
{ 
    next_stop += 100; 
    do_stuff(); 
} 
A monitor function is a function that disables interrupts while it is executing, and then restores the  previous interrupt state at function exit. 
A shorter monitor or A taste of C++ 
        A monitor function has the disadvantage that interrupts are turned off for the full duration of the function. Sometimes it would be nice to be able to turn off interrupts in the middle of a function. This can often be done using intrinsic functions; the minor disadvantage is that you will have to remember to put in a line of code to turn off interrupts, and one at the end of the section to enable it. In C++, you can embed this logic in a class—using IAR Embedded Workbench for AVR as an example, you can add the following class: 
extern "C" 
{ 
    #include <inavr.h> 
} 
  
class Mutex 
{ 
public: 
   Mutex () 
   { 
      _state = __save_interrupt(); 
      __disable_interrupt(); 
   } 
  
   ~Mutex () 
   { 
      __restore_interrupt(_state); 
   } 
  
private: 
   unsigned char _state; 
};  
To use this Mutex class in the previous example, you can use the below for main code:   
long tick; 
{ 
    Mutex m; 
    tick = tick_count; 
} 

if (tick >= next_stop) 
{ 
    next_stop += 100; 
    do_stuff(); 
} 
The constructor of m will be inserted first in the block, it will save previous interrupt state and disable interrupts. The body is then executed (reading  tick_count) and finally the destructor will restore the  previous state. 
Comments 

        There are numerous variations on the details of implementation, but the general principles are the same. 

        For C, if you use a compiler that does not have a monitor keyword, you would need to implement equivalent functionality in some other way, possibly using intrinsic functions similar to what is done in the Mutex class above. 

        With IAR Embedded Workbench for ARM you might use __get_CPSR() to find out the old state of the interrupts, with IAR Embedded Workbench for MSP430 you might use __get_interrupt_state(). 
        In some cases you may assume the previous interrupt status is known, i.e. just do enable and disable. 
        If you are using an operating system, it will provide a number of services that minimizes the need for you to disable and enable interrupts as above. For one thing, operating systems typically provide some kind of system clock where you only should use the appropriate system calls, not manipulate implementation details. Another thing to note is that most operating systems provide semaphores to protect common 
resources. 
Conclusion 
        The key topic of this text is to ensure that variables accessed asynchronously are protected properly,and that volatile is not always enough. 

        The general solution is to ensure that certain operations cannot be interrupted. Some ways to achieve this in C and in C++ are described, giving C and C++ code fragments for IAR Embedded Workbench for AVR as well as names of relevant intrinsic functions for IAR Embedded Workbench for ARM and IAR Embedded Workbench for MSP430. 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值