嵌入式全局数据中断访问问题
1. 背景
在嵌入式软件中,中断是会打断正在执行的主任务或者线程,在程序设计中,往往会避免中断和主任务同时修改某个变量,但是有时无法避免这个问题,在之前的一篇博客中提到的嵌入式日志项目1,是运行有操作系统的嵌入式设备中,我们禁止了在中断中记录日志,记录日志的动作是发生在不同的线程之间,有互斥锁和信号量来保护全局的资源。在我们项目组中,还有其他的产品线是直接使用裸机开发,也同样有记录日志的需求,但是问题是有些设备的关键信息是发生在中断中,这就意味着需要在中断中发出记录日志的请求,就出现了可能在中断和主任务中同时修改全局资源的情况,为了探究全局资源在有中断访问时的情况,设计了一个实验来验证这个问题,并提出解决意见。
2. 试验过程
2.1 环境
- 使用stm32f401rct6核心板
- 主频168MHz,256KB Rom,64KB Ram
- 使用arm-gcc编译工具链
2.2 设计方案
2.2.1 测试1
-
使用一个全局数组来存放数据信息,再使用一个全局变量来作为数组使用位置的指针。
-
在主任务和一个定时器中断中记录数据,均为1ms记录一次。
-
每次记录的信息为定长数据,为10个字节,前6个字节表示记录数据者,主任务中为main,中断中为int,后4个字节为记录信息序列号
-
主任务和中断中各记录2000次
2.2.2 测试2
- 在测试1的基础上对全局变量的指针使用volatile关键字
2.2.3 测试3
- 在测试1的基础上,在记录数据时进入临界区
其他相关
-
主任务信息的序列号从2000开始
-
中断信息的序列号从4000开始
-
设计一把锁,不阻塞程序,仅仅作为查看是否有主任务在记录信息时被打断的情况
-
最终我们需要查看指针值是否为4000,还有记录的数据是否正确,是否有信息因为覆盖产生丢失
-
最后我们使用临界区来保护数据
-
编译优化的问题,本次试验采用的是CMake的Release模式,编译优化选项为-Ofast
3. 测试结果
- 本次的测试结果如下
| 测试次数 | 数据丢失 | 锁冲突 | |
|---|---|---|---|
| 测试1 | 1 | 0 | 2 |
| 测试1 | 2 | 1 | 3 |
| 测试1 | 3 | 1 | 3 |
| 测试1 | 4 | 2 | 3 |
| 测试1 | 5 | 0 | 1 |
| 测试2 | 1 | 1 | 3 |
| 测试2 | 2 | 2 | 3 |
| 测试2 | 3 | 2 | 2 |
| 测试2 | 4 | 2 | 3 |
| 测试2 | 5 | 3 | 3 |
| 测试3 | 1 | 0 | 3 |
| 测试3 | 2 | 0 | 3 |
| 测试3 | 3 | 0 | 3 |
| 测试3 | 4 | 0 | 3 |
| 测试3 | 5 | 0 | 3 |
- 可以看出在正常情况下的测试1,在主任务和中断各记录两千次的情况下,是有较高概率发生数据的丢失。
- 在Release模式下,测试2和测试1的情况相似,但是在Debug模式下(数据未给出),测试2的指针由于有volatile的保护,使得指针的自增得以安全进行,但是数据覆盖还是会发生,在编译优化后,volatile的特性似乎被编译器给优化了,使得最后的指针不能指向数组最后。
- 在临界中进行数据写入,虽然还是有锁的冲突,但是写入数据时,关闭了中断,使得数据记录有序进行,没有发生数据丢失。
4. 总结
通过试验可以发现,之前的担忧是完全有理由的,高优先的中断会打断正在执行的任务,二者同时发生数据修改时,就有可能会发生数据覆盖和数据丢失的情况,在实际的项目中,我们要极力避免在不同中断和主任务中去修改全局变量。要是确实无法避免,可以考虑使用临界区来使得修改有序进行,或者使用系统调用来实现,在Cortex内核中系统的调用的优先级是高于0的,这就使得系统调用的效果和临界区相同,都可以保证数据有序修改。还有一个问题就是volatile并不能保证全局变量一定是绝对有序访问的,这个和具体编译器在不同编译模式的选择,编译可能有自己的判断。事实本次试验主要目的就是想检验中断对memcpy这样的操作是否会有影响,正如试验结果,全局变量尚且有可能会被覆盖修改,一个数组同样面临这样的问题,虽然这个全局数据修改冲突的是低概率事件,但是将时间拉长,我们不得不重视这个问题,在运行期间,数据冲突的问题,就应该是个大概率事件。回过头来,在中断中记录日志,其实有一个非常的好的解决方案就是使用事件调度器(在我之前的一篇博客中有介绍)2,但是在目前的日志组件化工作中,我并不想再引入一个较大的变量进来(会使得日志组件化工作严重推迟),目前的解决方案就是先使用临界区,之后再改为系统调用,如果事件调度器能够上线的化可能会精简一下资源,转而使用事件调度器。
最后,测试的代码在我的代码仓库3projects分支下Project/log_test_interrupt。
测试数据和测试所用二进制bin包附在博客附件中。
本文针对嵌入式设备中中断和主任务同时修改全局资源的问题展开研究。通过在stm32f401rct6核心板上进行测试,设计了三个测试方案。结果显示,正常测试有较高数据丢失概率,使用volatile关键字效果不佳,而进入临界区可保证数据有序记录。建议避免不同中断和主任务修改全局变量,可采用临界区或系统调用。
2550





