单片机中断服务程序和主程序操作同一变量安全吗?
目录
1.前言
上一篇文章我们分析了单核单片机使用多线程操作共享资源是否线程安全问题,这篇文章讲解裸机程序中断服务程序和主线程同时操作共享资源是否存在资源安全性问题,同样先通过实验来看现象,然后分析现象后的原因。
2.实验
还是采用STM32L471的板子来做实验,同样为了让实验结果更加明显,我们将单片机的主频设置为最高,也就是80MHz,该主频是STM32L471所能支持的最大主频,仍然使用STM32CubeMX代码生成工具进行代码生成,我们需要配置串口和时钟,时钟采用内部MSI振荡器然后通过锁相环倍频至80MHz,时钟树如下图所示:
我们的思路是定义一个全局共享的num变量,然后分别在主函数进行循环自增及在中断服务程序中进行自增,此处中断服务程序采用定时器,定时器频繁中断进行num的自增操作,这样就达到了主函数和中断服务程序并行对num这个共享变量同时进行操作的目的,定时器的CubeMX的配置如下图所示:
同时启用定时器溢出中断:
使用CubeMX生成代码,在main.c中首先定义全局变量num:
/* USER CODE BEGIN PV */
volatile int num = 0;
/* USER CODE END PV */
主函数中的核心代码如下:
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM6_Init(); // 此处定时器初始化后定时器开始工作,num变量在中断服务程序中自增
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
/* 循环MAX_NUM次,对num变量进行自增 */
for(int i = 0; i < MAX_NUM; i++)
{
num++;
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
LL_mDelay(500);
printf("num=%d\r\n", num);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
为了让实验效果更加显著,我们将代码中MAX_NUM这个宏定义为50万,足够大的循环次数才能让结果更加显著。再看一下中断服务程序中的核心代码:
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
static volatile int counter = 0;
/* Check whether update interrupt is pending */
if(LL_TIM_IsActiveFlag_UPDATE(TIM6) == 1)
{
/* Clear the update interrupt flag*/
LL_TIM_ClearFlag_UPDATE(TIM6);
}
/* USER CODE END TIM6_DAC_IRQn 0 */
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
if(counter < MAX_NUM)
{
num++;
counter++;
}
/* USER CODE END TIM6_DAC_IRQn 1 */
}
在中断服务程序中我们定义了一个static的变量counter,用来记录num自增的次数,如果num达到50万自增,则停止进行自增操作,此处counter变量由中断服务程序独享,所以不存在变量共享安全性问题,编译代码并运行,结果如下:
可以看到,num经过主函数50万次自增操作和定时器中断的50万次自增操作后,最终值为997520,与我们预期的100万,差了2480次,说明中断服务程序和主线程共享变量且同时进行写入操作时存在一定的安全风险!
3.结果及分析
此处的原理跟上一篇文章分析的原理是一致的,查看num变量自增的汇编代码如下:
LDR r1,[r4,#0x00] ;从内存加载num的值至寄存器
ADDS r1,r1,#1 ;num++
STR r1,[r4,#0x00] ;将寄存器中的值写入num内存
从汇编代码中可以看出,num自增操作实际上需要三步汇编指令才能完成,即取值、自增、回写三个步骤,实际例子可以查看上一篇文章,此处就不在赘述。为了避免出现共享变量的安全性问题,我们需要保证这三条汇编执行过程中不被打断,因此需要在自增操作前关闭中断,自增完成后再开启中断,主程序修改后的代码如下:
/* USER CODE BEGIN 2 */
for(int i = 0; i < MAX_NUM; i++)
{
__disable_irq();
num++;
__enable_irq();
}
/* USER CODE END 2 */
重新编译,运行,结果如下:
可以看到,最终自增的结果符合我们的预期。
4. 总结
本文设计了实验来验证裸机程序中断服务程序和主线程同时操作共享资源是否存在资源安全性问题,从结果来看,不添加关闭中断和开启中断的操作,共享资源存在安全性问题,解决方案也很简单,通过添加关闭中断的操作来保证操作共享资源时不被打断。在实际单片机应用场景中,尤其是关键行业的工业应用场景,我们一定要防范此类问题的发生,保证程序稳定可靠运行。
作者:mysoftlab