zoukankan      html  css  js  c++  java
  • 4.FreeRTOS调度器的启动简易分析

    FreeRTOS调度器的启动简易分析

    • 架构:Cortex-M3
    • 版本:FreeRTOS V9.0.0
    • 前言:上一篇我分析了关于一个任务的创建过程,既然创建了任务,自然是要用。那么FreeRTOS中对于任务的切换,调度器发挥着巨大的作用,这是一个核心。

    1.从函数vTaskStartScheduler入手

    便于分析我简化了代码:

    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. */
    		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();
    
    		/* 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;
    }
    
    1. 用动态创建任务的方式,创建了一个空闲任务
    2. 创建以及初始化定时器任务
    3. 最后调用了xPortStartScheduler

    我简化了一些代码xPortStartScheduler

    /*
     * See header file for description.
     */
    BaseType_t xPortStartScheduler( void )
    {
    	#if( configASSERT_DEFINED == 1 )
    	{
    		...
            ...
            ...
    	}
    	#endif /* conifgASSERT_DEFINED */
    
    	/* Make PendSV and SysTick the lowest priority interrupts. */
    	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    	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;
    
    	/* Start the first task. */
    	prvStartFirstTask();
    
    	/* Should not get here! */
    	return 0;
    }
    
    

    ​ 首先是我在分析List的时候讲过关于SVC和PendSV优先级的赋值情况,实际上这两个优先级是最低的。然后是启动软件定时器vPortSetupTimerInterrupt(),设置临界区嵌套深度uxCriticalNesting为0,调用prvStartFirstTask()开启第一个任务。

    函数如下:

    __asm void prvStartFirstTask( void )
    {
    	PRESERVE8
    
    	/* Use the NVIC offset register to locate the stack. */
    	ldr r0, =0xE000ED08
    	ldr r0, [r0]
    	ldr r0, [r0]
    
    	/* Set the msp back to the start of the stack. */
    	msr msp, r0
    	/* Globally enable interrupts. */
    	cpsie i
    	cpsie f
    	dsb
    	isb
    	/* Call SVC to start the first task. */
    	svc 0
    	nop
    	nop
    }
    

    ​ 首先,从中断向量表偏移寄存器0xE000ED08中取出向量表的偏移地址,向量表的前四个字节就是主堆栈地址,把地址传给MSP,开启总中断,调用svc 0,触发ISR。当进入SVC异常服务例程时,CPU处于特权模式了。在特权模式下就允许操作只有特权模式下能操作的硬件。

    ​ 接下来看SVC异常服务例程:

    __asm void vPortSVCHandler( void )
    {
    	PRESERVE8
    
    	ldr	r3, =pxCurrentTCB	/* Restore the context. */
    	ldr r1, [r3]			/* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
    	ldr r0, [r1]			/* The first item in pxCurrentTCB is the task top of stack. */
    	ldmia r0!, {r4-r11}		/* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
    	msr psp, r0				/* Restore the task stack pointer. */
    	isb
    	mov r0, #0
    	msr	basepri, r0
    	orr r14, #0xd
    	bx r14
    }
    

    ​ pxCurrentTCB的前四个字节就是pxTopOfStack,我前面分析了很久的pxTopOfStack并没有浪费,因为我知道,这个指针指向的位置,就是存放任务现场的地址。取出pxTopOfStack后,按照Cortex-M3的出栈顺序,取出栈中各个寄存器的值。最开始,R0存放的是pxTopOfStack的值,执行手动出栈R4~R11的之后,R0的值比pxTopOfStack多0x20,然后把R0赋值到PSP,此时执行了bx r14之后,PSP会再次出栈xPSR、PC、LR、R12、R3~R0,PSP又会再增加0x20,也就是到了任务执行代码的时候,PSP就会比pxTopOfStack大0x40。如图:

    最后调用:

    orr r14, #0xd
    bx r14
    

    ​ 根据Cortex-M3权威手册上的说明,R14的bit0为1表示返回thumb状态,bit1和bit2分别表示返回后sp用msp还是psp、以及返回到特权模式还是用户模式。R14存储的是返回的地址,这里的返回地址一定就是某个任务的函数指针。返回时,但并不是简简单单返回,当或上0xd时,就是要让CPU进入到线程模式、Thumb状态,当线程模式时,才会使用PSP。

    ​ 这么看来一切都通了:创建任务的时候,把任务当成正在运行的任务,然后把任务现场统统入栈到pxTopOfStack,在启动调度器时,又把pxTopOfStack保存的现场统统出栈,最后设置R14的低三位,使得CPU进入用户模式和跳到某个任务的执行代码中。

  • 相关阅读:
    R语言数据框部分笔记
    R语言数组部分的笔记
    R语言向量部分的笔记
    计算机等级考试二级python 第二章 python的基本语法元素
    计算机二级教程python第一章 程序设计语言
    Linux C实现发邮件功能
    telnet收发邮件
    Linux进(线)程同步各种锁
    About Mutex
    wait()与waitpid()与pthread_join()
  • 原文地址:https://www.cnblogs.com/r1chie/p/14128251.html
Copyright © 2011-2022 走看看