freertos 定时器 不启动_Freertos阅读笔记4~ Systick异常处理,任务调度流程

4 freertos任务调度源码分析

freertos之所以能够准确的按照配置的时间片进行任务调度完全依靠硬件支持。硬件上的某个计数器会提供周期性中断,在中断处理中解决任务调度 如:task1切换到task2,task2再切换到task1,如此循环往复,在外部就表现的如同多个任务在一起执行。

在CM3/CM4上有提供内部异常定时器:systick。使用systick作为实时系统的运行心脏再好不过,因为人家的名字就叫做系统滴答定时器,可谓门当户对。其它定时器也可以完成类似功能,因为freertos只关注能否提供周期中断,并不会在意中断产生着是白猫还是黑猫。使用systick的一个好处是在开发低功耗功能时它将不会受到影响。

在调度器启动之前已经使能了systick,systick中断发生后进入xPortSysTickHandler异常。在xPortSysTickHandler中没有发生任务切换,它只是利用xTaskIncrementTick函数处理了readylist与delaylist上的任务结点,需要进行任务切换时会交给pendSV来完成。

d9f87ae991ecb8fd6cbfe5b75949bdc9.png

(调度器流程图)(拖动放大)

4.1 xTaskIncrementTick

在进入xTaskIncrementTick之前首先屏蔽了中断,防止发生中断嵌套破坏内核链表。

xTaskIncrementTick中率先自增了xTickCount。xTickCount能够准确表示systick发生了多少次中断,它也理所当然成为freertos中的时间基石。很多api调用与时间有关,例如 某个任务需要等待一个信号量,可以给它设置等待超时,超过一定时间放弃等待;或者某个任务需要被delay一段时间再被重新调度。

4.1.1 链表交换

调度器使用xTickCount记录内核启动时间,一个TCB使用节点上的xItemValue值表示下一次事件触发的时间。假设当前xTickCount值为5,systick 1ms触发一次中断,此时运行的某个任务使用了vTaskDelay来挂起10ms,那么TCB上的xItemValue会被赋值为15,等到xTickCount累加到15时将任务拉回就绪态。

时间值一直累加总会有溢出的情况发生。为了更快的故意达到此效果,假设TCB上的xItemValue值与xTickCount都使用1字节存储。当xTickCount值为230时,某个任务调用了vTaskDelay挂起30ms,xItemValue值累加后发生溢出变成了4,调度器再次比较xTickCount与xItemValue值时就会认为此任务到达唤醒时间(小于xTickCount值都会被唤醒),vTaskDelay成为无效的调用。

xDelayedTaskList1和xDelayedTaskList2解决了tick值溢出的问题,当累加后xTickCount与xItemValue值比累加之前还要小时可以判断为溢出发生,插入的链表也要发生变化,如图:

5009619bbe68205a682ebd9681f20012.png

pxDelayedTaskList与pxOverflowDelayedTaskList两个指针分别指向xDelayedTaskList1和xDelayedTaskList2两条链表,若有xItemValue值发生溢出则插入pxOverflowDelayedTaskList中。

pxDelayedTaskList链表上的任务将等待xTickCount自增到某一值后重新被唤醒,然而

pxOverflowDelayedTaskList链表上的任务永远不会被唤醒,直到xTickCount值发生溢出将两个指针互换:

8e22ffd408874dc3dd0fadb21a14cf62.png

发生交换后,原来pxOverflowDelayedTaskList上的结点就能以准确的tick值被唤醒,如此重复,整个链表就能得以正确运行调度。

4.1.2 pxDelayedTaskList :检查挂起任务是否需要唤醒

每当有节点插入pxDelayedTaskList时会更新一次xNextTaskUnblockTime变量值,在pxDelayedTaskList上的任务都是需要“睡眠”一段时间,等到唤醒后重新插入pxReadyTasksLists参与调度。pxDelayedTaskList上的结点是按照升序插入,xNextTaskUnblockTime值会一直等于链表上第一个节点的值,也就是需要遍历pxDelayedTaskList链表的最小时间,这样可以减少在systick中断里遍历pxDelayedTaskList的次数。

4.1.3 最高优先级任务间的切换

if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )

检查当前优先级的任务数是否大于1,大于的话则使能pendSV。有了这行代码,在freertos中多个优先级相同的高优先级任务会按照时间片分配cpu资源。如果当前优先级任务数为1的话则放弃调度,cpu资源将会一直被当前任务占据,直到它被某个操作挂起才能轮到下一个较低优先级的任务执行。

4.2 pendSV

PendSV是CM3/CM4中能够被“缓期执行”一个异常,悬起 PendSV方法是手工向 NVIC 的 PendSV 悬起寄存器中写 1。

CM3权威指南:

PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。

需要把 PendSV 编程为最低优先级的异常(PendSV中断返回时需要置cpu状态为线程模式(LR位段控制),如果 OS 在某中断活跃时尝试切入线程模式,将触犯用法 fault 异常)。

xPortPendSVHandler是pendSV异常处理函数,由汇编实现,主要更新了重要寄存器和调用vTaskSwitchContext进行任务切换。

4.2.1 vTaskSwitchContext:pendSV中切换新任务

前面准备了一大堆中断屏蔽以及链表遍历等工作,都为的是这一刻的辉煌!在vTaskSwitchContext中pxCurrentTCB值终于被替换掉,新的任务被分配到cpu资源,调度由此产生。

在更换pxCurrentTCB之前还要检查当前任务栈是否溢出,本着认真负责的态度它还检查了两次。开发者在开发过程中可能只计算了自己的变量在任务中消耗的栈空间,并且“恰到好处的”分配了任务栈空间,若异常发生时由硬件自动压入的几个寄存器值很有可能会超出栈的设定范围(异常响应时若当前使用PSP则压入PSP,若使用MSP则压入MSP,进入异常后一直使用MSP),一个TCB中会记录当前任务栈的栈顶和栈底,如果栈顶地址超过栈底则发生溢出。所以只要内存够还是把栈搞得尽量大比较好。

获取当前最高任务优先级后,从pxReadyTasksLists中取出新TCB赋给pxCurrentTCB指针就完成了他的使命。

4.2.2 xPortPendSVHandler汇编分析

xPortPendSVHandler: mrs r0, psp //保存当前任务PSP地址到R0中 ldr r3, pxCurrentTCBConst //获取pxCurrentTCBConst指针地址 ldr r2, [r3] //R2被赋予当前TCB地址 stmdb r0!, {r4-r11} //R4-R11压入PSP str r0, [r2] //新的PSP地址存入到TCB中 stmdb sp!, {r3, r14} //压R3,R14值至MSP mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY //或缺中断屏蔽阈值  msr basepri, r0 //中断屏蔽 dsb isb bl vTaskSwitchContext //调用vTaskSwitchContext mov r0, #0  msr basepri, r0 //打开中断 dsb isb ldmia sp!, {r3, r14} //出栈R3,R14  ldr r1, [r3] //R3依然是pxCurrentTCBConst指针地址,R1变为新TCB地址 ldr r0, [r1] //R0值成为新TCB的栈地址(该TCB发生上一次调度的PSP值) ldmia r0!, {r4-r11} //出PSP msr psp, r0 //更新PSP  bx r14 //LR位段控制返回线程模式并使用PSP作为SP  .align 2 pxCurrentTCBConst: .word pxCurrentTCB

4.2.3 图解任务切换流程

调度时不只是更换pxCurrentTCB值,R0-R15等寄存器都要进行大换血才能恢复到新任务的上一次运行环境:

07f7bbcb7a4b3edadae6b1d588540af5.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值