目录
1.问题背景:
2.原子操作
2.1 硬件操作
2.1.1 LDREX/LDXR指令
2.1.2 STREX/STXR指令
2.2 软件操作
2.3 软件硬件操作的各性能对比
3.总结
1.问题背景:
我们知道,RTOS的任务调度算法是抢占式优先级调度算法。
既然是抢占了,说明会出现一种情况:当我们的任务还没完成的时候,CPU的权限就被更高优先级的任务给抢去了。
有的时候,我们任务还在进行重要数据的运算,然后这个数据的内存是和其他任务共享的,在这互相抢占中,就会出现数据运算的丢失。
既然我都说重要了,说明这件事情是不可容忍的!
那么我们有什么办法解决呢?
有的,兄弟有的,那就是原子操作。
2.原子操作
首先我们要知道什么是原子操作:
原子操作指的是在执行过程中不可被中断的操作。这意味着一旦这个操作开始执行,它就会持续执行直至完成,期间不会被其他线程或进程打断。
就是你不管在什么时候查询他的状态,他的状态有且只有两个,完成和未完成。
那这么厉害的东西,是怎么实现的呢?
目前主流的原子操作的实现方式是通过硬件实现的,少部分不支持硬件的MCU是通过软件实现的。
2.1 硬件操作
就像中断一样,原子操作也是通过硬件支持的,而硬件这么支持原子操作呢?我们可以查看ARM(Armv6)的手册。ARM Synchronization Primitives Development Article
得知我们原子操作和中断一样在汇编层提供了硬件指令集:LDREX和STREX
提示:
- 硬件支持就是指硬件对我们功能的实现提供了什么帮助,而这个帮助这么用到的呢?就是通过在软件层调用硬件指令,后续代码的实现就在硬件上不占用软件的资源。
- 软件支持就是软件开辟一个临界区,你的代码实现是在软件上,占用的是软件的资源。
- 在ARMv8指令集下,LDREX指令被改名成了LDXR指令,而STREX指令被改名成了STXR指令,功能基本上是一样的,除了添加了一个新的特性。当全局监视器标记的对某段内存的独占访问被清空后,将向所有标记了对该段内存独占访问的CPU核都发送事件,将它们从WFE指令中唤醒,继续执行。
下面我们查看手册中关键的部分。
2.1.1 LDREX/LDXR指令
LDREX 指令从内存中加载一个字节,并将独占监视器的状态初始化为用于跟踪同步操作的值。
LDREX R1,[R0]
会从 R0 中的地址处取出一个值(无法被打断),将值存入 R1 中,并更新独占监视器。
2.1.2 STREX/STXR指令
STREX 指令会对一个字进行条件存储到内存中。
如果独占监视器允许进行此存储操作,那么该操作会更新内存中的位置,并在目标寄存器中返回值 0,表示操作成功。
如果独占监视器不允许进行此存储操作,那么该操作不会更新内存位置,并在目标寄存器中返回值 1。
这使得能够基于内存操作的成功或失败来实现条件执行路径。
STREX R2, R1, [R0]
执行到 R0 中地址的存储独占操作,条件存储 R1 中的值,并在 R2 中指示成功或失败。
当然这个内存独占访问还是能继续写下去的,但这样的话篇幅过长脱离了我们的主题,且阅读难度骤升,因此本章先忽略,各位感兴趣的可以自行去其他博客查看。
2.2 软件操作
软件操作最终实现的结果肯定是和硬件相同的,原理就是我们软件算法和同步机制模拟原子性。依赖软件的锁/信号量和算法的实现。下面举一个例子。
pthread_mutex_lock(&mutex); // 加锁 // 执行原子操作 pthread_mutex_unlock(&mutex); // 解锁
注意:
如果在硬件支持的情况下,锁的实现是通过硬件指令实现的。
但如果没有硬件呢?那就是通过一些轮询或者算法来实现,例如:
- 忙等待(Busy Waiting):也称为自旋锁(Spinlock)。一个线程不断地检查锁是否可用,直到成功获取锁为止。这种方法简单但效率低下,因为它会占用大量的CPU时间。
- Peterson算法:一种经典的软件解决方案,用于在两个线程之间实现互斥。它利用了共享内存和几个布尔标志位以及一个指示哪个线程准备进入临界区的变量。
- Lamport's bakery algorithm:为了解决多个进程之间的互斥问题而设计的一种算法,它模拟了一个“面包店”取号排队的过程,确保每个“顾客”(进程)按照先来后到的顺序获得服务(进入临界区)。
2.3 软件硬件操作的各性能对比
对比维度 | 硬件实现 | 软件实现 |
性能 | 高(直接由硬件指令完成) | 低(依赖锁或算法,存在上下文切换) |
兼容性 | 依赖硬件支持(如x86、ARM指令集) | 通用性强,适用于任何硬件平台 |
实现复杂度 | 低(由编译器/库自动调用硬件指令) | 高(需手动实现锁或算法) |
适用场景 | 高性能并发编程(如数据库、操作系统) | 资源受限或硬件不支持的场景 |
3.总结
在项目越来越复杂,MCU的性能、项目对性能的要求越来越高的时候,单单一个裸机跑循环实现各种任务的优势会大大缩减,大部分都是通过各种RTOS这种抢占式任务的创建,来实现项目核心功能的稳定运行,这个时候,为了我们的数据的完整性,原子操作是必不可少的,因此学习原子操作,了解底层就显得有必要了。