FreeRTOS源码学习_01-任务调度器
一、写在前面
- 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 软件定时器2为10个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;
}