FreeRTOS源码学习_01-任务调度器-2021-10-28

一、写在前面

  • FreeRTOS版本:V10.4.5
  • 内存分配方式:动态分配
  • Port.c版本:GCC/ARM_CM4F_V10.4.5

我自己最近在学习FreeRTOS操作系统,在使用中发现,虽然官方的英文注释十分的详尽,但是很多地方不是特别好理解,初学者看了注释后还是一头雾水。因此决定将自己的使用理解以及注释写在这里,方便大家参考。

注意:因为加了注释符后,注释会变成斜体,看着很别扭,因此文中注释统一采用 “ ** … ** ” 的方式


第一阶段为基础篇 分为四篇:

01-任务调度器:分析开启任务调度函数
02-创建任务:分析任务是如何被创建的(待更新)
03-链表操作:系统中的任务、消息、信号量等都与链表息息相关,分析FreeRTOS中的链表操作(待更新)
04-汇编指令解析:分析FreeRTOS中的汇编代码(待更新)


注释写的十分详细,直接写在源码中,篇幅较长,希望耐心看完!
如果这篇文章帮助到了您,作者甚是欣慰。若文中有不正确或不恰当的地方,也请大家及时指正!



二、源码分析

1、开始任务调度:void 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 );
            xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
                                                 configIDLE_TASK_NAME,
                                                 ulIdleTaskStackSize,
                                                 ( void * ) NULL,       
                                                 portPRIVILEGE_BIT,     
                                                 pxIdleTaskStackBuffer,
                                                 pxIdleTaskTCBBuffer ); 

            if( xIdleTaskHandle != NULL )
            {
                xReturn = pdPASS;
            }
            else
            {
                xReturn = pdFAIL;
            }
        }
    #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
        {
            **
             创建空闲任务 采用动态方式 具体见第二篇文章 《 02-创建任务 》 
            **
            xReturn = xTaskCreate( prvIdleTask,
                                   configIDLE_TASK_NAME,
                                   configMINIMAL_STACK_SIZE,
                                   ( void * ) NULL,
                                   portPRIVILEGE_BIT,  /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                   &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 )
            {
            	**
            	 定时器任务创建 见第二小节 2、创建软件定时器任务 
            	**
                xReturn = xTimerCreateTimerTask();
            }
            **
             内存不足 空闲任务创建失败 这个宏需要由用户实现 用来提示任务创建失败 
            **
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    #endif /* configUSE_TIMERS */

	/* 内存充足 任务创建成功 */
    if( xReturn == pdPASS )
    {
        /* freertos_tasks_c_additions_init() should only be called if the user
         * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
         * the only macro called by the function. */
        #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
            {
                freertos_tasks_c_additions_init();
            }
        #endif

        ** 
         这里禁止中断 因为接下来的操作会设置Systick,关闭中断可以防止在任务开始调度前产生Tick 
         代码为关闭中断操作 源码为汇编 参考第四篇文章 《 04-汇编指令解析 》
        **
        portDISABLE_INTERRUPTS();

		** 
		 这里用来配置是否使用 newlib 默认不使能 即使用glibc 
		**
        #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.
                 * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
                 * for additional information. */
                _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
            }
        #endif /* configUSE_NEWLIB_REENTRANT */
		
		** 
		 从名字就可以知道 这个变量的意思是下个任务解除阻塞的时间 现在没有运行中任务 所以初始化为最大值 portMAX_DELAY 
		**
        xNextTaskUnblockTime = portMAX_DELAY;
        
        
        **
         xSchedulerRunning 是任务调度器运行的标志 这里置位 表示开启任务调度器 具体开启的操作在下面代码 
        **
        xSchedulerRunning = pdTRUE;
        
        
        
        ** 
         这里初始化FreeRTOS系统的计时时间 configINITIAL_TICK_COUNT 这个宏默认为0 即从0开始计时 
		 xTickCount 是个静态全局变量 会在SysTick中断中进行自加操作 
		**
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;



        **
         这个函数是个宏 需要用户手动配置 用来设置一个硬件定时器,用来统计任务的运行时间 
        **
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		**
		 这个宏用来实现回调函数 需要用户手动添加回调函数的功能 
		**
        traceTASK_SWITCHED_IN();



        ** 
        【十分重要】 xPortStartScheduler()用来真正开启任务调度 
        			源码分析 见第四小节 4、开启任务调度:xPortStartScheduler()
					调度器开启成功后会去执行任务相关代码 即if后面的代码不再执行 
		**
        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 );
    }

    **
     下面两个参数没有用到 前面加(void)可以防止编译器报警告
    **
    ( void ) xIdleTaskHandle;
    ( void ) uxTopUsedPriority;
}

2、创建软件定时器任务:

BaseType_t xTimerCreateTimerTask( void )
    {
        BaseType_t xReturn = pdFAIL;

        **
         软件定时器是基于链表和队列来实现的 这里检查必须的资源是否就绪 若没有就创建 
         源码分析 见第三小节 3、检查链表队列是否有效:prvCheckForValidListAndQueue()
        **
        prvCheckForValidListAndQueue();


		**
		 prvCheckForValidListAndQueue() 这个函数会创建定时器队列 因此代码到这里时 xTimerQueue 保存的是软件定时器队列的句柄,正常不应该为NULL 
		**
        if( xTimerQueue != NULL )
        {
        	**
        	 静态方式创建软件定时器任务 暂不考虑 
        	**
            #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                    StaticTask_t * pxTimerTaskTCBBuffer = NULL;
                    StackType_t * pxTimerTaskStackBuffer = NULL;
                    uint32_t ulTimerTaskStackSize;

                    vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
                    xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,
                                                          configTIMER_SERVICE_TASK_NAME,
                                                          ulTimerTaskStackSize,
                                                          NULL,
                                                          ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
                                                          pxTimerTaskStackBuffer,
                                                          pxTimerTaskTCBBuffer );

                    if( xTimerTaskHandle != NULL )
                    {
                        xReturn = pdPASS;
                    }
                }
            #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
                {
                	**
                	 动态方式创建软件定时器任务 具体见第二篇文章《 02-创建任务 》 
                	**
                    xReturn = xTaskCreate( prvTimerTask,
                                           configTIMER_SERVICE_TASK_NAME,
                                           configTIMER_TASK_STACK_DEPTH,
                                           NULL,
                                           ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
                                           &xTimerTaskHandle );
                }
            #endif /* configSUPPORT_STATIC_ALLOCATION */
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        configASSERT( xReturn );
        return xReturn;
    }

3、检查链表队列是否有效:prvCheckForValidListAndQueue()

static void prvCheckForValidListAndQueue( void )
    {
        **
         进入临界区 即关闭部分中断 目的是保护资源 汇编代码 具体见 第四篇文章《 04-汇编指令解析 》  
        **
        taskENTER_CRITICAL();
        {
	        **
	         第一次初始化 xTimerQueue 默认为NULL 
	        **
            if( xTimerQueue == NULL )
            {
            	**
            	 初始化软件定时器管理的两个链表 (在FreeRTOS中也叫列表) 
            	 xActiveTimerList1 用来保存当前定时器列表,并且按周期阻塞时间以升序的方式排列 
            	(比如:软件定时器1周期为5个Tick 软件定时器210个Tick,则软件定时器2会放在1后面,
            	 若软件定时器3得到周期为0xfffffff0,则加上当前的Tick后会溢出,则软件定时器3会挂在xActiveTimerList2 上) 
            	**
                vListInitialise( &xActiveTimerList1 );


				** 
				 xActiveTimerList2 用来保存溢出的列表 当xActiveTimerList2 也溢出时,xActiveTimerList2 和xActiveTimerList1的功能会对调,即 xActiveTimerList2 保存当前定时器列表 xActiveTimerList1保存溢出列表 
				**
                vListInitialise( &xActiveTimerList2 );


                **
                 pxCurrentTimerList 和 pxOverflowTimerList 是两个链表指针 用来指向上面创建的两个链表 
                **
                pxCurrentTimerList = &xActiveTimerList1;
                pxOverflowTimerList = &xActiveTimerList2;

                #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
				**
				 这里是采用静态的方式创建队列 暂不考虑 (因为代码注释太长 影响观看 暂时删除) 
				**
                #else
                    {
                    	**
                    	 创建一个消息队列 长度通过 configTIMER_QUEUE_LENGTH 这个宏来配置 
                    	 消息队列的源码分析会在第二阶段进行展示 
                    	**
                        xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
                    }
                #endif /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */

				**
				 configQUEUE_REGISTRY_SIZE 是用来配置调试消息队列的个数 方便调试消息队列 
				**
                #if ( configQUEUE_REGISTRY_SIZE > 0 )
                    {
                        if( xTimerQueue != NULL )
                        {
                        	**
                        	 将消息队列进行注册(想要调试消息队列 在创建完成后必须添加注册) 
                        	**
                            vQueueAddToRegistry( xTimerQueue, "TmrQ" );
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* configQUEUE_REGISTRY_SIZE */
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL();
    }

4、开启任务调度:xPortStartScheduler()

BaseType_t xPortStartScheduler( void )
{
	** 
	 断言FreeRTOS管理的最高优先级不为0 
	 configMAX_SYSCALL_INTERRUPT_PRIORITY 是个宏,用来设置FreeRTOS管理的最大中断优先级 
	 用户可通过配置 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 这个宏来设置系统管理的最大中断优先级 
	**
    configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
    

	** 
	 portCPUID 是个宏 取的是SCB寄存器的CPU-ID寄存器地址 可以用来识别CPU的类型
	 第一小节 一、写在前面 说了,源码是cm4的内核,因此M7的不可用
	**
    configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
    configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
    

	** 
	 下面的代码用来断言NVIC中可用的优先级数量 
	 我们知道,中断优先级的位数最大为8位,但是并不是8位全部都采用了,MCU厂商会进行裁剪,而大部分都不会用到8位
	 并且是高位有效 比如只用了3位,则设置优先级时应该左移5位,因为低5位无效
	**
    #if ( configASSERT_DEFINED == 1 )
        {
			**
			 保存原始优先级 
			**
            volatile uint32_t ulOriginalPriority;


            **
             获取外部中断#0的中断优先级 
            (portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER )的地址为0xE000E400,是管理外部中断#0的寄存器 
            **
            volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
            volatile uint8_t ucMaxPriorityValue;
            

			**
			 首先获取原始的寄存器值 默认为0 
			**
            ulOriginalPriority = *pucFirstUserPriorityRegister;


			**
			 给寄存器赋值 portMAX_8_BIT_VALUE,这个宏的值为0xff 
			**
            *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;


			**
			 再次将寄存器的值读出 保存到ucMaxPriorityValue变量中
			 有朋友可能会疑惑问什么要这么做,前面也说过,大部分MCU的中断优先级位数不是8位,因此写入0xff后,再次读取的值不一定是8位
			 举个例子:MCU的NVIC可用优先级数量为4,那么低4位无效,则写入0xff,实际写入的值为0xf0,因此再次读出后的值为0xf0,而不是0xff
			**
            ucMaxPriorityValue = *pucFirstUserPriorityRegister;


          	** 
          	 configMAX_SYSCALL_INTERRUPT_PRIORITY 这个宏是FreeRTOS管理的最高优先级 
          	**
            ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;


            **
             portMAX_PRIGROUP_BITS 这个宏为7,是最大分组优先级位数 因为还有子优先级 
             子优先级 + 分组优先级 = NVIC使用的中断优先级位数 
             至于子优先级和分组优先级各自分配多少位,可由软件来配置 
            **
            ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;


			**
			 这个while循环可以获取从寄存器中读取的数ucMaxPriorityValue 高位有多少个1 即该MCU的NVIC有多少位可用 
			**
            while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
            {
                ulMaxPRIGROUPValue--;
                ucMaxPriorityValue <<= ( uint8_t ) 0x01;
            }


			**
			 下面代码用来断言NVIC实际使用位数与软件中设置的是否相同 
			**
            #ifdef __NVIC_PRIO_BITS
                {
                    
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
                }
            #endif

            #ifdef configPRIO_BITS
                {
                    
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
                }
            #endif


           **
            ulMaxPRIGROUPValue 这个全局静态变量用来下面的断言检测 可不必关心 
           **
            ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
            ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;


			**
			 断言完成后 将最初的优先级还原,即设置为默认的0 
			**
            *pucFirstUserPriorityRegister = ulOriginalPriority;
        }
    #endif /* conifgASSERT_DEFINED */


	**
	 0xE000ED18 ~ 0xE000ED23为系统的中断优先级设置寄存器 
	**


    ** 
     设置PENDSV中断为最低优先级 portNVIC_SHPR3_REG 为PENDSV中断优先级寄存器地址 
    **
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;


    **
     设置SYSTICK中断为最低优先级 portNVIC_SHPR3_REG 为SYSTICK中断优先级寄存器地址 
    **
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;


    **
     这个函数比较简单 用来设置Systick的定时周期 并打开SysTick中断开关 
    **
    vPortSetupTimerInterrupt();


    **
     初始化临界区变量 这个变量用来记录进入临界区的嵌套次数 
    **
    uxCriticalNesting = 0;


    **
     使能硬件浮点,这里是为了确保硬件浮点开启,因为之前可能已经开启了 
     汇编源码 具体见 《 04-汇编指令解析 》 文章
    **
    vPortEnableVFP();

    /* Lazy save always. */
    *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;


    **
     开启第一个任务 SVC中断实现 
     汇编源码 具体见 《 04-汇编指令解析 》 文章
    **
    prvPortStartFirstTask();

    /* Should never get here as the tasks will now be executing!  Call the task
     * exit error function to prevent compiler warnings about a static function
     * not being called in the case that the application writer overrides this
     * functionality by defining configTASK_RETURN_ADDRESS.  Call
     * vTaskSwitchContext() so link time optimisation does not remove the
     * symbol. */
    vTaskSwitchContext();
    prvTaskExitError();

    /* Should not get here! */
    return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值