FreeRTOS原理剖析:任务调度器启动

1. 任务调度器启动相关API函数

函数描述
vTaskStartScheduler()开启任务调度器

任务调度启动中其它重要的API函数(介绍过的函数不列出,请参考前面的文章):

函数描述
xPortStartScheduler()由函数vTaskStartScheduler()调用,配置系统节拍时钟,并启动第一个任务
prvStartFirstTask()复位MSP,调用触发SVC中断,启动第一个任务
vPortSVCHandler()SVC中断服务函数

2. 任务调度的基本知识

任务调度器是使用相关的调度算法来找到最高优先级的任务,执行任务切换,从而总是使最高优先级的任务占用CPU资源。在嵌入式实时操作系统中,最核心的部分也就是任务调度和任务切换,任务调度最核心的是调度算法,为了性能最优,部分代码会使用汇编指令。

2.1 FreeRTOS支持的三种调度方式

FreeRTOS支持的三种调度方式有抢占式调度,时间片调度和合作式调度。 实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。

2.1.1 抢占式调度器基本概念

每个任务都有一个优先级,高优先级的任务能打断低优先级的任务,抢占CPU的的资源。如果任务在执行过程中,有更高优先级的任务就绪,调度器会执行调度,让更高优先级的任务占用CPU资源。
在这里插入图片描述

2.1.1 时间片调度器基本概念

当多个任务拥有同一个优先级时,FreeRTOS允许每个任务运行一个系统节拍后让出CPU使用权给其它任务。使用时间片调度器功能,需要设置以下两个宏定义。

#define configUSE_PREEMPTION	1       //1使用抢占式内核,0使用协程
#define configUSE_TIME_SLICING	1		//1使能时间片调度(默认是使能的)

时间片的长度为一个系统节拍,设置如下:

#define configTICK_RATE_HZ	(1000)      //每秒中断多少次;时钟节拍频率,这里设置为1000,周期就是1ms

若某优先级下有3个就绪态任务,执行过程如下:
在这里插入图片描述
如果任务在一个系统节拍后仍没有执行完,则会让出CPU使用权,等到下一个节拍接着运行。如果一个系统节拍内提前执行完成,则会提前执行任务切换,让出CPU使用权。

3. 开启任务调度源代码分析

3.1 函数vTaskStartScheduler()分析
void vTaskStartScheduler( void )
{
	BaseType_t xReturn;

	/* 如果使能静态方式创建任务 */
	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	{
		StaticTask_t *pxIdleTaskTCBBuffer = NULL;
		StackType_t *pxIdleTaskStackBuffer = NULL;
		uint32_t ulIdleTaskStackSize;

		/* 申请空闲任务空间,该函数需要用户自己定义 */
		vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
	
		/* 使用静态方式创建一个空闲任务,优先级为0 */
		xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
												"IDLE",
												ulIdleTaskStackSize,
												( void * ) NULL,
												( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
												pxIdleTaskStackBuffer,
												pxIdleTaskTCBBuffer ); 

		/* 如果申请内存成功 */
		if( xIdleTaskHandle != NULL )
		{
			xReturn = pdPASS;
		}
		else
		{
			xReturn = pdFAIL;
		}
	}
	#else	/* 如果不使用静态方式创建任务 */
	{
		/* 使用动态方式创建一个空闲任务 */
		xReturn = xTaskCreate(	prvIdleTask,
								"IDLE", configMINIMAL_STACK_SIZE,
								( void * ) NULL,
								( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
								&xIdleTaskHandle ); 
	}
	#endif /* configSUPPORT_STATIC_ALLOCATION */

	/* 如果使用软件定时器功能 */
	#if ( configUSE_TIMERS == 1 )
	{
		/* 如果创建空闲任务成功 */
		if( xReturn == pdPASS )
		{
			xReturn = xTimerCreateTimerTask();	/* 创建一个定时器任务 */
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */

	if( xReturn == pdPASS )
	{
		portDISABLE_INTERRUPTS();

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */

		xNextTaskUnblockTime = portMAX_DELAY;	/* 设置下一个唤醒任务系统节拍为portMAX_DELAY */
		xSchedulerRunning = pdTRUE;				/* 使能任务调度器 */
		xTickCount = ( TickType_t ) 0U;			/* 设置系统时钟片为0 */


		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();	/* 空代码 */

		if( xPortStartScheduler() != pdFALSE )
		{
			/* 如果调度器启动成功,不会允许到这里 */
		}
		else
		{
			/* 当调用 xTaskEndScheduler()才会运行到这里 */
		}
	}
	else
	{
		/* 如果允许到这里,说明创建空闲任务或定时任务失败,内存分配出错 */
		configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
	}

	/* 防止编译器警告 */
	( void ) xIdleTaskHandle;
}
3.2 函数xPortStartScheduler()
BaseType_t xPortStartScheduler( void )
{
	configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
	configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
	configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );

	#if( configASSERT_DEFINED == 1 )
	{
		volatile uint32_t ulOriginalPriority;
			
		/* 
		 * 获取第一个中断优先级寄存器的地址:0xE000 E400 
		 * 每个中断有8位表示优先级,其中高4位有效,连续4个中断组成一个32位的NVIC_IPRx
		 */
		volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
		
		volatile uint8_t ucMaxPriorityValue;	
		
		ulOriginalPriority = *pucFirstUserPriorityRegister;	/* 保存原有的寄存器的值 */


		/*  
		 * 首先向所有位写1,然后再读出来
		 * 由于无效的优先级位读出为0,确定1的个数就能知道平台有多少位表示优先级
		 */
		*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
		ucMaxPriorityValue = *pucFirstUserPriorityRegister;


		configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );

		/* 算出由FreeRTOS管理的优先级最高中断优先级 */
		ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

		/* 其中的portMAX_PRIGROUP_BITS为( ( uint8_t ) 7 ),即为0000 0111 */
		ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;

		/* 
		 * 判断最高优先级数ucMaxPriorityValue有多少个1 
		 * 在Cortex-M3/M4中有抢占优先级(分组优先级)和亚优先级(子优先级),FreeRTOS没有处理亚优先级情况
		 * ulMaxPRIGROUPValue表示优先级分组
		 * 当ulMaxPRIGROUPValue等于7时,表示0位抢占优先级;为3表示4位抢占优先级
		 */
		while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
		{
			ulMaxPRIGROUPValue--;
			ucMaxPriorityValue <<= ( uint8_t ) 0x01;
		}

		/* 右移8位,配置优先级组寄存器的[10:8]有效 */
		ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
		ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

		/* 恢复优先级配置寄存器值 */
		*pucFirstUserPriorityRegister = ulOriginalPriority;
	}
	#endif /* conifgASSERT_DEFINED */

	/* 
	 * 每个中断占据一个8位的优先级寄存器设置,数值越小,优先级越高
	 * 4个相临的寄存器组成32位的寄存器
	 * 从0xE000 ED20地址,PendSV和SysTick分别占据次高8位和最高8位,其中每8位中高4位才有效
	 * 设置PendSV和SysTick中断的优先级为0xF0,即最低优先级
	 */
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

	/* 初始化系统节拍定时器,即系统的SysTick定时器 */
	vPortSetupTimerInterrupt();

	/* 初始化边界嵌套计数器 */
	uxCriticalNesting = 0;

	/* 使能FPU功能 */
	prvEnableVFP();

	/*  
	 * CM4内核有个叫lazy stacking的特性,默认该特性已经开启,软件通过FPCCR寄存器来配置lazy stacking特性
	 * 它的目的是减少没必要的浮点寄存器入栈和出栈时间
	 */
	*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;

	/* 触发SVC异常,从而启动第一个任务 */
	prvStartFirstTask();

	return 0;	/* 一般不会返回 */
}
3.3 函数prvStartFirstTask()
__asm void prvStartFirstTask( void )
{
	PRESERVE8
	
	/* Cortext-M3硬件中,0xE000ED08地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/ 
	ldr r0, =0xE000ED08	

	 /* 取出向量表中的第一项,向量表第一项存储主堆栈指针MSP的初始值 */	
	ldr r0, [r0]			/* 将R0所保存的地址处的值赋值给R0 */
	ldr r0, [r0]			/* 获取MSP初始值 */

	msr msp, r0				/* 复位MSP */

	cpsie i					/* 使能中断(清除PRIMASK) */			
	cpsie f 				/* 使能中断(清除FAULTMASK) */

							/* 流水线相关 */			
	dsb						/* 数据同步隔离 */
	isb						/* 指令同步隔离 */
			
	svc 0					/* 调用SVC指令触发SVC异常,启动第一个任务 */
	nop
	nop
}
3.4 启动第一个任务

启动第一个任务使用SVC中断,其中断服务函数为SVC_Handler()。操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,这里触发SVC中断,在异常服务中启动第一个任务。

在FreeRTOS中使用了宏定义,如下:

#define vPortSVCHandler 	SVC_Handler

函数源代码如下:

__asm void vPortSVCHandler( void )
{
	PRESERVE8

	ldr	r3, =pxCurrentTCB		/* R3为pxCurrentTCB的储存地址,pxCurrentTCB永远指向当前需要运行的任务的TCB首地址 */
	ldr r1, [r3]				/* 将R3所处地址的数据赋值给R1,即获取了当前任务控制块的地址 */
	ldr r0, [r1]				/* 将R1所处地址的数据赋值给R0,即获取了栈顶指针指向的地址 */

	ldmia r0!, {r4-r11, r14}	/* 出栈,恢复R4 ~ R11,R14的值 */
	msr psp, r0					/* 进程栈指针PSP设置为堆栈 */
	isb 						/* 指令同步屏障 */
	mov r0, #0					/* 使R0为0 */
	msr	basepri, r0				/* 使寄存器basepri为0,即开启中断 */
	bx r14
}

参考资料:

【1】: 正点原子:《STM32F407 FreeRTOS开发手册V1.1》
【2】: 野火:《FreeRTOS 内核实现与应用开发实战指南》
【3】: 《Cortex M3权威指南(中文)》

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在ESP32上使用FreeRTOS进行任务调度非常简单。首先,你需要创建任务并设置其参数,例如任务函数、任务优先级等。然后,你可以使用调试工具来监视和分析任务的执行情况。 在创建任务之前,你需要准备好硬件和软件。硬件方面,你需要一个NodeMCU ESP32-S V3.0开发板和一条数据线。软件方面,你需要安装vscode和ESP-IDF的vscode扩展。 在代码中,你可以使用FreeRTOS的API函数来创建和管理任务。任务的创建需要指定任务函数,该函数将在任务被调度时执行。你还可以设置任务的优先级以及其他参数。 在任务调度期间,FreeRTOS会根据任务的优先级来决定执行哪个任务。较高优先级的任务将优先执行,而较低优先级的任务将在较高优先级任务执行完毕后才执行。这样可以确保任务按照一定的顺序和优先级进行调度,以满足系统的需求。 总结来说,使用ESP32上的FreeRTOS进行任务调度需要先创建任务并设置参数,然后使用调试工具进行监视和分析。硬件和软件准备也是必要的。最后,在任务调度期间,任务的执行顺序将根据其优先级确定。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [ESP32 FreeRTOS-任务的创建与删除 (1)](https://blog.csdn.net/believe666/article/details/127175049)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [ESP32-IDF开发实例-FreeRTOS任务优先级](https://blog.csdn.net/wujuxKkoolerter/article/details/117655266)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值