RTOS临界段知识详解

什么是临界段
代码的临界段也称为临界区,指处理时不可分割的代码区域,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即打开中断。
临界段的作用
其实在rtos中,使用最多的临界段是os本身的调用,但是我们用户也是需要对临界资源进行保护的(临界资源是一次仅允许一个线程使用的共享资源),特别是一些全局变量,当线程正在使用的时候不希望有人来打断我的操作,就行很多时候我们写代码时,需要集中精力,不希望别人打断我们的思路一样。这样子使得系统的运行更加稳定健壮。
什么时候会打断代码的执行?
顾名思义,代码正在正常运行的时候,基本不会被打断,能被打断的都是系统发生了异常(中断也是异常),在os中,除了外部中断能将正在运行的代码打断,还有线程的调度——pendsv,系统产生 pendsv中断,在 pendsv handler 里面实现线程的切换。我们要将这项东西屏蔽掉,保证当前只有一个线程在使用临界资源。
如何关闭中断?
其实,在我们常用的mcu中,一般为cortex-m内核的,m内核是有一些指令能快速关闭中断,一起来看看cortex-m权威指南吧(以cortex-m3为例)。
简单来说,快速屏蔽中断就是处理这些内核寄存器,在cortex-m中有相应的操作指令,一般我们无需关注,因为os已经给我们写好了这些底层的东西。不过如果你是想自己写一个os的话,可以了解一下,要访问 primask, faultmask 以及 basepri,同样要使用 mrs/msr 指令,如:
1mrs r0, basepri ;读取 basepri 到 r0 中2mrs r0, faultmask ;似上3mrs r0, primask ;似上4msr basepri, r0 ;写入 r0 到 basepri 中5msr faultmask, r0 ;似上6msr primask, r0 ;似上只有在特权级下,才允许访问这 3 个寄存器。
其实,为了快速地开关中断, cm3 还专门设置了一条 cps 指令,有 4 种用法:
1cpsid i ;primask=1, ;关中断2cpsie i ;primask=0, ;开中断3cpsid f ;faultmask=1, ;关异常4cpsie f ;faultmask=0 ;开异常上面的代码中的primask和 faultmast 是 cortex-m 内核 里面三个中断屏蔽寄存器中的两个,还有一个是 basepri,这些寄存器都用于屏蔽中断。
不同os的处理临界段的区别
freertos:
freertos对中断的开和关是通过操作 basepri 寄存器来实现的,即大于等于 basepri 的值的中断会被屏蔽,小于 basepri 的值的中断则不会被屏蔽。这样子的好处就是用户可以设置 basepri 的值来选择性的给一些非常紧急的中断留一条后路。比如飞控的防撞处理。代码在portmacro.h 中实现:
屏蔽中断:
1static portforce_inline void vportraisebasepri( void ) 2{ 3uint32_t ulnewbasepri = configmax_syscall_interrupt_priority; 4 5 __asm 6 { 7 msr basepri, ulnewbasepri 8 dsb 9 isb10 }11}打开中断:
1static portforce_inline void vportsetbasepri( uint32_t ulbasepri )2{3 __asm4 {5 msr basepri, ulbasepri6 }7}rt-thread:
与freertos不同的是,rt-thread 对临界段的保护处理的很干脆,不管三七二十一直接把中断全部关了(直接操作primask内核寄存器), 只有nmi fault 和硬 fault能被相应。 这种方法简单粗暴,是很不错的选择。一般我们临界段的处理时间是比较短的,关了再开其实并没有太大的影响。
现在要看看rt-thread的关中断的代码实现:
1rt_hw_interrupt_disable proc2 export rt_hw_interrupt_disable3 mrs r0, primask4 cpsid i5 bx lr6 endp开中断:
1rt_hw_interrupt_enable proc2 export rt_hw_interrupt_enable3 msr primask, r04 bx lr5 endp这短短的几句代码其实还是很有意思的,我就引用火哥的话来解释一下这些处理操作(我个人是不会汇编的,但是跟着书来解读这些代码还是很轻而易举的)
可能有人懂汇编的话,就会看出来,关中断,不就是直接使用 cpsid i 指令就行了嘛~开中断,不就是使用 cpsie i 指令就行了嘛,为啥跟我等凡人想的不一样?
rt-thread的处理好像是多此一举了,实则不然,“所有东西的存在必然有其存在的意义”这句话应该没人反驳吧~~因为rt-thread要防止用户错误地退出了中断临界段,因为这样子可能会产生巨大的危害,所以rt-thread将当前的primask的状态保存起来,这样子就必须要关多少次中断就得开多少次中断。
怎么说呢,用例子来证明吧:
1/* 临界段 1 开始 */ 2rt_hw_interrupt_disable(); /* 关中断,primask = 1 */ 3{ 4 /* 临界段 2 */ 5 rt_hw_interrupt_disable(); /* 关中断,primask = 1 */ 6 { 7 } 8 rt_hw_interrupt_enable(); /* 开中断,primask = 0 */ (注意) 9}10/* 临界段 1 结束 */11rt_hw_interrupt_enable(); /* 开中断,primask = 0 */如果直接操作primask,而不保存primask的状态,这样子当临界段2结束后调用一次打开中断,那么连临界段1的后半部分就无效了。而rt-thread的实现就能很好避免这种问题,也用代码来说明吧:
1/* 临界段 1 开始 */ 2level1 = rt_hw_interrupt_disable(); /* 关中断,level1=0,primask=1 */ 3{ 4 /* 临界段 2 */ 5 level2 = rt_hw_interrupt_disable(); /* 关中断,level2=1,primask=1 */ 6 { 7 } 8 rt_hw_interrupt_enable(level2); /* 开中断,level2=1,primask=1 */ 9}10/* 临界段 1 结束 */11rt_hw_interrupt_enable(level1); /* 开中断,level1=0,primask=0 */这样子就完全避免了对吧!
有人又会问了,freertos的临界段能允许嵌套吗,答案是肯定的,freertos中早已给我们想好调用的函数了,并且全部使用宏定义实现了:
1#define portdisable_interrupts() vportraisebasepri()2#define portenable_interrupts() vportsetbasepri( 0 )3#define portenter_critical() vportentercritical()4#define portexit_critical() vportexitcritical()5#define portset_interrupt_mask_from_isr() ulportraisebasepri()6#define portclear_interrupt_mask_from_isr(x) vportsetbasepri(x)其实原理都是差不多的,通过保存和恢复寄存器basepri的数值就可以实现嵌套使用。
1ubasetype_t uxsavedinterruptstatus; 2 3uxsavedinterruptstatus = portset_interrupt_mask_from_isr(); 4{ 5 uxsavedinterruptstatus = portset_interrupt_mask_from_isr(); 6 { 7 //临界区代码 8 } 9 portclear_interrupt_mask_from_isr( uxsavedinterruptstatus );10}11portclear_interrupt_mask_from_isr( uxsavedinterruptstatus );进入中断源码的实现:
1static portforce_inline uint32_t ulportraisebasepri( void ) 2{ 3uint32_t ulreturn, ulnewbasepri = configmax_syscall_interrupt_priority; 4 5 __asm 6 { 7 mrs ulreturn, basepri 8 msr basepri, ulnewbasepri 9 dsb10 isb11 }12 return ulreturn;13}退出中断源码实现:(跟前面的函数一样)
1static portforce_inline void vportsetbasepri( uint32_t ulbasepri )
2{
3 __asm
4 {
5 msr basepri, ulbasepri
6 }
7}
总结
对于时间关键的任务而言,恰如其分地使用 primask 和 basepri 来暂时关闭一些中断是非常重要的。
freertos源码中就有多处临界段的处理,除了freertos操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种:
读取或者修改变量(特别是用于任务间通信的全局变量)的代码,一般来说这是最常见的临界代码。
调用公共函数的代码,特别是不可重入的函数,如果多个任务都访问这个函数,结果是可想而知的。
总之,对于临界段要做到执行时间越短越好,否则会影响系统的实时性。
那假如我有一个线程,处理的时间较长,但是我又不想被其他线程打断,关中断可能影响系统的正常运行,怎么办呢?其实很简单,在os中一般可以直接挂起调度器,系统正常运行,但是不会切换线程,当我处理完再把调度器解除即可。

智能镜面显示屏,一种高端科技技术的最新产物
改善沟通在爆炸性环境中
新闻 | 中国移动联合华为打造SPN产业化应用项目,荣获中国通信学会科学技术奖二等奖
苹果A14大曝光,采用5nm工艺+5G
ZS3087SL 18W开关电源方案
RTOS临界段知识详解
北斗RTK高精度定位技术原理及优势
数字化成为新基建的主旋律 新华三:从我做起
RFID技术可以为企业带来什么
全面分析数字沙盘与电子沙盘之间的区别
土壤水分测量系统的应用领域有哪些,它有什么特点
2018年金融区块链数字资产交易平台系统开发将迎国家化标准
充分利用人工智能,实现更为高效的下一代数据存储
多平面图像的单视图合成
分享电磁阀控制系统原理图
城市治安监控系统的结构组成及功能实现
2018款比亚迪元EV360新能源车电池与电驱系统拆解
Manz将激光应用于防水密封技术
频谱分析仪的工作原理
脑电物理头模型数据采集系统的研究