【ucos】ucosIII 进入临界区与延迟发布的疑问

【ucos】ucosIII 进入临界区与延迟发布的疑问

  • 开关中断
    • CPU_CRITICAL_ENTER
    • CPU_CRITICAL_EXIT
  • 进出临界区
    • OS_CRITICAL_ENTER
    • OS_CRITICAL_EXIT
  • 延迟发布
    • OS_CRITICAL_ENTER 开启延迟发布
    • OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT
    • OS_CRITICAL_EXIT 开启延迟发布
  • 补充

临界区在ucos中是非常重要的一种机制,所以在此进行一个简单的梳理。先说一下为什么要进入临界区,因为ucos是一个可剥夺型内核,所以任何操作都有可能在任意时刻被打断,但是如果某些操作例如数据写入、内核对象创建、发布等工作是不能被打断的,那么就需要采用某种方法实现一段代码一定能够完整的执行完。在ucos中有两种方法进入临界区:1.关中断;2.锁调度器。
关中断很好理解,中断都关了,所有的外设中断、大部分的异常甚至ucos本身的时钟节拍都被暂停了,那么基本上是可以完整的执行完这段代码。
锁调度器则是禁止了ucos的任务调度,如果某段操作可以被中断打断但是不可以被其他任务打断就可以采用这个操作,好处是减少关中断时间。

开关中断

在ucos中可以通过CPU_CRITICAL_ENTER/EXIT来实现开关中断。在调用之前,需要先调用方法CPU_SR_ALLOC()。它的作用是声明一个CPU_SR变量。

 CPU_SR  cpu_sr = (CPU_SR)0

CPU_CRITICAL_ENTER

该函数背后调用的是汇编代码CPI_SR_Save,它的主要流程是将PRIMASK寄存器的值保存到R0寄存器中—关中断—返回。代码如下:

CPU_SR_Save
        MRS     R0, PRIMASK                  
        CPSID   I
        BX      LR

这里有一些知识点需要解释:
PRIMASK:是1位宽的中断屏蔽寄存器,它只有最低位可以读写。通过读取PRIMASK可以得到当前中断的开关状态。
MRS:MRS 。这段汇编的含义是将特殊寄存器读入寄存器。
MSR:MSR 。将寄存器的值写入特殊寄存器
在C语言中调用汇编语言时,如果程序有返回值,就会默认的读取R0寄存器的数值,所以写入R0后,返回值赋给CPU_SR就实现了中断状态的保存

CPU_CRITICAL_EXIT

CPU_SR_Restore                              
        MSR     PRIMASK, R0
        BX      LR

这里也很简单,就是将先前保存的中断状态重新写入PRIMASK寄存器中。这里有一个细节,在C语言中调用汇编时,如果存在参数,那么参数会保存在R0寄存器中。

进出临界区

OS_CRITICAL_ENTER

如果没有开启延迟发布,那么这个函数等价于CPU_CRITICAL_ENTER

OS_CRITICAL_EXIT

如果没有开启延迟发布,那么这个函数等价于CPU_CRITICAL_EXIT

延迟发布

#define OS_CFG_ISR_POST_DEFERRED_EN     1u   /* Enable (1) or Disable (0) Deferred ISR posts */

在os_cfg.h中可以看到这个配置宏,它的作用就是使能/失能延迟发布功能。
延迟发布也非常常见,它的原理也比较简单,就是将原本要放在中断中执行的工作,放到一个专门的任务中执行。比如最常见的一个应用场景就是在中断中发布一个信号量。设计这样一个功能的原因是什么?可以拿这个中断中发布信号量的例子来说。

假设某一外部中断发生了,此时应当执行某一任务进行处理。如何实现同步?简单的掉牙了,就是该任务的死循环中使用OSSemPend等待一个信号量,而中断中则使用OSSemPost发布信号量,就这么简单。但是前面说过,在内核对象的发布过程中需要进入临界区,如果使用的是关中断的方法,由于该方法耗时较多,就会使得关中断时间过长影响整个系统的性能。所以才会有延迟发布的概念,在中断中调用OSIntQPost,将信号量有关的信息保存下来,并且使得优先级为1的延迟发布任务就绪,当退出中断时会进行任务调度,然后立即执行延迟发布任务,在这个任务中进行OSSemPost工作。而此时也就不需要靠关闭中断进入临界区,只需要锁调度器即可。具体可参见ucosIII相关书籍中断管理部分。

OS_CRITICAL_ENTER 开启延迟发布

可以看到仅仅是将调度器上锁计数加1

#define  OS_CRITICAL_ENTER()                                       \
         do {                                                      \
             CPU_CRITICAL_ENTER();                                 \
             OSSchedLockNestingCtr++;                              \
             if (OSSchedLockNestingCtr == 1u) {                    \
                 OS_SCHED_LOCK_TIME_MEAS_START();                  \
             }                                                     \
             CPU_CRITICAL_EXIT();                                  \
         } while (0)

OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT

该方法的前面应当配合CPU_CRITICAL_ENTER使用,后面配合OS_CRITICAL_EXIT使用。可以实现前部分关中断后部分锁调度器

#define  OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT()                     \
         do {                                                      \
             OSSchedLockNestingCtr++;                              \
                                                                   \
             if (OSSchedLockNestingCtr == 1u) {                    \
                 OS_SCHED_LOCK_TIME_MEAS_START();                  \
             }                                                     \
             CPU_CRITICAL_EXIT();                                  \
         } while (0)

OS_CRITICAL_EXIT 开启延迟发布

这里可以看到一个注意点,当调度器完全解锁后会查看是否需要进行延迟发布,

#define  OS_CRITICAL_EXIT()                                        \
         do {                                                      \
             CPU_CRITICAL_ENTER();                                 \
             OSSchedLockNestingCtr--;                              \
             if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0) {     \
                 OS_SCHED_LOCK_TIME_MEAS_STOP();                   \
                 if (OSIntQNbrEntries > (OS_OBJ_QTY)0) {           \
                     CPU_CRITICAL_EXIT();                          \
                     OS_Sched0();                                  \
                 } else {                                          \
                     CPU_CRITICAL_EXIT();                          \
                 }                                                 \
             } else {                                              \
                 CPU_CRITICAL_EXIT();                              \
             }                                                     \
         } while (0)

这一块我有点疑问,延迟发布的原理我清楚,但是它主要是用于内核对象的发布用的。这里说的大大减小关中断时间并不是延迟发布的直接作用,而是在修改宏定义时会修改进出临界区的代码,将进出临界区由开关中断改为锁调度器。那我很好奇的一点是,如果我没有使能延迟发布同时将进出临界区的代码改为使能延迟发布的代码,也就是将关中断改为锁调度器会怎么样?比如说在中断中调用OSSemPost函数,没有使能中断延迟时,全程在中断中执行并且关中断;使能中断延迟时,该函数在延迟发布任务中执行,前半部分关中断,后半部分锁调度器。那如果我没有使能延迟发布,全程在中断中执行,并且同样也是前半部分关中断后半部分锁调度器,延迟发布的优势不就没了么?

补充

和朋友讨论了一下,ucos的延迟发布的主要作用是为了减少中断执行的时间。这一点从内核函数在使用延迟发布方式前会检查中断嵌套层数是否为0能看出来,也就是说如果不是在中断中压根就不会使用延迟发布,例如任务在发布信号量的时候还是直接调用的内部函数OS_SemPost。
至于减小关中断时间,则是由于OS_CRITICAL_ENTER/EXIT在使能/失能延迟发布时的定义不同,所以在调用发布的底层函数时会出现关中断时间减少。

你可能感兴趣的:(uC/OS,ucos,临界区,延时发布)