【学习日记】【FreeRTOS】手动任务切换详解

前言

本文是关于 FreeRTOS 中实现两个任务轮流切换并执行的代码详解。目前不支持优先级,仅实现两个任务轮流切换。

一、任务的自传

任务从生到死的过程究竟是怎么样的呢?(其实也没死),这个问题一直困扰着我,单纯看野火的讲解有点云里雾里,所有自己捋了一遍嘿嘿:P。

下面这个图描述了任务的创建,调度器的使用以及手动切换任务的过程(这时还没用到优先级进行任务切换)。
【学习日记】【FreeRTOS】手动任务切换详解_第1张图片

二、任务切换相关函数详解

1. taskYIELD()

  • 其实是一个宏定义,代码如下:
#define taskYIELD()			portYIELD()
  • 用于在任务执行完成后手动调用进行任务切换:
/* 任务 */
void Task1_Entry( void *p_arg )
{
	for( ;; )
	{
		//下面是任务代码
		
		/* 任务切换,这里是手动切换 */
        taskYIELD();
	}
}

2. portYIELD()

  • 通过设置标志位触发 PendSV 中断
  • 代码如下:
#define portYIELD()																\
{																				\
	/* 触发PendSV,产生上下文切换 */								                \
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}
  • 其中标志位定义如下:
#define portNVIC_PENDSVSET_BIT		( 1UL << 28UL )
  • 该标志位定义的根据是
    PM0056Programming manual
    STM32F10xxx Cortex-M3 programming manual
    【学习日记】【FreeRTOS】手动任务切换详解_第2张图片
    【学习日记】【FreeRTOS】手动任务切换详解_第3张图片

3. xPortPendSVHandler()

  • 该函数即 PendSVC Handler
  • 函数分为五个部分
    ① 上个任务运行环境的保存(将CPU寄存器的值保存到任务栈中)
    ② 调用任务指针切换函数前对寄存器的保护
    ③ 调用任务指针切换函数,使任务指针指向新的任务
    ④ 调用任务指针切换函数后对寄存器的恢复
    ⑤ 下个任务运行环境的加载(将任务栈中的值加载到CPU寄存器)
  • 函数代码如下:
__asm void xPortPendSVHandler( void )
{
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8
//1. 上个任务运行环境的保存(将CPU寄存器的值保存到任务栈中)
    /* 当进入PendSVC Handler时,上一个任务运行的环境即:
       xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
       这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存 */
    /* 获取任务栈指针到r0 */
	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* 加载pxCurrentTCB的地址到r3 */
	ldr	r2, [r3]                /* 加载pxCurrentTCB到r2 */

	stmdb r0!, {r4-r11}			/* 将CPU寄存器r4~r11的值存储到r0指向的地址 */
	str r0, [r2]                /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */				
                   
	
//2. 调用任务指针切换函数前对寄存器的保护
	stmdb sp!, {r3, r14}        /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,
                                  调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;
                                  R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护 */
	
//3. 调用任务指针切换函数,使任务指针指向新的任务	
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    /* 进入临界段 */
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext       /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */ 
	mov r0, #0                  /* 退出临界段 */
	msr basepri, r0
	

//4. 调用任务指针切换函数后对寄存器的恢复
	ldmia sp!, {r3, r14}        /* 恢复r3和r14 */

	
//5. 下个任务运行环境的加载(将任务栈中的值加载到CPU寄存器)
	ldr r1, [r3]
	ldr r0, [r1] 				/* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
	ldmia r0!, {r4-r11}			/* 出栈 */
	msr psp, r0
	isb
	bx r14                      /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、
                                   使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,
                                   然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,
                                   当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
	nop
}

4. vTaskSwitchContext()

其实就是对 pxCurrentTCB 进行了手动更新,目前还很粗糙。

//任务切换函数
void vTaskSwitchContext( void )
{    
    /* 两个任务轮流切换 */
    if( pxCurrentTCB == &Task1TCB )
    {
        pxCurrentTCB = &Task2TCB;
    }
    else
    {
        pxCurrentTCB = &Task1TCB;
    }
}

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

你可能感兴趣的:(RTOS,RTOS)