|____2.6 FreeRTOS 深度解析--任务挂起 / 恢复

|____2.6 FreeRTOS 深度解析--任务挂起 / 恢复 任务挂起 / 恢复1. 任务挂起 vTaskSuspend()2. 所有任务挂起(挂起调度器) vTaskSuspendAll()3. 任务恢复 vTaskResume()4. 任务恢复(专 门 用 在 中 断 服 务 程 序 中 ) xTaskResumeFromISR()5. 调度器恢复 xTaskResumeAll()1. 任务挂起 vTaskSuspend()任务可以调用 vTaskSuspend() 这个函数来挂起任务自身但是在挂起自身的时候会进行一次任务上下文切换需要挂起自身就将 xTaskToSuspend 设置为 NULL 传递进来即可。无论任务是什么状态都可以被挂起只要调用了 vTaskSuspend() 函数就会挂起成功不论是挂起其他任务还是挂起任务自身。将挂起任务从就绪列表pxReadyTasksLists、延时列表xDelayedTaskList、事件列表xEventListItem中删除将挂起任务添加到挂起列表xSuspendedTaskList进行任务切换vTaskSwitchContext()(1)如果想要使用任务挂起函数 vTaskSuspend()则必须将宏定义 INCLUDE_vTaskSuspend 配置为 1(2)xTaskToSuspend 是挂起指定任务的任务句柄任务必须为已创建的任务可以通过传递 NULL 来挂起任务自己(3)利用任务句柄 xTaskToSuspend 来获取任务控制块通过调用 prvGetTCBFromHandle() 函数得到对应的任务控制块(4)从就绪/阻塞列表中删除即将要挂起的任务。然后更新最高优先级变量 uxReadyPriorities目的是维护这个变量这个变量的如下功能在使用通用方法找到最高优先级任务时它用来记录最高优先级任务的优先级。在使用硬件方法找到最高优先级任务时它的每一位共 32bit的状态代表这个优先级上边有没有就绪的任务(5)如果任务在等待事件也将任务从等待事件列表中移除(6)将任务状态添加到挂起列表xSuspendedTaskList中(7)重置下一个任务的解除阻塞时间。重新计算一下还要多长时间执行下一个任务如果下个任务的解锁刚好是被挂起的那个任务那么就是不正确的了因为挂起的任务对调度器而言是不可见的所以调度器是无法对挂起态的任务进行调度所以要重新从延时列表中获取下一个要解除阻塞的任务(8)如果挂起的是当前运行中的任务并且调度器已经是运行的则需要立即切换任务。不然系统的任务就错乱了这是不允许的(9)调度器未运行(xSchedulerRunning pdFALSE )但 pxCurrentTCB 指向的任务刚刚被挂起所以必须重置 pxCurrentTCB 以指向其他可以运行的任务(10)首先调用函数 listCURRENT_LIST_LENGTH() 判断一下系统中所有的任务是不是都被挂起了也就是查看列表 xSuspendedTaskList 的长度是不是等于uxCurrentNumberOfTasks事实上并不会发生这种情况因为空闲任务是不允许被挂起和阻塞的必须保证系统中无论如何都有一个任务可以运行(11)如果没有其他任务准备就绪因此将 pxCurrentTCB 设置为 NULL在创建下一个任务时 pxCurrentTCB 将重新被设置。但是实际上并不会执行到这里因为系统中的空闲任务永远是可以运行的(12)有其他可运行的任务则切换到其他任务2. 所有任务挂起(挂起调度器) vTaskSuspendAll()(1)uxSchedulerSuspended 用于记录调度器是否被挂起该变量默认初始值为 pdFALSE表明调度器是没被挂起的每调用一次 vTaskSuspendAll()函数就将变量加一用于记录调用了多少次 vTaskSuspendAll()函数3. 任务恢复 vTaskResume()无论任务在挂起时候调用过多少次这个vTaskSuspend()函数也只需调用一次vTaskResume()函数即可将任务恢复运行当然无论调用多少次的vTaskResume()函数也只在任务是挂起态的时候才进行恢复。将要恢复的任务从挂起列表xSuspendedTaskList中删除将要恢复的任务添加到就绪列表pxReadyTasksLists中去如果恢复的任务优先级比当前正在运行的任务优先级更高则需要进行任务切换(1)如果想要使用任务恢复函数 vTaskResume()则必须将宏定义 INCLUDE_vTaskSuspend 配置为 1(2)xTaskToResume 是恢复指定任务的任务句柄(3)根据 xTaskToResume 任务句柄获取对应的任务控制块(4)检查要恢复的任务是存在如果不存在调用恢复任务函数没有任何意义(5)pxTCB 任务控制块指针不能为 NULL肯定要已经挂起的任务才需要恢复同时要恢复的任务不能是当前正在运行的任务因为当前正在运行运行态的任务不需要恢复只能恢复处于挂起态的任务(6)进入临界区防止被打断(7)判断要恢复的任务是否真的被挂起了如果被挂起才需要恢复没被挂起那当然也不需要恢复(8)将要恢复的任务从挂起列表xSuspendedTaskList中删除(9)将要恢复的任务添加到就绪列表pxReadyTasksLists中去任务从挂起态恢复为就绪态(10)如果恢复的任务优先级比当前正在运行的任务优先级更高则需要进行任务的切换调用taskYIELD_IF_USING_PREEMPTION()进行一次任务切换(11)退出临界区4. 任务恢复(专 门 用 在 中 断 服 务 程 序 中 ) xTaskResumeFromISR()无 论 通 过 调 用 一 次 或 多 次vTaskSuspend()函数而被挂起的任务也只需调用一次xTaskResumeFromISR()函数即可解挂。当函数的返回值为 pdTRUE 时恢复运行的任务的优先级等于或高于正在运行的任 务 表 明 在 中 断 服 务 函 数 退 出 后 必 须 进 行 一 次 上 下 文 切 换 使 用 portYIELD_FROM_ISR()进行上下文切换。当函数的返回值为 pdFALSE 时恢复运行的任务的优先级低于当前正在运行的任务表明在中断服务函数退出后不需要进行上下文切换xTaskResumeFromISR() 通常被认为是一个危险的函数因为它的调用并非是固定的中断可能随时来来临。所以xTaskResumeFromISR()不能用于任务和中断间的同步如果中断恰巧在任务被挂起之前到达这就会导致一次中断丢失任务还没有挂起调用 xTaskResumeFromISR()函数是没有意义的只能等下一次中断。这种情况下可以使用信号量或者任务通知来同步就可以避免这种情况(1)xTaskToResume 是恢复指定任务的任务句柄(2)定义一个是否需要进行任务切换的变量 xYieldRequired默认为 pdFALSE当任务恢复成功并且需要任务切换的话则重置为 pdTRUE以表示需要进行任务切换。(3)根据 xTaskToResume 任务句柄获取对应的任务控制块(4)定义一个变量 uxSavedInterruptStatus 用于保存关闭中断的状态(5)检查要恢复的任务是存在如果不存在调用恢复任务函数没有任何意义(6)调用 portSET_INTERRUPT_MASK_FROM_ISR()函数设置 basepri 寄存器用于屏蔽系统可管理的中断防止被处理被其他中断打断当 basepri 设置为 configMAX_SYSCALL_INTERRUPT_PRIORITY 的时候该宏在 FreeRTOSConfig.h 中定义现在配置为 5会让系统不响应比该优先级低的中断而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为 5 的时候中断优先级数值在 0、1、2、3、4 的这些中断是不受 FreeRTOS 管理的不可被屏蔽而中断优先级在 5 到 15 的中断是受到系统管理可用被屏蔽的(7)判断要恢复的任务是否真的被挂起了如果被挂起才需要恢复没被挂起那当然也不需要恢复(8)检查可以访问的就绪列表检查调度器是否被挂起如果没有被挂起则继续执行(9)(10)的程序内容(9)如果刚刚恢复的任务优先级比当前任务优先级更高需要进行一次任务的切换重置 xYieldRequired pdTRUE 表示需要进行任务切换(10)可以访问就绪列表因此可以将任务从挂起列表中删除然后添加到就绪列表中(11)因为 uxSchedulerSuspended 调度器被挂起无法访问就绪列表因此任务将被添加到待处理的就绪列表中直到调度器被恢复再进行任务的处理(12)调用 portCLEAR_INTERRUPT_MASK_FROM_ISR()函数清除 basepri 的设置恢复屏蔽的中断(13)返回 xYieldRequired 结果在外部选择是否进行任务切换5. 调度器恢复 xTaskResumeAll()调用了多少次vTaskSuspendAll()函数就必须同样调用多少次xTaskResumeAll()函数(1)断言如果 uxSchedulerSuspended 为 0则此函数与先前对 vTaskSuspendAll()的调用次数不匹配也就是说明没有调用过不需要调用 vTaskSuspendAll() 函数不需要调用 xTaskResumeAll()恢复调度器(2)进入临界区(3)我 们 知 道 每 调 用 一 次 vTaskSuspendAll() 函数就会将 uxSchedulerSuspended 变量加一那么调用对应的 xTaskResumeAll()肯定就是将变量减一(4)如果调度器恢复正常工作也就是调度器没有被挂起就可以将所有待处理的就绪任务从待处理就绪列表 xPendingReadyList 移动到适当的就绪列表中(5)当待处理就绪列表xPendingReadyList中是非空的时候就需要将待处理就绪列表中的任务移除添加到就绪列表中去(6)如果移动的任务的优先级高于当前任务需要进行一次任务的切换重置 xYieldPending pdTRUE 表示需要进行任务切换(7)在调度器被挂起时任务被解除阻塞这可能阻止了重新计算下一个解除阻塞时间在这种情况下需要重置下一个任务的解除阻塞时间。调用 prvResetNextTaskUnblockTime()函数将从延时列表中获取下一个要解除阻塞的任务(8)如果在调度器挂起这段时间产生滴答定时器的计时并且在这段时间有任务解除阻塞由于调度器的挂起导致没法切换任务当恢复调度器的时候应立即处理这些任务。这样既确保了滴答定时器的计数不会滑动也保证了所有在延时的任务都会在正确的时间恢复(9)调用 xTaskIncrementTick()函数查找是否有待进行切换的任务如果有则应该进行任务切换(10)如 果 需 要 任 务 切 换 则 调 用 taskYIELD_IF_USING_PREEMPTION() 函数发起一次任务切换(11)退出临界区