MCU上的无锁原子读操作

judy的头像
judy 发布于:周一, 03/19/2018 - 16:01 ,关键词:

原子读操作是在MCU并发编程中常用的操作,简单举个例子来阐述问题:

我们使用RTOS或裸机状态编程时,必然需要一个全局时钟基准,通常是在一个定时器中断中累加实现,简化代码如下:

static unsigned long volatile __jiffies = 0; /* 全局时钟基准节拍累加器 */

ISR_TIMER() /* 定时中断服务函数 */
{
++__jiffies;
/* 其它代码...: */
}

对于其中的__jiffies变量,就是全局时间基准,程序中其它地方都会对其进行原子读操作来判断时间,典型的接口实现如下:

unsigned long get_jiffies(void)
{
unsigned long tmp;

CLOCK_IRQ_DIS(); /* 关定时中断 */
tmp = __jiffies;
CLOCK_IRQ_EN(); /* 开定时中断 */

return tmp;
}

请注意,其中关于对中断的开关是对该定时中断中所有代码会带来影响。如果在RTOS中,关中断的时间是一种重要性能指标,决定了整个系统的中断快速响应能力。

在此假设一个最艰难的架构,8位机(AVR、51等等),其上只有8位单字节数据的读写是单指令原子的,其中unsigned long型在这样的架构下是32位8字节。

根据各位朋友提出情况,进行说明:

1、有朋友认为读操作没必要关中断.

这个显然不可能,当你读了32位变量任何一个字节的时候,剩下的7个字节都可能改变。

2、认为在中断函数建立数据拷贝

这个理由同上,无论如何复制,都难以避免读的瞬间数据被破坏

3、建立单字节原子锁

该体系必须支持测试清零指令,而且就算支持。如果中断里发现锁被占有了,那这个周期还能进行+1操作么?无论是用变量缓存还是丢弃,所记时间都不准了。

实现如下:
unsigned long get_jiffies(void)
{
unsigned long tmp;

do {
tmp = __jiffies;
} while(tmp != __jiffies);

return tmp
}

简单得大家可能都不相信,可以满足任何MCU架构完成如上对__jiffies变量的操作(必须单核),大家可以仔细想想。

无锁单读单写队列是MCU上经常用的,对中断通信接口的缓冲非常方便可靠。以此为基础,可跨平台实现。

围观 607