第 四 章 FreeRtos的调度机制探究

任务调度简介

       一个处理器核心在某一时刻只能运行一个任务,如果在各个任务之间迅速切换,这样看起来就像多个任务在同时运行。操作系统中任务调度器的责任就是决定在某一时刻要执行哪个任务

   FreeRTOS使用基于优先级的抢占式任务调度策略。

  • 抢占式调度:FreeRTOS采用抢占式调度方式,允许更高优先级的任务在任何时刻抢占正在执行的低优先级任务。这确保了高优先级任务能够及时响应,并提高了系统的实时性。
  • 时间片轮转:在相同优先级的任务之间,FreeRTOS采用时间片轮转策略。每个任务执行一个时间片,如果有其他同优先级的任务等待执行,则切换到下一个任务。这有助于公平地分配CPU时间。
  • 协程式调度(废弃 不用了解)

但是并不是说高优先级的任务会一直执行,导致低优先级的任务无法得到执行。如果高优先级任务等待某个资源(延时或等待信号量等)而无法执行,调度器会选择执行其他就绪的高优先级的任务。

以上是关于FreeRtos调度机制的简单介绍,下面我将通过问答的方式 来进行更深层次的剖析。

1. 什么是任务? 任务的优先级有什么作用,有什么特点?如何设置优先级?取值范围?
     FreeRTOS 中,任务是独立的执行单元,本质是一个无限循环的函数,拥有独立的堆栈和任务控制块(TCB)

     任务的优先级就是为了区分任务的重要程度和实时性。比如有 传感器采集任务、数据处理任务 、显示更新任务 

             传感器采集任务需实时响应物理信号(如超时可能丢失数据),实时性要求最高;

             数据处理依赖采集的原始数据,需在数据有效时及时处理,实时性次之;

             显示更新对实时性要求较低(延迟几毫秒用户通常无感知),优先级可最低。

            那么我们可以设置 传感器采集任务 为 3, 数据处理为 2 ,显示更新任务为1  

                                               
    任务的优先级在创建任务时由用户指定,后期也可以通过函数修改。在FreeRTOSConfig.h中    #define configMAX_PRIORITIES   ( 6 ) 

    该配置确定我们的任务的优先级最大值为6  可取值为 0-5 

     在stm32中受硬件影响我们该配置最大值可以设置为32,可取值 0-31  在实际使用中 根据我们的情况配置。

    任务相关的详细信息我会放在后面继续介绍,在这介绍只是为了方便理解调度器。

 2. 什么是时间片?时间片的来源是什么?一个时间片是多少时间?

     当多个同优先级任务处于就绪状态时,调度器不会让某个任务一直占用 CPU,而是让它们轮流获得一段固定的 CPU 执行时间

      这段分配给每个任务的固定时长就称为 “时 间片”。

      时间片的来源于系统滴答定时器(SysTick)每产生一次滴答中断 都代表一个时间片

   时间片时长由滴答时钟频率决定。 在FreeRTOSConfig.h中      #define configTICK_RATE_HZ            ( ( TickType_t ) 1000 )

      1/1000  则 1 个时间片为 1 毫秒

3. 什么情景下需要切换任务? 在什么时候检测是否需要切换任务?  任务的切换是如何完成的?

   切换任务的4种情景:

            情景1  高优先级的任务被唤醒: 从 “阻塞态” 转为 “就绪态”

                             高优先级的任务由于等待某种资源如信号量 处于"阻塞态"

                             此时低优先级的任务正在执行 内部释放了信号量 。调度器会立即唤醒高优先级任务 并触发任务切换

                             即便低优先级的任务当前的时间片没有用完       

           情景2   当前任务主动放弃 CPU: 从 “执行态” 转为 “阻塞态”   
                           当前任务调用阻塞类API 如延时 等待信号量等  进入阻塞态。调度器会立即从就绪列表中选择优先级最高的任务。

                           关于就绪列表,下面会有讲解。

           情景3    当前时间片用完 : 多个同级的任务,当前任务执行完一个时间片,会切换到下一个同级就绪任务 执行

           情景4     主动强制切换: 任务调用taskYIELD()主动让出 CPU,触发切换到同级或更高优先级的就绪任务(通常用于优化调度效率)

     切换任务检测: 

            1. 当任务主动调用 释放资源的API 如xSemaphoreGive()、xQueueSend()、vTaskNotifyGive() taskYIELD() 等 在函数内部会触发检测

            2. 当滴答定时器产生中断时会触发检测。滴答定时器的中断函数被FreeRtos重新定义了 在内部会执行检测。

             以上两种情况都是 FreeRtos主动管理的,还有一种需要用户自定义来检测的情况。

           3. 当产生用户自定义的中断(uart中断 外部中断 定时器中断 等)并且 中断函数中释放了资源或信号量 。这种情况是不被FreeRtos管理的。

               那如果有高级的的任务因此被唤醒了咋么办?FreeRtos针对这种情况提供了一套完整策略。

                         第一步 在中断函数中定义一个变量,用来识别是否有更高级的任务被唤醒。

                         第二步 将该变量传递给释放资源的函数 如果导致更高级别任务被唤醒 这个函数就修改变量的值

                         第三步 当发现变量被改变  用户需要调用函数  发出切换任务的"指示"

                         第四步 当中断函数执行完 根据指示进行任务切换
               上面的3种检测 如果 检测到了更高级的任务 都会发出 切换任务的"指示",  这个任务的指示是什么呢?

                       这个指示就是 PendSV 的中断标志位置1 ,那有什么用 ?请往下看

    任务的切换是如何完成的?

                调度器在指示切换任务时 会把 PendSV 的中断标志位置1   当NVIC控制器检测到标志后 立即触发中断函数 PendSVHandler(PendSV的中断优先级为最低 他必须等到其他中断都执行完 才会触发
                  PendSVHandler是任务切换的核心函数。这个函数被FreeRtos重新定义了  在port.c文件中 该函数使用汇编代码写的

                  函数总结下来分为4个步骤:

                                        保存当前任务上下文   -> 调用调度器选择下一个任务  ->  恢复新任务的上下文 -> 返回新任务继续执行

                   具体的函数代码 我放在下面了。

如果大家对调取方面 有其他的问题?可以打在评论区  我会挑合适的  补充进来

 PendSV的中断函数的具体实现

/* portable/GCC/ARM_CM3/portasm.s */

        __asm void xPortPendSVHandler( void )

        {
            PRESERVE8
            /* 保存r0-r3, r12, lr, pc, xPSR到当前任务的堆栈 */
            mrs r0, psp                  /* 获取当前任务的堆栈指针(PSP) */
            isb
            /* 保存r4-r11寄存器到当前任务的堆栈 */
            push {r4-r11}
            /* 调用vTaskSwitchContext()选择下一个任务 */
            ldr r1, =pxCurrentTCB        /* 获取pxCurrentTCB的地址 */
            ldr r1, [r1]                 /* 获取pxCurrentTCB指向的TCB */
            str r0, [r1]                 /* 保存当前堆栈指针到TCB->pxTopOfStack */
            /* 调用调度器函数,选择下一个任务 */
            bl vTaskSwitchContext
            /* 恢复新任务的堆栈指针 */
            ldr r1, =pxCurrentTCB        /* 获取pxCurrentTCB的地址 */
            ldr r1, [r1]                 /* 获取pxCurrentTCB指向的新TCB */
            ldr r0, [r1]                 /* 从新TCB中获取堆栈指针 */
            /* 恢复r4-r11寄存器 */
            pop {r4-r11}
            /* 恢复r0-r3, r12, lr, pc, xPSR */
            msr psp, r0                  /* 设置PSP为新任务的堆栈指针 */
            isb
            /* 返回新任务的上下文,从新任务的断点处继续执行 */
            bx lr
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值