浅析FreeRTOS_v4.5.0的任务切换原理和栈结构
文章来源:http://gliethttp.cublog.cn[转载请声明出处]
FreeRTOS的更新速度很快,基本2、3个月就会出现一个新版本,看来一直在完善和各种功能增加中,截止
2007/09/27日为止,FreeRTOS_v4.5.0是最新版本,下面研究一下FreeRTOS_v4.5.0在at91sam7s64处理器
上的任务切换代码.
当有更高优先级的task任务就绪或者当前task任务因为vTaskDelay()延时或者消息事件等待而主动让出cpu
时,FreeRTOS_v4.5.0会在适当的位置执行taskYIELD();进行task进程调度,让更应该持有cpu的task获得
持有权.
1.触发软中断执行处理器对应的swi处理函数
//在/source/include/Task.h中有如下定义:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
//在/source/portable/iar/atmelsam7s64/Portmacro.h中有如下定义:
//只是借壳下蛋,中断号0,其值本身没有意义,因为swi处理函数中,并不会使用到中断号值,
//这和linux的系统调用有些出入,linux的系统调用,中断号值对应了相应系统调用处理函数
//的索引值,可以参看《浅析arm-linux系统调用的流程 》//文章地址:http://blog.chinaunix.net/u1/38994/showart_331915.html#define portYIELD() asm ( "SWI 0" )
因此可以看出来FreeRTOS_v4.5.0使用软中断触发task的调度函数,来看看软中断入口函数:
//在/demo/arm7_at91sma7s64_iar/srciar/Cstratup.s79中有中断向量处理代码: B InitReset ; 0x00 Reset handler
undefvec:
B undefvec ; 0x04 Undefined Instruction
swivec://很明显portYIELD()调用之后将直接导致cpu去进一步执行vPortYieldProcessor B vPortYieldProcessor ; 0x08 Software Interrupt
pabtvec:
B pabtvec ; 0x0C Prefetch Abort
dabtvec:
B dabtvec ; 0x10 Data Abort
rsvdvec:
B rsvdvec ; 0x14 reserved
irqvec:
LDR PC, [PC, #-0xF20] ; Jump directly to the address given by the AIC
//在/source/portable/iar/atmelsam7s64/Portasm.s79中定义了vPortYieldProcessor处理函数vPortYieldProcessor:
//lr+4是为了调整lr的值,使其和IRQ中断进入的模式值一致 ADD LR, LR, #4
//接下来就可以安全的认为是由于IRQ中断引起的任务进、出栈(gliethttp) portSAVE_CONTEXT//保存上、下文 LDR R0, =vTaskSwitchContext
mov lr, pc
BX R0//计算出优先级最高的任务//恢复vTaskSwitchContext函数计算出的优先级最高任务的上、下文,进而在cpu中运行优先级最高任务. portRESTORE_CONTEXT
2.FreeRTOS_v4.5.0在at91sam7s64上的任务切换出、入栈汇编代码分析
///source/portable/iar/atmelsam7s64/ISR_Support.h中定义了这两个汇编子函数<2.1>portSAVE_CONTEXT入栈宏
在进行分析下面这段代码之前,首先必须清楚FreeRTOS_v4.5.0在at91sam7s64上使用的模式情况
首先,上电之后sam64工作在svc模式,当完成所有sam64自身的初始化工作以及xTaskCreate()之后,
仍然处于svc模式,只有当调用vTaskStartScheduler()->xPortStartScheduler()->vPortStartFirstTask()
之后sam64就随着portRESTORE_CONTEXT()的执行使得task进入sys模式下执行了,
所以其实最后任务将在sys模式下执行,这也是pxPortInitialiseStack()所定义的spsr的模式值
portSAVE_CONTEXT MACRO
STMDB SP!,{R0}//R0推入swi专用栈 STMDB SP, {SP}^//将sys模式下的sp值推入当前swi模式下的sp中 NOP
SUB SP, SP, #4//swi堆栈调整到执行STMDB SP!, {SP}^之后的值 LDMIA SP!,{R0}//把swi栈中存放的sys模式下的sp值转存到r0中 STMDB R0!,{LR}//将swi模式中的lr推入sys模式下的堆栈(gliethttp) MOV LR, R0//将sys模式下的堆栈值转用lr临时存储 LDMIA SP!,{R0}//现在sys模式下的sp堆栈已经使用swi下的lr代替,所以r0已经不再被使用, //swi模式下的sp已经释放,r0恢复到了sys模式下的r0值 STMDB LR, {R0-LR}^//把sys下的r0-lr的数据推入sys模式下的sp堆栈中 NOP
SUB LR, LR, #60//调整lr,使其等于sys模式下的sp堆栈位置 MRS R0, SPSR//r0已经入栈,所以可以使用;spsr中含有IRQ和FIQ的中断时能否标志以及sys模式标志 STMDB LR!,{R0}//将spsr入栈 LDR R0, =ulCriticalNesting
LDR R0, [R0]
STMDB LR!,{R0} //将ulCriticalNesting入栈 LDR R1, =pxCurrentTCB
LDR R0, [R1]
STR LR, [R0]//pxCurrentTCB为task进程的上下文存储单元,其中第一项为pxTopOfStack本task的栈顶值 ENDM
所以入栈后sys栈中的数据为:
pc //task被抢占执行到的地址值r14
...
r0
spsr
ulCriticalNesting
pxCurrentTCB->pxTopOfStack
一共18项
<2.2>portRESTORE_CONTEXT出栈宏
portRESTORE_CONTEXT MACRO
LDR R1, =pxCurrentTCB//pxCurrentTCB对应当前切换出去之后需要执行的task的上下文 //它的数据结构体偏移0处就是task的pxTopOfStack堆栈顶值 LDR R0, [R1]
LDR LR, [R0]//task的sp存放到swi的lr LDR R0, =ulCriticalNesting
LDMFD LR!,{R1}
STR R1, [R0]//恢复新task栈中的ulCriticalNesting到全局量ulCriticalNesting LDMFD LR!,{R0}
MSR SPSR_cxsf, R0//将新task的spsr恢复到SPSR_cxsf中 LDMFD LR, {R0-R14}^//恢复新task的r0-r14寄存器 NOP
LDR LR, [LR, #+60]//调整sp指针,并读取pc值 SUBS PC, LR, #4//因为是在ISR模式下的lr,所以需要+4调整,执行新task,同时恢复SPSR_cxsf到CPSR ENDM
以上汇编设计的很精巧,短短几行就完成了数据的保存与恢复,还有一个设计的很精巧的地方就是第一次
启动FreeRTOS的启动函数vTaskStartScheduler()->xPortStartScheduler()->vPortStartFirstTask()
//在source/portable/iar/atmelsam7s64/Portasm.s79中定义vPortStartFirstTask:
portRESTORE_CONTEXT//很巧妙,先将task从TCB中恢复,之后r0~lr、spsr、ulCriticalNesting
//和pxCurrentTCB->pxTopOfStack就真的是那个task执行起来的环境值了