赞
踩
FreeRTOS源码解析集合(全网最详细)
手把手教你FreeRTOS源码解析(一)——内存管理
手把手教你FreeRTOS源码详解(二)——任务管理
手把手教你FreeRTOS源码详解(三)——队列
手把手教你FreeRTOS源码详解(四)——信号量、互斥量、递归互斥量
在FreeRTOS中,每个任务都有一个属于自己的任务控制块,方便对任务进行管理,TCB结构体源码如下(已经删去了一些条件编译):
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*·指向任务栈顶*/
ListItem_t xStateListItem; /* 表示任务状态的链表(就绪, 阻塞, 挂起 )*/
ListItem_t xEventListItem; /* 指向事件链表中的某一任务*/
UBaseType_t uxPriority; /*任务的优先级,0为最低优先级 */
StackType_t *pxStack; /*指向栈的起始地址 */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*保存任务名称*/
} tskTCB;
首先我们来看一下任务创建函数xTaskCreate的参数。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
pxTaskCode:函数指针,指向要执行的函数
pcName:任务的名称
usStackDepth:任务栈大小,注意单位为字(4个字节)
pvParameters:传递给任务函数的参数
uxPriority:任务的优先级
pxCreatedTask :任务句柄,用于管理任务
在FreeRTOS中,很多项目是根据堆栈的增长方向来的配置的,这使FreeRTOS有了更好的兼容性。堆栈的增长方向有向下增长和向上增长两种。
向下增长:高地址向低地址增长。
向上增长:低地址向高地址增长。
在任务创建之初,需要分别给TCB任务控制块和任务分配内存空间,对于堆栈增长方向的不同,给TCB任务控制块和任务分配内存空间的顺序也有所差异。
源码如下:
#if( portSTACK_GROWTH > 0 )
{
/* 为TCB结构体申请内存堆 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
/* 为任务申请内存堆 */
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
if( pxNewTCB->pxStack == NULL )
{
/* Could not allocate the stack. Delete the allocated TCB. */
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
/*栈向下增长*/
#else /* portSTACK_GROWTH */
{
StackType_t *pxStack;
/* 为任务申请内存堆 */
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
if( pxStack != NULL )
{
/* 为TCB结构体申请内存堆 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */
if( pxNewTCB != NULL )
{
/* 用TCB结构体的成员变量来指向任务内存堆 */
pxNewTCB->pxStack = pxStack;
}
else
{
/* 由于TCB结构体创建失败,因此任务堆不能使用,释放任务内存堆 */
vPortFree( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
}
portSTACK_GROWTH>0表示堆栈向上增长,先申请TCB任务控制块的堆栈空间,再申请任务堆栈;反之,如果堆栈向下增长,先申请任务堆栈,再申请TCB任务控制块的堆栈空间。按这样的顺序申请堆栈空间,堆栈的扩展就不会覆盖掉TCB任务控制块的内容,Cortex-M采用的是向下增长。
以向下增长为例,若先申请TCB任务控制块堆栈空间,再申请任务堆栈(谁先申请堆栈谁的地址更低):
这样任务堆栈的扩展可能会覆盖掉TCB区域,若先申请任务堆栈,再申请TCB任务控制块的堆栈空间:
任务控制块就没有被覆盖的危险了。堆栈向上增长也类似。
堆栈申请完成,判断任务控制块内存是否申请成功(申请失败返回NULL)
if( pxNewTCB != NULL )
如果是动态创建的任务,则进行标记,便于后面删除任务
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
/*将任务标记为动态创建,以便于后面删除任务 */
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
初始化新任务以及将任务添加至就绪列表–后续讲解
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
prvAddNewTaskToReadyList( pxNewTCB );
首先计算出任务栈顶地址(堆栈增长方向不同计算方法不同),再进行地址对齐。
若堆栈向下增长(STM32采用这种增长方式):
#if( portSTACK_GROWTH < 0 )
{
/*计算栈顶地址*/
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
/*将地址作8字节对齐--&(~0x0007)*/
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. */
/* Check the alignment of the calculated top of stack is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
}
pxNewTCB->pxStack为任务块堆栈的起始地址,pxTopOfStack 指向当前任务块堆栈的栈顶。
若堆栈向上增长:
/* 栈向上增长 */
#else /* portSTACK_GROWTH */
{
/*指向任务栈顶*/
pxTopOfStack = pxNewTCB->pxStack;
/* Check the alignment of the stack buffer is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
/* 指向任务栈底 */
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}
pxTopOfStack 指向当前任务块堆栈的栈顶,pxNewTCB->pxEndOfStack指向任务块的栈底。
用for循环将输入的任务名赋值给TCB结构体的成员变量pcTaskName
/* 在TCB结构体中保存任务的名字 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* 自己定义的任务名字是不定长的,若定义的任务名短于configMAX_TASK_NAME_LEN,则赋值完(检测到0)就跳出该循环 */
if( pcName[ x ] == 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
最后在任务名的结尾加上字符串结束标识符’\0’
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
检测任务优先级是否过大,若超过最高优先级,则置为最高优先级,任务的最高优先级为configMAX_PRIORITIES -1,如configMAX_PRIORITIES 为5时,优先级可取0,1,2,3,4
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
将任务优先级赋值给TCB结构体
pxNewTCB->uxPriority = uxPriority;
初始化TCB结构体中的任务状态表、事件表
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
设置TCB控制块状态表的成员变量pvOwner,是属于pxNewTCB的
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
设置TCB控制块任务表成员变量xItemValue的值
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
设置TCB控制块任务表的成员变量pvOwner,是属于pxNewTCB的
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
如果存在任务句柄(输入的任务句柄不为NULL),则将任务句柄指向任务控制块
if( ( void * ) pxCreatedTask != NULL )
{
/* 将任务句柄指向任务控制块TCB */
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
先进入临界区,防止中断打断就绪链表更新
taskENTER_CRITICAL为最强的临界保护,直接屏蔽了中断,使用时应尽量保证临界区较小一些
vTaskSuspendAll仅仅挂起了任务调度器,即关闭了任务调度器,防止任务之间的资源抢夺
taskENTER_CRITICAL();
当前任务数加一
uxCurrentNumberOfTasks++;
当前没有任务,或任务均被挂起,则将该任务设置为将执行的任务,并且如果是第一次创建任务,则需要初始化任务状态链表
if( pxCurrentTCB == NULL )
{
/* 如果没有其它任务,或者其它任务均被挂起 - 将该任务设置为将执行的任务 */
pxCurrentTCB = pxNewTCB;
/*第一次创建任务*/
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
/* 第一次创建任务,初始化任务链表 */
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
如果不是第一次创建任务,且任务调度器未执行,该任务为优先级最高的任务,则将其设置为当前任务
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
任务数加一,该任务数用作任务的编号
uxTaskNumber++;
#if ( configUSE_TRACE_FACILITY == 1 )
{
/* 初始化TCB任务控制块编号 */
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
将任务添加至就绪列表
prvAddTaskToReadyList( pxNewTCB )
prvAddTaskToReadyList( pxNewTCB )实际调用的函数是vListInsertEnd,即采用尾插法,在链表的尾部插入元素
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
pxList:列表项要插入的列表
pxNewListItem :要插入的列表项
若任务调度正在执行,创建的任务优先级高于正在执行的任务,则正在执行的任务“让步”于新任务
if( xSchedulerRunning != pdFALSE )
{
/* 若创建的任务优先级高于正在执行的任务,则正在执行的任务“让步”于新任务 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
在FreeRTOS中,一个任务有多种状态,每种状态对应一个链表,将任务置于不同的状态,实质上就是将任务添加至对应的状态链表。
我们首先来看一下链表初始化函数vListInitialise( List_t * const pxList )
pxIndex表示列表项的索引号,初始状态时,链表中只有xListEnd一个元素,因此pxIndex指向xListEnd
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
在链表中,按照xItemValue升序排列的,xItemValue越小代表优先级越高
将 pxList->xListEnd.xItemValue设置为最大,保证xListEnd始终在链表的最后位置
pxList->xListEnd.xItemValue = portMAX_DELAY;
任务链表为双向链表,对其进行初始化
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
uxNumberOfItems记录链表中元素的个数,刚初试化链表,元素个数为0
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
分析完链表初始化函数后,我们回到任务链表初始化函数prvInitialiseTaskLists
不同优先级的就绪任务,阻塞任务,挂起任务以及已经删除但为释放内存的任务都有对应的链表。
遍历初始化不同优先级的就绪任务链表
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
初始化阻塞任务链表
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
初始化就绪任务过渡链表,此时调度器关闭,一些任务进入就绪状态,但是任务还未放入就绪链表中,等待任务调度器开始,进行一次新的调度
vListInitialise( &xPendingReadyList );
初始化任务删除链表
vListInitialise( &xTasksWaitingTermination );
初始化挂起任务链表
vListInitialise( &xSuspendedTaskList );
同样,删除任务首先进入临界区。
taskENTER_CRITICAL();
如果输入句柄为NULL,则获取当前运行任务的句柄,否则pxTCB就为输入句柄
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
prvGetTCBFromHandle实质如下:
( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) pxCurrentTCB : ( TCB_t * ) ( pxHandle ) )
将任务从链表中删除,若删除后链表中的任务数为0,则清除相应就绪链表的就绪标志位
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
任务是否在等待某个事件,如果是,则将其放置于相应的链表
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
如果删除的任务为正在运行的任务,将需要删除的任务插入xTasksWaitingTermination中,在空闲函数里面来释放内存
/*如果删除的任务为正在运行的任务*/
if( pxTCB == pxCurrentTCB )
{
/* 将需要删除的任务插入xTasksWaitingTermination中,在空闲函数里面来释放内存*/
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
/* uxDeletedTasksWaitingCleanUp为删除链表xTasksWaitingTermination中的任务数 */
++uxDeletedTasksWaitingCleanUp;
/* 删除任务钩子函数,用户自己实现 */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
若删除的任务不是正在运行的任务,则直接删除
else
{
/*正在运行的任务数减1*/
--uxCurrentNumberOfTasks;
/* 删除TCB结构体,释放堆栈和任务控制块内存*/
prvDeleteTCB( pxTCB );
/* 重置下一个任务唤醒时间,避免下一个唤醒的任务为当前删除的任务*/
prvResetNextTaskUnblockTime();
}
退出临界区
taskEXIT_CRITICAL();
如果删除的是正在运行的任务那么就需要强制进行一次任务切换。
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
configASSERT( uxSchedulerSuspended == 0 );
/*强制进行任务切换*/
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
首先获取需要删除的任务处于哪一个链表中
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
将任务从链表中删除,即将任务前后两个任务连接起来。
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
如果pxIndex指向要被删除的任务,则将pxIndex指向要被删除任务的前一项
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
将被被删除对象的成员变量pvContainer清空
pxItemToRemove->pvContainer = NULL;
删除后,链表中任务数减1
( pxList->uxNumberOfItems )--;
最后返回链表中的任务数目
return pxList->uxNumberOfItems;
延时函数首先判断延时时间是否大于0
if( xTicksToDelay > ( TickType_t ) 0U )
挂起任务调度器
vTaskSuspendAll();
挂起任务调度器实质就是将uxSchedulerSuspended加1,当uxSchedulerSuspended大于0时,即不会进行任务调度,当uxSchedulerSuspended为0时就会进行任务调度。
void vTaskSuspendAll( void )
{
++uxSchedulerSuspended;
}
将需要延时的任务添加至延时列表
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
重新恢复任务调度器,并用xAlreadyYielded获取其返回值,若为pdFALSE则未进行任务调度,需要在后续进行任务调度
xAlreadyYielded = xTaskResumeAll();
若未进行任务调度,在此处强制进行任务调度
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
vTaskDelay函数的本质是调用prvAddCurrentTaskToDelayedList,将任务添加至对应的延时、阻塞链表,同样,首先将任务从就绪列表中移除,移除后并所移除的就绪列表中是否还有其余就绪的任务,若剩余就绪任务数为0,则清除该列表的就绪标志位
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* The current task must be in a ready list, so there is no need to
check, and the port reset macro can be called directly. */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
这里我们只考虑允许阻塞的情况,即宏INCLUDE_vTaskSuspend=1时
如果延时时间为最大,且允许阻塞时,直接将任务添加至阻塞列表中去,xCanBlockIndefinitely 为pdTRUE即为允许阻塞
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
{
/* Add the task to the suspended task list instead of a delayed task
list to ensure it is not woken by a timing event. It will block
indefinitely. */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
如果阻塞时间不为最大时,首先计算出唤醒任务的时间,xConstTickCount为执行任务prvAddCurrentTaskToDelayedList的时间,xTicksToWait为延时时间,xTimeToWake为唤醒时间
xTimeToWake = xConstTickCount + xTicksToWait;
将xTimeToWake写入任务列表的状态列表成员变量xItemValue中
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
xTimeToWake为TickType_t类型,为uint_16型,当xTimeToWake<xConstTickCount时,即发生了溢出,则将任务添加至溢出链表pxOverflowDelayedTaskList中
if( xTimeToWake < xConstTickCount )
{
/* Wake time has overflowed. Place this item in the overflow
list. */
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
如果没有发生溢出,则将任务添加至链表pxDelayedTaskList中
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) )
全局变量xNextTaskUnblockTime保存着下一个任务的唤醒时间,如果xTimeToWake<xNextTaskUnblockTime时,即有任务需更快的被唤醒,则更新xNextTaskUnblockTime为xTimeToWake
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。