FreeRTOS启动第一个任务和任务的切换实现过程

FreeRTOS启动第一个任务和任务的切换实现过程

此篇文章主要参考了野火的《FreeRTOS内核实现与应用开发指南》,和其他博主的一些资料并加入了一些个人理解,作为学习笔记,在此感谢火哥和其他博主;

第一个任务的启动

vPortSVCHandler函数开始真正启动第一个任务;
上代码:

__asm void vPortSVCHandler( void )
{
	extern pxCurrentTCB; 	(1)
	PRESERVE8
	ldr r3, =pxCurrentTCB   (2)
	ldr r1, [r3] 			(3)
	ldr r0, [r1]            (4)
	ldmia r0!, {r4-r11}     (5)
	msr psp, r0             (6)
	isb
	mov r0, #0              (7)
	msr basepri, r0         (8)
	orr r14, #0xd           (9)
	bx r14                  (10)
}

(1) 是声明外部变量pxCurrentTCB,pxCurrentTCB是一个全局变量指针,用来指向当前正在运行或者即将运行任务的任务控制块。
(2) 加载pxCurrentTCB任务控制块的地址到R3中;
(3) 加载pxCurrentTCB到R1
(4) 加载pxCurrentTCB指向的任务控制块到R0中,因为任务控制块中的第一个成员变量就是任务的栈顶指针: pxTopOfStack,所以,R0就等于下图中的pxTopOfStack。
FreeRTOS启动第一个任务和任务的切换实现过程_第1张图片:-:
图1任务栈初始化完成后的栈空间分布图
(5) 以r0为基地址(指针先加后操作)将栈中的8个字的数据加载到CPU中的R4-R11中。这时候的R0中的地址见图2;
(6) 将操作5后新的栈顶指针R0更新到PSP中(注意:在执行异常的时候,SP以MSP为栈指针,在执行任务时SP以PSP为栈指针);
(7) 执行7之前清流水线,确保操作6指令执行完毕。清除R0;
(8) 设置BASEPR寄存器为0 即打开所有中断;
(9) (10)两个操作具体解释如下
当r14为0xFFFFFFFX,执行是中断返回指令,
cortext-m3的做法,X的bit0为1表示返回thumb状态,bit1和bit2分别表示返回后sp用msp还是psp、以及返回到特权模式还是用户模式;
异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器:xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)同时PSP的值也将更新,即指向任务栈的栈顶 。

0x0D 表示返回后为thumb状态,且指定SP出栈指针使用PSP作为出栈指针,这里的栈指针指向的准备运行的栈,所有在执行BX LR后,在恢复现场时会以当前的PSP为基地址,把栈中的剩下内容将会自动加载到CPU寄存器: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)中,又因为跳转是BX 无链接,所以跳转后的R15 PC会被从新的PC从堆栈中恢复到R15(PC)寄存器中,所以程序会从该新任务入口函数开始执行。

(注意:)发生异常跳转到异常处理服务前,自动执行的现场保护会保留返回模式。

FreeRTOS启动第一个任务和任务的切换实现过程_第2张图片
图2第一个任务启动后PSP的指向位置

任务的切换

在FreeROTS任务的切换实际由xPortPendSVHandler函数完成,主要完成上下文切换即保存上文,切换下文两个主要目的操作;
切换前我们需要了解上文需要保存什么,下文切换的又是什么;
上文中保存的主要内容是:
(1).寄存器中的R4-R11数据(其余的进入异常前CPU自动保存);
(2).当前任务的栈顶指针;
(3).全局TCB地址;
下文切换需要切换的内容主要是:
(1).最新任务的TCB;
(2).最新任务中栈中的r4-r11数据到CPU R4-R11;
(3)新任务栈顶存入PSP,用来出栈根据PSP调用新任务;
主要流程如下图所示:
FreeRTOS启动第一个任务和任务的切换实现过程_第3张图片
上代码:

__asm void xPortPendSVHandler( void ) 
{
	extern pxCurrentTCB;                          (1) 
	extern vTaskSwitchContext;                 	  (2) 
	PRESERVE8                                     (3)  
	mrs r0, psp                                   (4) 
	isb 
	
	ldr r3, =pxCurrentTCB                         (5) 
	ldr r2, [r3]                                  (6) 
	stmdb r0!, {r4-r11}                           (7) 
	str r0, [r2]                                  (8) 
	stmdb sp!, {r3, r14}                          (9) 
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    (10) 
	msr basepri, r0                                  (11) 
	dsb 
	isb 
	bl vTaskSwitchContext                            (12) 
	mov r0, #0                                       (13) 
	msr basepri, r0                                   
	ldmia sp!, {r3, r14}                             (14) 
	ldr r1, [r3]                                     (15) 
	ldr r0, [r1]                                     (16) 
	ldmia r0!, {r4-r11}                              (17) 
	msr psp, r0                                      (18) 
	isb 
	bx r14                                           (19) 
	nop 
} 

说明;

上文的保存

注意:在进入该函数前,系统(CPU)会自动的将上一个任务的运行的环境即: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)这些寄存器的值会存储到任务的栈中,是自动完成的。
(4) 进入到xPortPendSVHandler函数中第一步要做的是将剩下的 R4~R11手动保存入栈,同时PSP 会自动更新(在更新之前 PSP 指向任务栈的栈顶)。
这时当前PSP指向见图3
FreeRTOS启动第一个任务和任务的切换实现过程_第4张图片
图3上文环境自动保存到任务栈后PSP的指向
(5)将pxCurrentTCB的地址加载到R3中;
(6)将R3中指向的内容加载到R2中R2 =pxCurrentTCB ;
(7)先将R0指向地址递减在以新R0为及地址将CPU中的R4-R11手动入栈,这时的R0的具体指向见图4;

FreeRTOS启动第一个任务和任务的切换实现过程_第5张图片 图4上一个任务的运行环境手动入栈后,R0的指向
(8)将R0的值存储到R2指向的内容,因为R2等于pxCurrentTCB,而pxCurrentTCB的第一个成员为pxTopOfStack,所以就是将R0存储到的上一个任务的pxTopOfStack任务栈顶指针中。完成了上文的保存;

切换下文

(9)把R14(LR)寄存器入栈保存,是因为调用vTaskSwitchContext函数返回时,CPU会把返回地址自动保存到LR寄存器中,R14(LR)寄存器的值被覆盖故需要进入入栈保护处理。把R3一并入栈保存是因为下面要调用vTaskSwitchContext函数来切换任务控制块,可能会用到R3,把R3的值覆盖,而我们还需要原R3来找到当操作新的pxCurrentTCB,所以保险起见一并把R3也保存了起来;
(10)把configMAX_SYSCALL_INTERRUPT_PRIORITY值存到R0中,用来屏蔽BASEPRI的值;
(11)关闭部分高优先级中断;
(12)调用vTaskSwitchContext函数用来更新下一个需要运行任务的任务控制块pxCurrentTCB;
(13)退出临界段,开中断,直接往 BASEPRI写 0;
(14)从堆栈中恢复R3,R14的值,此时SP使用的时MSP;
(15)将将要运行任务的任务控制块pxCurrentTCB的地址指向的内容即任务控制块本身加载到R1中;
(16)加载R1中指向的内容即将要运行的任务的栈顶指针到R0中;
(17)把R0为基地址,将下一个将要运行任务的栈加载到R4-R11寄存器中,此时的R0的指向已经变化;
(18)将R0的值更新到PSP中,用来退出异常后以PSP为及地址,将新任务栈中剩下的内容加载到CPU寄存器中,然后清流水线,保证此操作完成后在执行下一条指令。
(19) 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回, 然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址及参数r0,r1,r2,r3,r12,r14,r15,xpsr自动加载到R0,R1,R2,R3,R12,R14,R15(PC)以及xPSR寄存器后,CPU根据被出栈到PC寄存器的值,执行新的任务指令。

刚入门FreeRTOS系统实现,此文章叙述任务的调度,后期把任务的创建机制及管理方式实现补上来。看了几天比较吃力,可能以上理解稍有偏差,仅供个人笔记和学习参考使用。

你可能感兴趣的:(嵌入式,c,FreeRTOS)