完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
来源 网络 什么是临界段 代码的临界段也称为临界区,指处理时不可分割的代码区域,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即打开中断。 临界段的作用 其实在RTOS中,使用最多的临界段是OS本身的调用,但是我们用户也是需要对临界资源进行保护的(临界资源是一次仅允许一个线程使用的共享资源),特别是一些全局变量,当线程正在使用的时候不希望有人来打断我的操作,就行很多时候我们写代码时,需要集中精力,不希望别人打断我们的思路一样。这样子使得系统的运行更加稳定健壮。 什么时候会打断代码的执行? 顾名思义,代码正在正常运行的时候,基本不会被打断,能被打断的都是系统发生了异常(中断也是异常),在OS中,除了外部中断能将正在运行的代码打断,还有线程的调度——PendSV,系统产生 PendSV中断,在 PendSV Handler 里面实现线程的切换。我们要将这项东西屏蔽掉,保证当前只有一个线程在使用临界资源。 如何关闭中断? 其实,在我们常用的MCU中,一般为Cortex-M内核的,M内核是有一些指令能快速关闭中断,一起来看看Cortex-M权威指南吧(以Cortex-M3为例)。 简单来说,快速屏蔽中断就是处理这些内核寄存器,在Cortex-M中有相应的操作指令,一般我们无需关注,因为OS已经给我们写好了这些底层的东西。不过如果你是想自己写一个OS的话,可以了解一下,要访问 PRIMASK, FAULTMASK 以及 BASEPRI,同样要使用 MRS/MSR 指令,如: 1.MRS R0, BASEPRI ;读取 BASEPRI 到 R0 中 2.MRS R0, FAULTMASK ;似上 3.MRS R0, PRIMASK ;似上 4.MSR BASEPRI, R0 ;写入 R0 到 BASEPRI 中 5.MSR FAULTMASK, R0 ;似上 6.MSR PRIMASK, R0 ;似上 只有在特权级下,才允许访问这 3 个寄存器。 其实,为了快速地开关中断, CM3 还专门设置了一条 CPS 指令,有 4 种用法: 1.CPSID I ;PRIMASK=1, ;关中断 2.CPSIE I ;PRIMASK=0, ;开中断 2.CPSID F ;FAULTMASK=1, ;关异常 FreeRTOS: FreeRTOS对中断的开和关是通过操作 BASEPRI 寄存器来实现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则不会被屏蔽。这样子的好处就是用户可以设置 BASEPRI 的值来选择性的给一些非常紧急的中断留一条后路。比如飞控的防撞处理。代码在portmacro.h 中实现: 屏蔽中断: 1.static portFORCE_INLINE void vPortRaiseBASEPRI( void ) 2.{ 3.uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; 4. 5. __asm 6. { 7. msr basepri, ulNewBASEPRI 8. d*** 9. i*** 10. } 11.} 打开中断: 1.static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI ) 2.{ 3. __asm 4. { 5. msr basepri, ulBASEPRI 6. } 7.} RT-Thread: 与FreeRTOS不同的是,RT-Thread 对临界段的保护处理的很干脆,不管三七二十一直接把中断全部关了(直接操作PRIMASK内核寄存器), 只有NMI FAULT 和硬 FAULT能被相应。 这种方法简单粗暴,是很不错的选择。一般我们临界段的处理时间是比较短的,关了再开其实并没有太大的影响。 现在要看看RT-Thread的关中断的代码实现: 1.rt_hw_interrupt_disable PROC 2. EXPORT rt_hw_interrupt_disable 3. MRS r0, PRIMASK 4. CPSID I 5. BX LR 6. ENDP 开中断: 1.rt_hw_interrupt_enable PROC 2. EXPORT rt_hw_interrupt_enable 3. MSR PRIMASK, r0 4. BX LR 5. 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 d*** 10 i*** 11 } 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中一般可以直接挂起调度器,系统正常运行,但是不会切换线程,当我处理完再把调度器解除即可。 |
|
相关推荐 |
|
只有小组成员才能发言,加入小组>>
2249个成员聚集在这个小组
加入小组灵动微电子MM32全系列MCU产品应用手册,库函数和例程和选型表
11736 浏览 3 评论
【MM32 eMiniBoard试用连载】+基于OLED12864的GUI---U8G2
5943 浏览 1 评论
【MM32 eMiniBoard试用连载】移植RT-Thread至MM32L373PS
10979 浏览 0 评论
【MM32 eMiniBoard测评报告】+ 开箱 + 初探
4582 浏览 1 评论
灵动微课堂(第106讲) | MM32 USB功能学习笔记 —— WinUSB设备
4308 浏览 1 评论
[MM32软件] MM32F002使用内部flash存储数据怎么操作?
1001浏览 1评论
819浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-3 12:06 , Processed in 0.654750 second(s), Total 50, Slave 38 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号