Attention to those who modify global variables in interrupt programs - the role of volatile in C

Publisher:Serendipity66Latest update time:2018-12-18 Source: eefocus Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

A variable defined as volatile means that the variable may be changed unexpectedly, so the compiler will not make assumptions about the value of the variable. To be precise, the optimizer must carefully re-read the value of the variable each time it is used, rather than using the copy stored in the register. Here are some examples of volatile variables: 
     

1). Hardware registers of parallel devices (such as status registers) 
    

 2). Non-automatic variables accessed in an interrupt service subroutine 
    

 3). Variables shared by several tasks in a multithreaded application 
    

 Those who cannot answer this question will not be hired. I think this is the most basic question that distinguishes C programmers from embedded system programmers. Embedded system programmers often deal with hardware, interrupts, RTOS, etc., all of which require volatile variables. Not understanding the content of volatile will bring disaster. 
     

Assuming the interviewee answers this question correctly (hmm, doubt it), I would dig a little deeper to see if this guy really understands the importance of volatile. 
    

 1). Can a parameter be both const and volatile? Explain why. 
     

2). Can a pointer be volatile? Explain why. 
     

3). What is wrong with the following function: 


          int square(volatile int *ptr) 
          { 
               return *ptr * *ptr; 
          } 
     

Here are the answers: 
    

 1). Yes. An example is a read-only status register. It is volatile because it can be changed unexpectedly. It is const because the program should not attempt to modify it. 
     

2). Yes. Although this is not very common. An example is when a service routine modifies a pointer to a buffer. 
     

3). There is a trick in this code. The purpose of this code is to return the square of the value pointed to by pointer *ptr. However, since *ptr points to a volatile parameter, the compiler will generate code similar to the following: 


     int square(volatile int *ptr) 
     { 
          int a,b; 
          a = *ptr; 
          b = *ptr; 
          return a * b; 
      } 
     

Since the value of *ptr may be changed unexpectedly, a and b may be different. As a result, this code may not return the square value you expect! The correct code is as follows: 


      long square(volatile int *ptr) 
      { 
             int a; 
             a = *ptr; 
             return a * a; 
      }

Let me share my understanding: (You are welcome to criticize me...~~!)


The key lies in two places:     


1. Compiler optimization (please help me understand the following)


In this thread, when reading a variable, in order to improve access speed, the compiler sometimes reads the variable into a register first during optimization; later, when getting the variable value, it directly gets the value from the register;


When the variable value is changed in this thread, the new value of the variable will be copied to the register at the same time to keep it consistent


When the value of a variable is changed by another thread, the value of the register will not change accordingly, causing the value read by the application to be inconsistent with the actual variable value.


When the register value is changed by another thread, the value of the original variable will not change, causing the value read by the application to be inconsistent with the actual variable value.


Here is an inaccurate example:


When distributing salaries, the accountant always called employees to register their bank card numbers. One time, the accountant did not register immediately to save time and used the previously registered bank card number. It happened that an employee's bank card was lost and the bank card number had been reported lost. As a result, the employee could not receive his salary.


Employee -- Original variable address 

Bank card number - backup of the original variable in the register


2. Under what circumstances will it appear (as mentioned in the first post)


 1). Hardware registers of parallel devices (such as status registers) 
     

2). Non-automatic variables accessed in an interrupt service subroutine 
     

3). Variables shared by several tasks in a multithreaded application 
    

Supplement: Volatile should be interpreted as "directly accessing the original memory address". The interpretation of "volatile" is a bit misleading.


"Volatile" is caused by external factors, such as multithreading, interrupts, etc. It does not mean that the variable modified with volatile is "volatile". If there is no external factor, it will not change even if it is defined with volatile.


After defining it with volatile, the variable will not change due to external factors and can be used with confidence. Let's see if the previous explanation (volatile) is misleading.


-------------A brief example is as follows:------------------


The volatile keyword is a type modifier that indicates that the type variable declared with it can be changed by some unknown factors of the compiler, such as the operating system, hardware, or other threads. When encountering a variable declared with this keyword, the compiler will no longer optimize the code that accesses the variable, thereby providing stable access to special addresses. 
Examples of using this keyword are as follows: 
int volatile nVint; 

>>>>When the value of a variable declared with volatile is requested, the system always re-reads the data from the memory where it is located, even if the previous instruction has just read the data from there. And the read data is saved immediately.

 
For example: 
volatile int i=10; 
int a = i; 
... 
//Other codes do not explicitly tell the compiler that operations have been performed on i 
int b = i; 

>>>>volatile indicates that i may change at any time. Each time it is used, it must be read from the address of i. Therefore, the assembly code generated by the compiler will re-read the data from the address of i and put it in b. The optimization method is that since the compiler finds that the code between the two reads from i has not operated on i, it will automatically put the last read data in b. Instead of re-reading from i. In this way, if i is a register variable or represents a port data, it is easy to make mistakes, so volatile can ensure stable access to special addresses. 

>>>>Note that in vc6, the general debugging mode does not perform code optimization, so the effect of this keyword cannot be seen. The following inserts assembly code to test the impact of the volatile keyword on the final code of the program: 

>>>>First, use classwizard to create a win32 console project, insert a voltest.cpp file, and enter the following code: 
>> 
#include


-------------------------------------------------



The variable corresponding to volatile may change without your program knowing. 
For example, in a multi-threaded program, multiple programs can manipulate the variable in the shared memory. 
Your own program cannot determine when the variable will change. 
For example, it corresponds to a certain state of an external device. When the external device operates, the system changes the value of the variable through the driver and interrupt events, but your program does not know. 
For volatile variables, the system directly extracts them from the corresponding memory every time it uses them, instead of using the original value in the cache to adapt to the unknown changes that may occur. The system will not optimize the processing of such variables - obviously because its value may change at any time.


-------------------------------------------------- ----------------------------------


A typical example 
is for ( int i=0; i<100000; i++); 
This statement is used to test the speed of an empty loop, 
but the compiler will definitely optimize it and not execute it at all. 
If you write 
for ( volatile int i=0; i<100000; i++); 
it will execute.


The original meaning of volatile is "volatile". 
Since register access is faster than RAM, compilers generally optimize to reduce access to external RAM. For example:


static int i=0;


int main(void) 

... 
while (1) 

if (i) dosomething(); 

}


/* Interrupt service routine. */ 
void ISR_2(void) 

i=1; 
}


The original intention of the program is to call the dosomething function in main when the ISR_2 interrupt occurs. However, since the compiler determines that i has not been modified in the main function, it 
may only execute the read operation from i to a certain register once, and then each if judgment only uses the "copy of i" in this register, resulting in dosomething never being 
called. If the variable is modified with volatile, the compiler guarantees that the read and write operations of this variable will not be optimized (definitely executed). The same should be said for i in this example.


Generally speaking, volatile is used in the following places:


1. Variables modified in the interrupt service program for detection by other programs need to be added with volatile;


2. Flags shared between tasks in a multitasking environment should be added with volatile;


3. Memory-mapped hardware registers usually also need to be declared volatile, because each read or write to them may have a different meaning;


In addition, the above situations often require the consideration of data integrity (several interrelated flags are interrupted and rewritten halfway through reading). In 1, this can 
be achieved , in 2, task scheduling can be disabled, and in 3, only good hardware design can be relied upon.


Reference address:Attention to those who modify global variables in interrupt programs - the role of volatile in C

Previous article:Correct usage of watchdog
Next article:Solve the problem of red cross in keil5 (compilation passed)

Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号