82921934 2019-12-15
首先要明白这几个知识点:关键字volatile的使用,原子操作,临界区的使用。明白的直接跳到文中的4.全局变量的使用及保护处查看。
1.关键字volatile
关键字volatile用于告诉编译器,说明被修身的变量可能会被意想不到地改变,防止编译器对代码进行优化。
比如如下程序:
1 ucNms=0x65;2 ucNms=0x66;3 ucNms=0x67;4 ucNms=0x68;
上述4条语句,如果变量在声明的时候(unsigned char ucNms;)没有使用volatile,那么编译器有可能对其优化,只编译最后一条语句ucNms=0x68;(即忽略前三条语句,只产生一条机器汇编代码);如果变量在声明的时候(volatile unsigned char ucNms;)使用了volatile,则编译器会逐一地进行编译并产生四条相应的机器代码(产生四条代码)。
精确地说就是,编译器在编译这个变量语句时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。所以下面几个情况在声明的时候需要用volatile关键字对其修饰:
1)并行设备的硬件寄存器(如:状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量
2.原子操作
原子操作可以理解为不被打断的操作,可以是一个步骤的操作,也可以是多个步骤的操作,总之确保操作不被打断。
3.临界区
指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。
4.全局变量的使用及保护
单片机裸机编程,使用全局变量时,一般是一个或多个*.c文件(或模块)中会使用到某个全局变量(假设为A),还有中断中也会用到这个全局变量。这样在使用时就要考虑变量的安全性。单片机裸机编程是前后台系统,如下图:
单片机裸机编程前后台系统
首先要明白大循环(后台)对这个变量的访问是依次的,不管全局变量A是在哪一个模块或者*.c文件中,每一个时刻只有一个地方对变量A访问。然后中断和中断嵌套程序中也会有对全局变量A的访问。
于是就存在这样的问题,大循环(后台)在访问全局变量A时(比如说访问到一半时),被中断(前台)程序打断并修改了全局变量A,这样大循环(后台)程序再次对全局变量A访问,就会导致访问到的A存在不确定性。从而会影响程序的不正常运行。
这样就可以很明确的知道,只要在大循环(后台)访问A时,不让中断(前台)打断其访问即可。确保对A的访问是原子操作。于是就有这样的解决方法:
关中断-->>全局变量A-->>开中断
有的时候,如果访问变量A的过程比较长,可以对全局变量A做一个副本拷贝a,用拷贝的a作为模块处理的数据。于是就有了这样:
关中断-->>访问全局变量A-->>副本拷贝a-->>开中断->>操作副本拷贝a
这种复杂的情况也可以做一个锁这样做:
大循环(后台):
关中断-->>上锁-->>开中断-->>访问变量A-->>关中断-->>解锁-->>开中断
中断(前台):
如果是解锁的,操作全局变量A,如果是上锁的就不操作
当然,如果访问全局变量A本身就是一个原子操作(比如一条指令就可以访问完成),这样也就不需要做开关中断的处理了。
示例1:禁止中断方法保护全局变量
大循环(后台)
ET0=0; //禁止定时中断 访问全局变量A; 其他代码部分; ET0=1; //开启允许定时中断
定时器中断(前台)
操作全局变量A;
示例2:加锁的方法保护全局变量
大循环(后台)
ET0=0; //禁止定时中断 Lock = 1; ET0=1; //开启允许定时中断 访问全局变量A; 其他代码部分; ET0=0; //禁止定时中断 Lock =0; ET0=1; //开启允许定时中断
定时器中断(前台)
If(lock ==0) 操作全局变量A; else{;}
示例3:加锁的方法保护全局变量
大循环(后台)
Lock = 1;//若此条语句对应汇编指令是原子操作可以不用开关中断保护此锁 访问全局变量A; 其他代码部分; Lock =0;//若此条语句对应汇编指令是原子操作可以不用开关中断保护此锁
定时器中断(前台)
If(lock ==0) 操作全局变量A; else{;}
总结下:中断全局变量尽量要用volatile修饰,中断全局变量要原子操作访问,要时刻明白中断全局变量是临界区资源,共享访问时需要保护。
转发自:http://www.360doc.com/content/19/0117/10/39315025_809417729.shtml#