任务调度

开启任务调度器

vTaskStartScheduler()

作用:用于启动任务调度器

实现过程:

  • 创建空闲任务
  • 若使能软件定时器,则创建定时器任务
  • 关闭中断,运行第一个任务时打开
  • 初始化全局变量,并将任务调度器标志位 设置为 已运行
  • 初始化任务运行时间统计功能的时基定时器
  • 调用xPortStartScheduler()

xPortStartScheduler()

作用:用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务

实现过程:

  • 检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误
  • 配置PendSV和SysTick 的中断优先级为最低优先级
  • 调用函数vPortSetupTimerInterrupt()配置SysTick
  • 初始化临界区嵌套计数器为0
  • 调用函数prvEnableVFP() 使能 FPU
  • 调用函数prvStartFirstTask() 启动第一个任务

启动第一个任务

prvStartFirstTask()

  • 开启第一个任务,主要重新设置MSP指针,使能全局中断,触发SVC中断
  • MSP主堆栈指针:由内核、异常服务例程以及所有需要特权访问的应用程序代码来使用
  • PSP进程堆栈指针:用于常规应用程序代码(不处于异常服务函数中)

vPortSVCHandler()

  • SVC中断服务函数,通过pxCurrentTCB 获取优先级最高的就绪态任务的任务地址,就是将要执行的任务
  • 通过任务的 栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在创建任务时已被初始化
  • 设置PSP指针
  • 通过在basepri 寄存器中写0,开启中断
  • 返回R14 中的值
// 将第一个任务 的内核寄存器入栈 开始执行 
__asm void vPortSVCHandler( void )
 {
     PRESERVE8
 
     /* 获取当前任务的TCB 地址 */
     ldr    r3, =pxCurrentTCB
     ldr r1, [r3]
     ldr r0, [r1]
     /* Pop the core registers. */
     ldmia r0!, {r4-r11, r14}	// cortex M3 无FPU,无需操作 R14,M4 M7有FPU 需要手动操作R14
     msr psp, r0
     isb
     mov r0, #0
     msr    basepri, r0
     bx r14
 }

注意:

  • 中断产生时,硬件自动将xPSR、PC、LR、R12、R3-R0保存和恢复,R4-R11需要手动保存恢复
  • 进入中断后硬件会强制使用MSP指针,此时LR(R14)的值会被自动更新为特殊的EXC_RETURN

任务切换

高优先级任务触发时 会立即抢占当前任务,不等执行完当前时间片

只要调用了任务谢欢函数,就会进行切换,跟时间片无关,时间片只是定时会执行切换函数

本质:CPU内核寄存器的切换

在PendSV中执行上下文切换,切换时R4-R11、R14需要手动保存恢复 其余自动保存恢复

PendSV中断触发方式:

  • 滴答定时器中断 调用
  • 执行FreeRTOS中提供API:portYIELD()
// 任务切换 保存现场 恢复现场
__asm void xPortPendSVHandler( void )
{
   extern uxCriticalNesting;
   extern pxCurrentTCB;
   extern vTaskSwitchContext;

   PRESERVE8

   mrs r0, psp
   isb
//保存现场        
   /* 获取TCB地址 */
 	ldr r3, =pxCurrentTCB  // 将 pxCurrentTCB 的地址加载到 r3
   ldr r2, [r3]           // 获取 pxCurrentTCB 的值(即当前TCB的地址)

   /* 是否使用浮点计算单元,使用则手动保存R14 */
   tst r14, #0x10         // 测试 EXC_RETURN 值(在 r14/LR 中)的第4位
   it eq                  // 如果结果为0(使用FPU),则执行下一条指令
   vstmdbeq r0!, {s16-s31} // 将FPU寄存器 s16-s31 压入任务栈
   
   /* 手动保存R4-R11寄存器,其余自动保存*/
   stmdb r0!, {r4-r11, r14} // 将寄存器 r4-r11 和 r14 压入任务栈

   /* 保存寄存器起始地址 到TCB 的第一个元素*/
    str r0, [r2]          // 将更新后的栈指针保存到 TCB 的第一个成员(pxTopOfStack)

   stmdb sp!, {r0, r3}   // 临时保存 r0 和 r3 到主栈(MSP)
   mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
   msr basepri, r0       // 设置 BASEPRI,屏蔽低于 5 优先级的中断
   dsb                   // 数据同步屏障
   isb                   // 指令同步屏障
   bl vTaskSwitchContext // 调用函数,选择下一个要运行的任务
   mov r0, #0
   msr basepri, r0       // 取消中断屏蔽
   ldmia sp!, {r0, r3}   // 恢复 r0 和 r3

//恢复现场    
   /* 加载当前任务的 TCB 的第一个元素 */
   ldr r1, [r3]         // 获取新的 TCB 地址
   ldr r0, [r1]         // 获取新任务的栈顶指针(pxTopOfStack)

   /*压入栈 */
   ldmia r0!, {r4-r11, r14} // 弹出寄存器 r4-r11 和 r14(EXC_RETURN)

   /* Is the task using the FPU context?  If so, pop the high vfp registers
   too. */
   tst r14, #0x10        // 检查是否使用了FPU
   it eq
   vldmiaeq r0!, {s16-s31} // 如果用了,弹出FPU寄存器 s16-s31

	msr psp, r0          // 将新任务的栈指针设置到 PSP
   isb                  // 指令同步屏障
   #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
   	#if WORKAROUND_PMU_CM001 == 1
   		push { r14 }
   		pop { pc }
   		nop
   	#endif
   #endif

   bx r14
}
// 获取当前最高优先级任务的 TCB,相同优先级 按时间片调度
#define taskSELECT_HIGHEST_PRIORITY_TASK()												
{																			
	UBaseType_t uxTopPriority;														
	/* Find the highest priority list that contains ready tasks. */					
	portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );						
	configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0);	
	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );	
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )		
{																					
	List_t * const pxConstList = ( pxList );											
	/* Increment the index to the next item and return the item, ensuring */				
	/* we don't return the marker used at the end of the list.  */							
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	
	{																				
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;					
	}																				
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;									
}

空闲任务及其钩子函数

钩子函数作用:

  • 配置进入低功耗模式
  • 测量空闲任务执行时间占比
  • 执行低优先级 但要不停处理的任务

钩子函数实现限制:

  • 不能阻塞或挂起,否则会导致没有任务能进入运行态,如果空闲任务阻塞,而所有其他任务也在等待某些事件,系统将没有任何可运行的任务
  • 需要尽快返回,在有任务被删除后 空闲任务负责回收内核资源
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐