CORTEX_M4F_STM32F407ZG-SK为例,参考书籍Cortex-m权威指南
系统启动:
/* Start the scheduler. */
vTaskStartScheduler()(main.c)-----> vTaskStartScheduler()(tasks.c,设置节拍定时器和启动第一个任务,开始系统正常运行调度)
--xPortStartScheduler(port.c)---->vPortStartFirstTask(port.c)--->
vPortStartFirstTask 启动第一个任务,函数重新初始化系统的栈指针,表示 freertos 开始接手平台控制,同时出发svc系统调用,运行第一个任务。
FreeRTOSv10.2.0\FreeRTOS\Source\tasks.c void vTaskStartScheduler( void )
FreeRTOSv10.2.0\FreeRTOS\Source\portable\GCC\ARM_CM4F\port.c 汇编实现,如 xPortPendSVHandler xPortStartScheduler( void )
PendSV异常:
1、在PendSV中断服务函数中实现任务切换具体过程。
2、将中断寄存器BIT28置1,产生PendSV中断。
BIT28置1后,将PendSV中断设置为挂起状态,PendSV优先级比较低,等到优先级高于PendSV的中断执行完后,PendSV中断服务程序才会执行,进行切换工作。
任务切换场合:
1、执行系统调用,如:taskYIELD(), vTaskDelay() -->portYIELD_WITHIN_API() -----> portYIELD()(设置中断寄存器BIT28置1),中断服务程序使用portYIELD_FROM_ISR强制切换任务。
2、systick中断服务函数。(系统滴答时钟)
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD() (portmacro.h,设置中断寄存器BIT28置1)
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
异常处理函数:
FreeRTOSConfig.h
#define xPortPendSVHandler PendSV_Handler
FreeRTOSv10.2.0\\FreeRTOS\Demo\CORTEX_M4F_Infineon_XMC4000_Keil\startup_XMC4200.s
ExcpVector PendSV_Handler ; PendSV Handler
xPortPendSVHandler --->vTaskSwitchContext --->taskSELECT_HIGHEST_PRIORITY_TASK
xPortPendSVHandler
1、判断是否使用FPU,如果使用的话将S16-S31入栈,EXC_RETURN 当处理异常的时候bit4会被CONTROl的FPCA位替代,判断EXC_RETURN的bit4是否为1.
2、R4-R11 R14入栈。
3、关闭中断
4、调用vTaskSwitchContext,获取下一个要运行任务。
4、打开中断
6、获取要切换任务的栈顶指针。
7、R4-R11,R14出栈。
8、判断是否使用FPU,如果使用的话S16-S31出栈。
9、任务切换完成。
总结:
msp和psp指针切换,堆栈保存,任务切换,入栈出栈,FPU浮点单元
vTaskSwitchContext()
1、判断 uxSchedulerSuspended 是否为pdFALSE,任务调度器有没有被挂起,如果被挂起xYieldPending赋值为pdTRUE。
2、如果任务调度器没有挂起,查找下一个要运行的任务。
3、调用taskSELECT_HIGHEST_PRIORITY_TASK获取下一个要运行的任务,通用方法或者硬件方法。
taskSelect_HIGHEST_PRIOPITY_TASK
通过configUSE_PORT_OPTIMISED_TASK_SELECTION宏选择软件还是硬件,
硬件方法:
硬件(设置为1)效率高,优先级数目32,需要体系结构支持。
1、调用portGET_HIGHEST_PRIORITY获取最高优先级任务。
2、listGET_OWNER_OF_NEXT_ENTRY获取下一个要运行任务的任务控制块,并将其保存在pxCurrentTCB.
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) ucPortCountLeadingZeros( ( uxReadyPriorities ) ) )
uxTopReadyPriority使用每一位来表示任务,bit0位1,表示存在优先级位0的就绪任务,32位整数最多32位,优先级0~31.
__clz( (uxTopReadyPriority) 是什么意思, __clz() 会被汇编指令CLZ替换掉,这个指令用来计算一个变量从最高位开始的连续零的个数。
软件方法:
1、获取处于就绪态的优先级最高的任务,直接读取 uxTopReadyPriority 即可。
2、listGET_OWNER_OF_NEXT_ENTRY 获取下一个要运行任务的任务控制块,并将其保存在 pxCurrentTCB。
中断退出后,需要做什么判断?
任务切换,执行寄存器PC中保存的任务函数。
抢占如何实现?
主动抢占:系统调用或者systick进行抢占,会查找运行高优先级的任务。获取cpu运行权。
被动抢占:systick调度器进行抢占。通过时间片调度。
任务阻塞,延迟、信号量等待等阻塞式 API来释放cpu运行权(此任务挂起),运行其他高优先级任务(当延迟时间到,获取到信号量等将会进行抢占,返回上一次任务)。
抢占式调度器,当前的任务要么被高优先级任务抢占,要么通过调用阻塞式 API 来释放 CPU 使用权让低优先级任务执行,没有用户任务执行时就执行空闲任务
系统在每一次节拍计数器中断服务程序xPortSysTickHandler(平台实现 port.c 中) 中调用处理函数 xTaskIncrementTick, 依据该函数返回值判断是否需要触发 PendSV 异常, 进行任务切换。
涉及任务时间片轮循, 任务阻塞超时, 以及结束以此实现的延时函数。
FreeRTOS会在关键区即taskENTER_CRITICAL()和taskEXIT_CRITICAL()包裹的区间中,执行进程切换。即在关闭中断的时候,进行进程切换。
我们已经知道,即便关闭中断,PowerPC的sc中断,还是可以得到响应。但是时钟中断呢?这是个外部中断,无法得到响应。
那么是何时打开的中断呢?新进程切入之后,立即打开中断?在多个TASK级别做开关中断配对?
触发任务切换的两种情况 :
高优先级任务就绪抢占和同优先级任务时间共享(包括提前挂起)。
系统中,时间延时和任务阻塞,时间片都以 Systick 为单位。
xTaskIncrementTick()
系统每次节拍中断服务程序中主要任务由函数 xTaskIncrementTick 完成。
在任务调度器没有挂起的情况下( xTaskIncrementTick != pdFALSE ),该函数主要完成 :
判断节拍计数器 xTickCount 是否溢出, 溢出轮换延时函数队列
判断是否有阻塞任务超时,取出插入就绪链表
同优先级任务时间片轮
而当任务调度器被挂起时, 该函数累加挂起时间计数器 uxPendedTicks, 调用用户钩子函数, 此时,正在运行的任务不会被切换, 一直运行。
当恢复调度时, 系统会先重复调用 xTaskIncrementTick 补偿 (uxPendedTicks次)。
不管, 系统调度器是否挂起, 每次节拍中断都会调用用户的钩子函数 vApplicationTickHook。 由于函数是中断中调用,不要在里面处理太复杂的事情!!