8.1.1 任务调度器开启函数分析

    main()函数中先创建一个开始任务 start_task,后面紧接着调用函数 vTaskStartScheduler(),在文件 tasks.c
中有定义。

void vTaskStartScheduler( void )   //开启任务调度器
{
    BaseType_t xReturn;

	/* Add the idle task at the lowest priority. */
	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	{
		//...
	}
	#else
	{
		/* The Idle task is being created using dynamically allocated RAM. */
                //创建空闲任务,优先级为 tskIDLE_PRIORITY

		xReturn = xTaskCreate(	prvIdleTask,
				     "IDLE", configMINIMAL_STACK_SIZE,
				     ( void * ) NULL,
				     ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
				     &xIdleTaskHandle );

         /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast                         
         to all supported compilers. */
	}
	#endif /* configSUPPORT_STATIC_ALLOCATION */

	#if ( configUSE_TIMERS == 1 )
	{
		if( xReturn == pdPASS )
		{
			xReturn = xTimerCreateTimerTask();  //创建定时器服务任务
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */

	if( xReturn == pdPASS )
	{
		/* Interrupts are turned off here, to ensure a tick does not occur
		before or during the call to xPortStartScheduler().  The stacks of
		the created tasks contain a status word with interrupts switched on
		so interrupts will automatically get re-enabled when the first task
		starts to run. */

		portDISABLE_INTERRUPTS();  //关闭中断

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to the task that will run first. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */

		xNextTaskUnblockTime = portMAX_DELAY;
		xSchedulerRunning = pdTRUE;      //调度器开始运行
		xTickCount = ( TickType_t ) 0U;

		/* If configGENERATE_RUN_TIME_STATS is defined then the following
		macro must be defined to configure the timer/counter used to generate
		the run time counter time base. */
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();   // 为1使能时间统计功能

		/* Setting up the timer tick is hardware specific and thus in the
		portable interface. */
		if( xPortStartScheduler() != pdFALSE )  //初始化跟调度器启动有关的硬件
		{
			/* Should not reach here as if the scheduler is running the
			function will not return. */
		}
		else
		{
			/* Should only reach here if a task calls xTaskEndScheduler(). */
		}
	}
	else
	{
		/* This line will only be reached if the kernel could not be started,
		because there was not enough FreeRTOS heap to create the idle task
		or the timer task. */
		configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
	}

	/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
	meaning xIdleTaskHandle is not used anywhere else. */
	( void ) xIdleTaskHandle;
}

8.1.2 内核相关硬件初始化函数分析

     FreeRTOS 系统时钟是由滴答定时器来提供,任务切换会用到 PendSV 中断,由函数 xPortStartScheduler()初始化。

     在ARM_CM4F的port.c文件中。

/*
 * See header file for description.
 */
BaseType_t xPortStartScheduler( void )
{
	/* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
	See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
	configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );

	/* This port can be used on all revisions of the Cortex-M7 core other than
	the r0p1 parts.  r0p1 parts should use the port from the
	/source/portable/GCC/ARM_CM7/r0p1 directory. */
	configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
	configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );

	#if( configASSERT_DEFINED == 1 )
	{
		//...
	}
	#endif /* conifgASSERT_DEFINED */

	/* Make PendSV and SysTick the lowest priority interrupts. */
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;    //设置 PendSV 为最低优先级
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;    //设置滴答定时器为最低优先级

	/* Start the timer that generates the tick ISR.  Interrupts are disabled
	here already. */
	vPortSetupTimerInterrupt(); //设置滴答定时器的定时周期,使能滴答定时器的中断

	/* Initialise the critical nesting count ready for the first task. */
	uxCriticalNesting = 0;  //初始化临界区嵌套计数器

	/* Ensure the VFP is enabled - it should be anyway. */
	prvEnableVFP();     //使能 VFP

	/* Lazy save always. */
	*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;  //设置FPCCR bit31,bit30为1,异常入口和退出时会自动保存和恢复

	/* Start the first task. */
	prvStartFirstTask();  //启动第一个任务

	/* Should not get here! */
	return 0;
}

8.1.3 使能 FPU 函数分析

     xPortStartScheduler()中会调用 prvEnableVFP()来使能 FPU,代码是汇编形式。

   在ARM_CM4F的port.c文件中。

__asm void prvEnableVFP( void )
{
	PRESERVE8

	/* The FPU enable bits are in the CPACR. */
	ldr.w r0, =0xE000ED88    //CPACR(bit20 ~ bit23)使能或禁止FPU
	ldr	r1, [r0]    //读取CPACR 寄存器的值,保存在R1

	/* Enable CP10 and CP11 coprocessors, then save back. */
	orr	r1, r1, #( 0xf << 20 )  //位或运算,bit20~bit23设为1
	str r1, [r0]    //R1的值写入CPACR开启FPU
	bx	r14    //bx 为间接跳转指令,跳转到链接寄存(LR),LR用函数或子程序调用时返回地址的保存
	nop
}

8.1.4 启动第一个任务

    prvStartFirstTask()启动第一个任务,代码是汇编。

    在ARM_CM4F的port.c文件中。

__asm void prvStartFirstTask( void )
{
	PRESERVE8

	/* Use the NVIC offset register to locate the stack. */
	ldr r0, =0xE000ED08  //向量表偏移寄存器(VTOR)为0XE000ED08 保存在R0
	ldr r0, [r0]  //读取VTOR的值保存R0
	ldr r0, [r0]  //读取0X08000000的值保存R0,R0 存 MSP 的初始值
	/* Set the msp back to the start of the stack. */
	msr msp, r0    //复位 MSP
	/* Globally enable interrupts. */
	cpsie i    //使能中断(清除 PRIMASK)
	cpsie f    //使能中断(清除 FAULTMASK)
	dsb    //数据同步屏障
	isb    //指令同步屏障
	/* Call SVC to start the first task. */
	svc 0   //触发请求管理调用(SVC) 中断,SVC 异常由 SVC 指令触发,启动第一个任务
	nop
	nop
}

 

 

8.1.5 SVC 中断服务函数

      prvStartFirstTask()调用 SVC 指令触发 SVC 中断,第一个任务的启动是在 SVC 中断服务函数中完成的, SVC 中断函数为 SVC_Handler()。

     FreeRTOS Config.h 中通过#define 的方式重新定义为了 xPortPendSVHandler()。

#define xPortPendSVHandler 	PendSV_Handler

 在ARM_CM4F的port.c文件中。代码是汇编。

__asm void vPortSVCHandler( void )
{
	PRESERVE8

	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB  //获取pxCurrentTCB指针的值地址,指向 TCB_t 的指针,永远指向正在运行的任务
	ldr r1, [r3]  //获取当前任务的任务控制块的值地址
	ldr r0, [r1]  //任务控制块的第一个字段是任务堆栈的栈顶指针 pxTopOfStack 所指向的位置
	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}  //LDMIA 指令是多加载/存储指令
	msr psp, r0  //设置进程栈指针(PSP)为任务的堆栈
	isb           //指令同步屏障
	mov r0, #0  //R0 为 0
	msr	basepri, r0    // BASEPRI 为 R0,打开中断
	bx r14     //堆栈使用进程栈 PSP,任务调度器开始运行
}

 8.1.6 空闲任务

        空闲任务是空闲的时候运行的任务,空闲任务是 FreeRTOS 系统自动创建,空闲任务的任务优先级是最低的,为 0。

空闲任务

  •  判断系统是否有任务删除
  • 运行用户设置的空闲任务钩子函数
  • 判断是否开启低功耗 tickless 模式