单片机中断防护概述

主优先级相同的中断,相当于同处一个线程,main链条是优先级最低的线程,如果把中断分组设置为共4个主优先级,那么加上main,整个程序共有5个线程,优先级由高到低依次为:int0 int 1 int2 in3 main

同级别的中断,对同一个变量的操作,永远不会发生读写一半被改掉的情况,如同main链中的各个函数一样,属于同步操作。

当多线程(或中断)异步操作同一个变量(即使是个单字节变量)时,中断(线程)防护需要面临的情景,有且仅有以下3种:

1.高优先级[读],且低优先级[读写]或[写]

异常:低优先级把变量才写了一半,就被高优先级给读到,因此低优先级必须保证自己写变量不能被打断。

解决:低优先级写变量时,要屏蔽高优先级中断

2 高优先级[读写]或[写],且低优先级[读]

异常:低优先级读变量,读到一半,被高优先级打断后修改掉了,低优先级再读出另一半,此时会发生错误。低优先级必须保证,自己的读不能被打断。

解决:低优先级读变量时,要屏蔽高优先级中断

3.高优先级[读写]或[写],且低优先级[读写]或[写]

异常:低优先级读写时,均不能被打断。

解决:低优先级读写变量时,都要屏蔽高优先级中断

代码实例:

1、在32位平台上操作uint32数据

情景描述:一个u32变量tim3Tick在定时中断中++,main函数通过函数接口不断的读取这个变量的值到变量tick中。

1.1在中断中对内存变量做++操作

下图在定时器中断中对一个u32进行++操作:

以上代码对应的汇编代码如右边所示,我们发现一个简单的++操作,竟然有5行汇编代码,下面进行逐行解析:

(1)把PC寄存器的内容+752的和(这个和实际上是tim3Tick变量的内存地址)存到R0寄存器。也即现在R0中存的是tim3Tick变量的地址
0x0800AF90 48BC      LDR           r0,[pc,#752]  ; @0x0800B284

(2)把R0指向的内存地址中数值存到R0,也即这行代码执行完以后,R0的值就是内存中的变量tim3Tick的当前值
0x0800AF92 6800      LDR           r0,[r0,#0x00]

(3)把R0寄存器中的值加1:
0x0800AF94 1C40      ADDS          r0,r0,#1

(4)把PC寄存器的值+748得到的和(这个和实际上是tim3Tick变量的内存地址)存到R1寄存器。也即,现在R1中存放的是tim3Tick变量的地址。看到这里会有同学疑问了,为什么上面步骤1中是*PC+752,到这里却成了*PC+748呢?答:因为经过1~4这4步操作,cpu执行这行代码时,PC的值已经增大了4,所以这里求的和,步骤1求的和,结果是一样的。
0x0800AF96 49BB      LDR           r1,[pc,#748]  ; @0x0800B284

(5)把R0寄存器中的值,存到R1指向的内存地址中。
0x0800AF98 6008      STR           r0,[r1,#0x00]

至此,汇编代码分析完毕。我们发现C语言变量的++操作,在汇编中经历了这些步骤:把变量的值载入Rn寄存器,在Rn中++,之后再回写到变量的内存中。

1.2 在main中读取变量的值

对应的汇编如下:

 

 我们发现通过函数接口getTim3Tick()读取变量的值到main函数中,汇编代码有3段,共6行,分别是:函数跳转、取得变量的值、写入main变量的内存中。下面逐行解析:

(1)执行函数跳转
0x08008756 F002FC21  BL.W          getTim3Tick (0x0800AF9C)

(2)把*PC+748的值(也即tim3Tick变量的地址)存入R0
0x0800AF9C 48BB      LDR           r0,[pc,#748]  ; @0x0800B28C

(3)把tim3Tick变量的值存入R0
0x0800AF9E 6800      LDR           r0,[r0,#0x00]

(4)函数返回。我们发现,函数的返回值是存在R0中的
0x0800AFA0 4770      BX            lr

(5)把*PC+28的值(也即main中tick变量的地址)存到R1
0x0800875A 4907      LDR           r1,[pc,#28]  ; @0x08008778

(6)把R0的内容(也即getTim3Tick函数的返回值)存到R1指向的内存地址处,也即把tim3Tick的值存到了tick变量的内存中
0x0800875C 6008      STR           r0,[r1,#0x00]

至此汇编分析完毕。

那么在以上过程中,没有发现main函数读变量读了一半,中途变量值被中断给改掉的情况。在32位机器上,对u32的读写总是原子性的,不会出现u32的4个字节中的部分字节被改掉的情况。但是我们仍然要注意到,如果不加中断防护,main虽然读到的值是可靠的,但却无法保证是最新的,例如当上面第(3)行汇编执行完以后,tim3Tick的值已经被载入到了R0中函数准备返回,此时如果发生了中断,tim3Tick的值就会被更新,等中断结束后,main函数取得的getTim3Tick的返回值,就不是最新的了,而是之前的值。这种异常情况到底有没有问题,不能一概而论,具体到看业务需求,在某些业务场景下,读到的数据被延迟可能并不算问题。

2.在32位平台操作uint64数据

情景描述:一个u64变量tim3Tick在定时中断中++,main函数通过函数接口不断的读取这个变量的值到变量tick中。

2.1 变量在中断中++ 

这段++操作共有6行汇编,比之前u32版本多了一行,下面逐行分析:

(1)把tim3Tick的地址载入R0:
0x0800AF94 48BF      LDR           r0,[pc,#764]  ; @0x0800B294

(2)把R0指向的内存区的内容,双字(64bit)分别载入到R2(低32位)、 R0(高32位)寄存器。也即tim3Tick的值高低32bit分别进入了R0 R2
0x0800AF96 E9D02000  LDRD          r2,r0,[r0,#0]

(3)低32位执行+1操作,同时更新进位标志到C
0x0800AF9A 1C52      ADDS          r2,r2,#1

(4)高32位执行与0、与进位标志相加。等价于R0=R0+0+C,其中C为上一步更新的进位标志
0x0800AF9C F1400000  ADC           r0,r0,#0x00

(5)把tim3Tick的地址载入R1
0x0800AFA0 49BC      LDR           r1,[pc,#752]  ; @0x0800B294

(6)把R2 R0的值按双字存入R1指向的内存地址处。
0x0800AFA2 E9C12000  STRD          r2,r0,[r1,#0]

至此汇编分析完毕,我们发现在32位平台上,u64变量的++操作,在汇编中是要按高低32bit分别计算的。

2.2 在main中读取变量的值

 这行C代码对应的汇编有7行,如下:

 

 

 汇编解析:

LDR 把tim3Tick的地址载入R1,
LDM把R1指向的内存区的内容连续载入R0~R1中,也即函数的返回值就存在R0 R1中了
BX函数返回
LDR 把tick变量的地址载入R2
STR 把R0中的值存到R2指向的地址偏移0字节的位置
STR 把R1中的值存到R2指向的地址偏移4字节的位置

汇编解析完毕。我们发现,返回tim3Tick的值时,是用一行汇编LDM实现的,把返回值赋值给tick时,是用两行STR实现的。

2.3 问题分析

以上两个小节分析可见,在32位平台上,u64变量的++操作不是原子性的。假如反过来,例如当main中有变量tim3Tick=0x0001FFFF时,执行tim3Tick++时,低32位先+1,得到FFFF+1=0000,且进位标志C置位,高32位的汇编加法为0x0001+0+C=0x0002,如果高32位加法尚未执行,发生了中断,中断想要读取tim3Tick的值,分别读取高低32位,就会得到0x0001 0x0000,显然出现了不能容忍的错误。

不过好在我们没有开启编译器优化(也即O0),以上情况并没有发生,因为高低32位分别进行的加法操作都是在Rn中进行的,只要两个加法未完成,就不会把结果写回内存,而读取操作,总是从内存中直接提取,所以这里从内存读到的数总是完整的。而且u64写回内存的操作,是用STRD指令原子性写入的,u64的内存可以保证完整性。

但是要注意的是,不是所有的硬件平台都能保证u64回写内存的原子性,如果没有STRD指令,你就必须用u32的写命令分别写高低32位,这时就会发生前面所说的不能容忍的错误。

如果我们开启了编译器优化(>O1),u64的读写操作可能不会由内存进行中转,而是直接在Rn中进行,这时也会出现同样的不完整性问题。

3. 在32位平台操作uint8数据

可以发现,操作u8是完全原子性的,与操作u32无异,区别在于LDR指令换成了LDRB指令,LDRB指令是操作内存单字节的。

4.其他例子

main中对一个变量做一个1000次的加法,中断中也对这个变量做1000次加法,理论上,main和中断都执行完以后,这个变量应当=2000.

但是由于变量的加法操作总是需要Rn寄存器中转,一定会造成寄存器数据与内存数据不一致。假设main中读取了该变量=100,并载入了Rn寄存器,这时中断发生了,中断从内存读出该值也=100,然后+1写回了内存,程序返回main,而main计算+1时,应当从101开始+,但由于之前已经把变量值100载入Rn了,加法操作会在Rn中进行,加完还是101,而不是102

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值