1.获取任务优先级
-
功能:查询指定任务的优先级。
-
参数 :
xTask为要查询的任务句柄,若为NULL,则查询当前调用任务的优先级。 -
返回值:该任务的当前优先级(数值越大,优先级越高)。
-
const修饰句柄,表明不会通过该句柄修改任务数据。
1.1 源码分析
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )
{
TCB_t const * pxTCB;
UBaseType_t uxReturn;
taskENTER_CRITICAL(); //程序进入临界区
{
/* If null is passed in here then it is the priority of the task
* that called uxTaskPriorityGet() that is being queried. */
/* 若 xTask 为 NULL 则返回当前任务 TCB;否则根据句柄查找对应 TCB */
pxTCB = prvGetTCBFromHandle( xTask );
/* 从 TCB 中直接读取任务的当前调度优先级。
* 注意:如果任务正持有互斥量并且发生了优先级继承,
* 此值可能是暂时提升后的优先级,而非任务原本的基础优先级。
* 基础优先级记录在 pxTCB->uxBasePriority 中。 */
uxReturn = pxTCB->uxPriority;
}
taskEXIT_CRITICAL(); //程序退出临界区
return uxReturn;
}
1.2 使用示例
/* 通过句柄,获取任务的优先级 */
UBaseType_t task_priority = 0;
task_priority = uxTaskPriorityGet(task1_handle);
printf("task1任务优先级=%d\r\n", task_priority);
2.获取系统中任务数量
-
功能 :返回当前系统中已创建且未被删除的任务总数。
-
返回值 :
uxCurrentNumberOfTasks的当前值,类型为UBaseType_t(通常为uint32_t)
2.1 源码分析
UBaseType_t uxTaskGetNumberOfTasks( void )
{
/* A critical section is not required because the variables are of type
* BaseType_t. */
/* 该变量是BaseType_t类型,不需要临界区 */
/* 在 32 位平台上正好是 CPU 的自然字长,且由编译器保证对齐,
* 所以硬件可用单条指令完成读取,该操作是不可打断的(原子),
* 不存在读到"半新半旧"撕裂值的可能,因此无需进入临界区。 */
return uxCurrentNumberOfTasks;
}
2.2 使用示例
/* 获取系统中任务的数量 */
UBaseType_t task_num = 0;
task_num = uxTaskGetNumberOfTasks();
printf("系统中任务数量=%d\r\n", task_num);
3.获取任务状态
-
功能:查询任务的状态。
-
参数 :
xTask为待查任务句柄。 -
返回值 :
eTaskState枚举值,包括eRunning、eReady、eBlocked、eSuspended、eDeleted
3.1 源码分析
eTaskState eTaskGetState( TaskHandle_t xTask )
{
eTaskState eReturn; //存储最终状态
List_t const * pxStateList; //指向 xStateListItem 当前所在的链表(如就绪列表、延迟列表、挂起列表等)
List_t const * pxDelayedList; //指向当前使用的延迟列表
List_t const * pxOverflowedDelayedList; //指向当前使用的溢出延迟列表
const TCB_t * const pxTCB = xTask; //指向目标任务的 TCB,防止修改
/* 断言保证句柄有效,需要用户自定义实现 */
configASSERT( pxTCB );
if( pxTCB == pxCurrentTCB )
{
/* The task calling this function is querying its own state. */
/* 如果查询的是当前任务自己,那它必定处于运行态,直接返回 eRunning
* 这里没有进入临界区,因为当前任务正在运行,其状态不可能同时被改变,读取到的值是确定的 */
eReturn = eRunning;
}
else
{
taskENTER_CRITICAL(); //程序进入临界区,确保读取的列表指针和任务状态一致
{
/* listLIST_ITEM_CONTAINER 返回状态列表项 xStateListItem 当前所在的链表 */
pxStateList = listLIST_ITEM_CONTAINER( &( pxTCB->xStateListItem ) );
/* 保存两个延迟列表的当前指针,用于后续比较 */
pxDelayedList = pxDelayedTaskList;
pxOverflowedDelayedList = pxOverflowDelayedTaskList;
}
taskEXIT_CRITICAL(); //程序退出临界区
/* 判断是否处于阻塞状态 */
/* 如果状态列表项位于任一个延迟列表中,说明任务正在延时(vTaskDelay)或等待事件超时,即处于阻塞态 */
if( ( pxStateList == pxDelayedList ) || ( pxStateList == pxOverflowedDelayedList ) )
{
/* The task being queried is referenced from one of the Blocked
* lists. */
eReturn = eBlocked;
}
/* 判断挂起(Suspended)或挂起下的特殊情况(阻塞于通知) */
#if ( INCLUDE_vTaskSuspend == 1 )
else if( pxStateList == &xSuspendedTaskList ) //判断任务的状态列表项在挂起列表中
{
/* The task being queried is referenced from the suspended
* list. Is it genuinely suspended or is it blocked
* indefinitely? */
/* 处理任务因等待通知而同时位于挂起列表的情况 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL )
{
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
BaseType_t x;
/* The task does not appear on the event list item of
* and of the RTOS objects, but could still be in the
* blocked state if it is waiting on its notification
* rather than waiting on an object. If not, is
* suspended. */
/* 若 xEventListItem 容器为 NULL,正常情况下任务可以判定为 eSuspended。但 FreeRTOS 引入了一个特殊机制:
* 任务通知可以导致任务在无事件列表项的情况下依然处于阻塞态。当任务调用 xTaskNotifyWait 等待通知时,
* 它会被置入延迟列表或直接挂起?其实等待通知的任务如果设置了超时,会进入延迟列表;
* 如果无限期等待,则会进入挂起列表(vTaskSuspend 并非唯一进入挂起列表的路径,
* xTaskNotifyWait 在无限等待时也会将任务放到挂起列表,同时设置 ucNotifyState 为 taskWAITING_NOTIFICATION)。
* 因此,当看到任务在挂起列表中且事件列表项为空时,还需遍历通知状态数组,检查是否有任何一项为 taskWAITING_NOTIFICATION。
* 如果有,则任务实际上是在阻塞等待通知,应返回 eBlocked;否则才是真正的挂起。 */
eReturn = eSuspended;
for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
{
if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
{
eReturn = eBlocked;
break;
}
}
}
#else /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
{
eReturn = eSuspended; //未开启任务通知则直接标记挂起
}
#endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
}
else
{
/* 若事件列表项不为 NULL,说明任务在等待一个内核对象(如队列、信号量),此时无论是否在挂起列表,都应视为 eBlocked。
* 不过正常情况下挂起任务的事件列表项会被清除,此分支更多是防御性编程。 */
eReturn = eBlocked;
}
}
#endif /* if ( INCLUDE_vTaskSuspend == 1 ) */
/* 判断删除(Deleted) */
#if ( INCLUDE_vTaskDelete == 1 )
/* 如果任务在等待终止列表(自身删除但尚未被空闲任务回收),或者状态列表项未插入任何列表(NULL,比如内存刚被部分释放),则返回 eDeleted */
else if( ( pxStateList == &xTasksWaitingTermination ) || ( pxStateList == NULL ) )
{
/* The task being queried is referenced from the deleted
* tasks list, or it is not referenced from any lists at
* all. */
eReturn = eDeleted;
}
#endif
/* 就绪态(Ready)*/
else /*lint !e525 Negative indentation is intended to make use of pre-processor clearer. */
{
/* If the task is not in any other state, it must be in the
* Ready (including pending ready) state. */
/* 排除掉以上所有情况(运行、阻塞、挂起、删除),那么任务必定在就绪列表中(包括被抢占后处于就绪队列中),返回 eReady */
eReturn = eReady;
}
}
return eReturn;
}
3.2 使用示例
/* 获取任务状态 */
eTaskState e_task_state = 0;
e_task_state = eTaskGetState(task1_handle);
printf("task1任务状态=%d\r\n",e_task_state);
4.获取单个任务信息
-
功能 :获取指定任务的各项信息,填充到
TaskStatus_t结构体中。 -
参数:
-
xTask:目标任务句柄,若为NULL则查询调用者自身。 -
pxTaskStatus:输出参数,指向用户提供的TaskStatus_t结构体,用于存储任务信息。 -
xGetFreeStackSpace:是否计算剩余栈空间(pdTRUE则计算,否则跳过以节省时间)。 -
eState:如果调用者已知 任务状态,可直接传入;若传入eInvalid,则函数内部通过eTaskGetState重新查询。
-
-
返回值 :无,结果通过
pxTaskStatus返回
4.1 源码分析
void vTaskGetInfo( TaskHandle_t xTask,
TaskStatus_t * pxTaskStatus,
BaseType_t xGetFreeStackSpace,
eTaskState eState )
{
TCB_t * pxTCB;
/* xTask is NULL then get the state of the calling task. */
/* 获取任务 TCB */
/* 若 xTask == NULL,返回当前任务 TCB;否则根据句柄查找对应 TCB */
pxTCB = prvGetTCBFromHandle( xTask );
/* 填充信息 */
pxTaskStatus->xHandle = ( TaskHandle_t ) pxTCB; //任务句柄
pxTaskStatus->pcTaskName = ( const char * ) &( pxTCB->pcTaskName[ 0 ] ); //任务名称
pxTaskStatus->uxCurrentPriority = pxTCB->uxPriority; //任务当前优先级
pxTaskStatus->pxStackBase = pxTCB->pxStack; //栈起始地址
#if ( ( portSTACK_GROWTH > 0 ) && ( configRECORD_STACK_HIGH_ADDRESS == 1 ) ) //栈向上增长且开启记录栈末端地址
pxTaskStatus->pxTopOfStack = pxTCB->pxTopOfStack; //栈顶地址
pxTaskStatus->pxEndOfStack = pxTCB->pxEndOfStack; //栈结束地址
#endif
pxTaskStatus->xTaskNumber = pxTCB->uxTCBNumber; //任务编号
#if ( configUSE_MUTEXES == 1 ) //启用互斥信号量
{
pxTaskStatus->uxBasePriority = pxTCB->uxBasePriority; //任务基础优先级
}
#else
{
pxTaskStatus->uxBasePriority = 0; //未启用互斥量则基础优先级记为 0,因为此时 uxPriority 已经是永远不变的原始优先级
}
#endif
#if ( configGENERATE_RUN_TIME_STATS == 1 ) //启用运行时间统计
{
pxTaskStatus->ulRunTimeCounter = pxTCB->ulRunTimeCounter; //记录任务消耗的 CPU 时间
}
#else
{
pxTaskStatus->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0; //未启用运行时间统计则记录为 0
}
#endif
/* Obtaining the task state is a little fiddly, so is only done if the
* value of eState passed into this function is eInvalid - otherwise the
* state is just set to whatever is passed in. */
/* 确定任务状态 */
if( eState != eInvalid ) //调用者传入了状态
{
if( pxTCB == pxCurrentTCB )
{
/* 传入任务为当前运行任务,标记运行态 */
pxTaskStatus->eCurrentState = eRunning;
}
else
{
pxTaskStatus->eCurrentState = eState; //直接拷贝传入状态
#if ( INCLUDE_vTaskSuspend == 1 )
{
/* If the task is in the suspended list then there is a
* chance it is actually just blocked indefinitely - so really
* it should be reported as being in the Blocked state. */
/* 即使认为任务是 eSuspended,也必须检查其事件列表项。若事件列表项不为空(等待队列/信号量/通知),
* 若事件列表项非空,说明任务正无限期阻塞于某个内核对象(如队列、信号量、事件组),
* 此时逻辑上属于阻塞态,应改为 eBlocked。
* 注意:此检查不覆盖"无限等待任务通知"的情况,后者事件列表项为空,
* 但任务实际上也属于无限期阻塞(这在 eTaskGetState 中会通过通知状态纠正,此处因性能原因未处理)。 */
if( eState == eSuspended )
{
vTaskSuspendAll();
{
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
pxTaskStatus->eCurrentState = eBlocked;
}
}
( void ) xTaskResumeAll();
}
}
#endif /* INCLUDE_vTaskSuspend */
}
}
else //调用者未传入状态,调用函数进行状态查询
{
pxTaskStatus->eCurrentState = eTaskGetState( pxTCB );
}
/* Obtaining the stack space takes some time, so the xGetFreeStackSpace
* parameter is provided to allow it to be skipped. */
/* 计算剩余栈空间(可选) */
if( xGetFreeStackSpace != pdFALSE ) //需要计算
{
/* 计算任务的剩余栈空间(高水位标记法)。
* 任务栈在初始化时全部填充为魔数 0xA5 (tskSTACK_FILL_BYTE)。
* 随着任务运行,栈的使用会覆盖这些魔数值。
* 本函数从栈的"远端"(即最后被使用的区域)开始扫描,
* 统计仍保持为 0xA5 的连续字节数,从而得到未被使用的最小栈空间。
*
* 栈的生长方向由 portSTACK_GROWTH 宏决定,影响扫描的起点和方向:
*
* 【栈向下生长 (portSTACK_GROWTH < 0, 如 ARM Cortex-M)】
* - 内存布局:高地址为栈顶(起始),低地址为栈底(pxStack)。
* - 栈使用方向:从高地址向低地址推进。
* - 检查起点:从最低地址的栈底(pxStack)开始。
* - 扫描方向:向高地址移动,寻找连续 0xA5 的区域。
* - 指针移动:pucStackByte -= portSTACK_GROWTH;
* 由于 GROWTH 为 -1,实际等同于 pucStackByte += 1(向后移动)。
*
* 【栈向上生长 (portSTACK_GROWTH > 0, 如部分 MIPS/PIC32)】
* - 内存布局:低地址为栈顶(起始),高地址为栈底。
* - 栈使用方向:从低地址向高地址推进。
* - 检查起点:从最高地址的栈末端(pxEndOfStack)开始。
* - 扫描方向:向低地址移动,寻找连续 0xA5 的区域。
* - 指针移动:pucStackByte -= portSTACK_GROWTH;
* 由于 GROWTH 为 1,等同于 pucStackByte -= 1(向前移动)。
*
* 返回值为连续未使用"字(通常4字节)"的数量,而非字节数。*/
#if ( portSTACK_GROWTH > 0 )
{
/* 栈向上增长时,则从 pxEndOfStack 开始 */
pxTaskStatus->usStackHighWaterMark = prvTaskCheckFreeStackSpace( ( uint8_t * ) pxTCB->pxEndOfStack );
}
#else
{
/* 对栈向下增长的情况(portSTACK_GROWTH < 0),传入 pxTCB->pxStack(栈底)作为扫描起点,背离栈顶方向检查直至遇到第一个改变了的值 */
pxTaskStatus->usStackHighWaterMark = prvTaskCheckFreeStackSpace( ( uint8_t * ) pxTCB->pxStack );
}
#endif
}
else //不需要计算,直接传递0
{
pxTaskStatus->usStackHighWaterMark = 0;
}
}
4.2 使用示例
/* 获取单个任务的信息 */
TaskStatus_t task_state;
vTaskGetInfo(
task1_handle, // 要获取的任务信息的句柄
&task_state, // 用来保存查询到的任务状态信息
pdTRUE, // 要查询 任务栈 历史剩余最小值
eInvalid); // 设为eInvalid,才会真正去获取指定任务的状态
printf("任务名称:%s\r\n", task_state.pcTaskName);
printf("任务编号:%d\r\n", task_state.xTaskNumber);
printf("任务状态:%d\r\n", task_state.eCurrentState);
printf("任务优先级:%d\r\n", task_state.uxCurrentPriority);
5.获取所有任务的任务信息
-
功能:获取系统中当前所有任务的状态快照,填充到数组中,并可选地提供总 CPU 运行时间。
-
参数:
-
pxTaskStatusArray:用户分配的TaskStatus_t数组,用于存储结果。 -
uxArraySize:数组的容量(最多能存多少个任务)。 -
pulTotalRunTime:指向一个变量的指针,用于返回总运行时间;若为NULL则跳过。
-
-
返回值:实际填充到数组中的任务数量
5.1 源码分析
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
const UBaseType_t uxArraySize,
configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime )
{
/* uxTask 记录已经填充到数组中的任务个数(即数组的当前写入索引)
* uxQueue 初始化为最高优先级,用于从高到低遍历就绪列表 */
UBaseType_t uxTask = 0, uxQueue = configMAX_PRIORITIES;
vTaskSuspendAll(); //挂起调度器(但不关中断)。这确保在获取所有任务信息的过程中,任务不会被切换,也不会被添加到/移出各种列表,从而得到一个一致的快照
{
/* Is there a space in the array for each task in the system? */
/* 只有当数组足够大,能容纳当前系统所有任务时,才执行填充
* 如果数组太小,则不进行任何操作,直接返回(此时 uxTask 仍为初始值 0) */
if( uxArraySize >= uxCurrentNumberOfTasks )
{
/* prvListTasksWithinSingleList本质上就是遍历由 pxList 指定的内核链表(例如某个优先级的就绪列表,或延迟列表),
* 对链表中的每个任务调用 vTaskGetInfo 填写一个 TaskStatus_t 结构体,并将预设状态 eState 传递给该结构体 */
/* Fill in an TaskStatus_t structure with information on each
* task in the Ready state. */
/* 这里从最高优先级 configMAX_PRIORITIES-1 一直遍历到 tskIDLE_PRIORITY(空闲任务优先级),确保所有就绪列表中的任务都被收录 */
/* 每次调用 prvListTasksWithinSingleList,uxTask 都会增加对应数量,数组的写入位置也相应后移 */
do
{
uxQueue--;
uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &( pxReadyTasksLists[ uxQueue ] ), eReady );
} while( uxQueue > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
/* Fill in an TaskStatus_t structure with information on each
* task in the Blocked state. */
/* pxDelayedTaskList 和 pxOverflowDelayedTaskList 存放正在延时或超时等待的任务(即阻塞态)。
* 两个列表都要遍历,以覆盖所有可能因系统节拍计数溢出而分散在两个列表中的任务。
* 分配给这些任务的状态均标记为 eBlocked */
uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxDelayedTaskList, eBlocked );
uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxOverflowDelayedTaskList, eBlocked );
/* 如果启用了任务删除功能,则把 xTasksWaitingTermination 列表中所有等待空闲任务清理的任务也纳入快照,状态标记为 eDeleted */
#if ( INCLUDE_vTaskDelete == 1 )
{
/* Fill in an TaskStatus_t structure with information on
* each task that has been deleted but not yet cleaned up. */
uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xTasksWaitingTermination, eDeleted );
}
#endif
/* 若支持任务挂起,则把 xSuspendedTaskList 中的任务信息填入数组,并标记为 eSuspended */
#if ( INCLUDE_vTaskSuspend == 1 )
{
/* Fill in an TaskStatus_t structure with information on
* each task in the Suspended state. */
uxTask += prvListTasksWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xSuspendedTaskList, eSuspended );
}
#endif
/* 若启用运行时间统计且用户提供了非空指针,则读取当前系统的总运行计数器值
* 反之,则置0 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
if( pulTotalRunTime != NULL )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ( *pulTotalRunTime ) );
#else
*pulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
}
}
#else /* if ( configGENERATE_RUN_TIME_STATS == 1 ) */
{
if( pulTotalRunTime != NULL )
{
*pulTotalRunTime = 0;
}
}
#endif /* if ( configGENERATE_RUN_TIME_STATS == 1 ) */
}
else
{
mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空
}
}
/* 解除调度器挂起,恢复正常的任务调度。如果在此期间有高优先级任务被解锁或恢复了,将在此处触发一次上下文切换。 */
( void ) xTaskResumeAll();
return uxTask;
}
5.2 使用示例
TaskStatus_t task_status[10] = 0;
UBaseType_t task_num = 0;
uxTaskGetSystemState(task_status, task_num, NULL);
printf("任务名\t\t任务编号\t\t任务状态\t\t当前优先级\t\t任务原始优先级\r\n");
for (uint8_t i = 0; i < task_num; i++)
{
printf("%s\t\t%d\t\t%d\t\t%d\t\t%d\r\n",
task_status[i].pcTaskName,
task_status[i].xTaskNumber,
task_status[i].eCurrentState,
task_status[i].uxCurrentPriority,
task_status[i].uxBasePriority);
}
6.获取当前任务的任务句柄
-
功能:返回调用者的任务句柄(即当前任务的控制块指针)
-
参数:无
-
返回值 :当前任务的句柄(
pxCurrentTCB的值)
6.1 源码分析
TaskHandle_t xTaskGetCurrentTaskHandle( void )
{
TaskHandle_t xReturn;
/* A critical section is not required as this is not called from
* an interrupt and the current TCB will always be the same for any
* individual execution thread. */
xReturn = pxCurrentTCB;
return xReturn;
}
6.2 使用示例
/* 获取当前任务的任务句柄 */
TaskHandle_t task_handle = 0;
task_handle = xTaskGetCurrentTaskHandle();
printf("获取到的当前任务句柄=%p,task2的句柄=%p\r\n", task_handle, task2_handle);
7.通过任务名获取指定任务句柄
-
功能 :根据任务名称(字符串)在系统中查找对应任务,返回其句柄;若未找到则返回
NULL。 -
参数 :
pcNameToQuery为要查找的任务名称(C 字符串)。 -
返回值 :匹配的任务句柄,或
NULL。
7.1 源码分析
TaskHandle_t xTaskGetHandle( const char * pcNameToQuery ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
UBaseType_t uxQueue = configMAX_PRIORITIES; //始化为最高优先级,接下来从高到低遍历就绪列表。这是为了优先找到高优先级的任务
TCB_t * pxTCB; //用于接收找到的 TCB 指针
/* Task names will be truncated to configMAX_TASK_NAME_LEN - 1 bytes. */
/* 断言名称长度,需用户自定义实现 */
configASSERT( strlen( pcNameToQuery ) < configMAX_TASK_NAME_LEN );
vTaskSuspendAll(); //挂起任务调度器, 与临界区(taskENTER_CRITICAL)相比,vTaskSuspendAll 允许中断发生,但对任务的上下文切换加锁,适合较长时间遍历
{
/* Search the ready lists. */
/* 循环从 configMAX_PRIORITIES - 1 递减到 tskIDLE_PRIORITY(包含空闲优先级) */
do
{
uxQueue--;
/* 遍历指定链表,逐个比较任务名称,找到后返回 TCB 指针,否则返回 NULL */
pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) &( pxReadyTasksLists[ uxQueue ] ), pcNameToQuery );
if( pxTCB != NULL )
{
/* Found the handle. */
break;
}
} while( uxQueue > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
/* 若在就绪列表未找到,任务可能正处于延时或超时等待状态。
* 依次在pxDelayedTaskList 和 pxOverflowDelayedTaskList 列表中查找 */
/* Search the delayed lists. */
if( pxTCB == NULL )
{
pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) pxDelayedTaskList, pcNameToQuery );
}
if( pxTCB == NULL )
{
pxTCB = prvSearchForNameWithinSingleList( ( List_t * ) pxOverflowDelayedTaskList, pcNameToQuery );
}
/* 若启用了任务挂起功能,继续在挂起列表 xSuspendedTaskList 中搜索 */
#if ( INCLUDE_vTaskSuspend == 1 )
{
if( pxTCB == NULL )
{
/* Search the suspended list. */
pxTCB = prvSearchForNameWithinSingleList( &xSuspendedTaskList, pcNameToQuery );
}
}
#endif
/* 如果启用了任务删除,且在之前列表中都未找到,任务可能已被删除但尚未被空闲任务回收,在xTasksWaitingTermination 中搜索 */
#if ( INCLUDE_vTaskDelete == 1 )
{
if( pxTCB == NULL )
{
/* Search the deleted list. */
pxTCB = prvSearchForNameWithinSingleList( &xTasksWaitingTermination, pcNameToQuery );
}
}
#endif
}
( void ) xTaskResumeAll(); //解除调度器挂起,如果有待处理的上下文切换请求,将在此处执行
return pxTCB;
}
7.2 使用示例
task_handle = xTaskGetHandle("task1");
printf("获取到的名为task1任务句柄=%p,task1的句柄=%p\r\n", task_handle, task1_handle);
8.获取任务栈历史剩余最小值
-
功能 :返回任务从创建以来未被使用的栈空间的最小值(以字为单位)。数值越低,说明栈使用量越接近极限,越可能发生溢出。
-
参数 :
xTask为待查任务句柄;若为NULL则查询当前任务自身。 -
返回值:剩余栈空间的"高水位"值(单位:字,通常 4 字节)。
8.1 源码分析
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
{
TCB_t * pxTCB; //指向目标任务控制块
uint8_t * pucEndOfStack; //指向栈的"远端"地址(即栈初始化时未被使用的区域的起点),传递给 prvTaskCheckFreeStackSpace 作为扫描起点
UBaseType_t uxReturn; //保存并返回剩余栈空间值
/* 若 xTask 为 NULL,返回当前任务 TCB;否则根据句柄查找对应 TCB */
pxTCB = prvGetTCBFromHandle( xTask );
/* 对于栈向下生长(portSTACK_GROWTH < 0,如 ARM Cortex‑M):
* 栈底对应最低地址,即 pxTCB->pxStack。
* 栈从未被使用的"远端"正是栈底,因此将 pucEndOfStack 指向 pxTCB->pxStack。
* 扫描将从低地址向高地址推进,统计连续保留的魔数(0xA5)的数量。
* 对于栈向上生长(portSTACK_GROWTH > 0,如部分 MIPS):
* 栈顶在低地址,栈底在高地址,栈末端为 pxTCB->pxEndOfStack。
* 未使用的部分位于栈的末端(高地址),因此从此处开始向低地址扫描。 */
#if portSTACK_GROWTH < 0
{
pucEndOfStack = ( uint8_t * ) pxTCB->pxStack;
}
#else
{
pucEndOfStack = ( uint8_t * ) pxTCB->pxEndOfStack;
}
#endif
/* 计算任务的剩余栈空间(高水位标记法)。
* 任务栈在初始化时全部填充为魔数 0xA5 (tskSTACK_FILL_BYTE)。
* 随着任务运行,栈的使用会覆盖这些魔数值。
* 本函数从栈的"远端"(即最后被使用的区域)开始扫描,
* 统计仍保持为 0xA5 的连续字节数,从而得到未被使用的最小栈空间。
*
* 栈的生长方向由 portSTACK_GROWTH 宏决定,影响扫描的起点和方向:
*
* 【栈向下生长 (portSTACK_GROWTH < 0, 如 ARM Cortex-M)】
* - 内存布局:高地址为栈顶(起始),低地址为栈底(pxStack)。
* - 栈使用方向:从高地址向低地址推进。
* - 检查起点:从最低地址的栈底(pxStack)开始。
* - 扫描方向:向高地址移动,寻找连续 0xA5 的区域。
* - 指针移动:pucStackByte -= portSTACK_GROWTH;
* 由于 GROWTH 为 -1,实际等同于 pucStackByte += 1(向后移动)。
*
* 【栈向上生长 (portSTACK_GROWTH > 0, 如部分 MIPS/PIC32)】
* - 内存布局:低地址为栈顶(起始),高地址为栈底。
* - 栈使用方向:从低地址向高地址推进。
* - 检查起点:从最高地址的栈末端(pxEndOfStack)开始。
* - 扫描方向:向低地址移动,寻找连续 0xA5 的区域。
* - 指针移动:pucStackByte -= portSTACK_GROWTH;
* 由于 GROWTH 为 1,等同于 pucStackByte -= 1(向前移动)。
*
* 返回值为连续未使用"字(通常4字节)"的数量,而非字节数。*/
uxReturn = ( UBaseType_t ) prvTaskCheckFreeStackSpace( pucEndOfStack );
return uxReturn;
}
8.2 使用示例
UBaseType_t task_stack_remain_min = 0;
task_stack_remain_min = uxTaskGetStackHighWaterMark(task2_handle);
printf("task2任务栈历史剩余最小值=%d\r\n",task_stack_remain_min);
9.获取系统中的任务信息(表格形式)
-
功能:生成一个包含所有任务信息的字符串表格,写入用户提供的缓冲区。
-
参数 :
pcWriteBuffer是一个指向字符缓冲区的指针,由调用者分配且需足够大,以容纳所有任务的信息。 -
注意 :该函数主要用于演示和调试,不建议在产品代码中使用 ,因为它会调用耗时的
sprintf,并且会动态分配内存
9.1 源码分析
void vTaskList( char * pcWriteBuffer )
{
TaskStatus_t * pxTaskStatusArray; //指向动态分配的 TaskStatus_t 数组,用于存储每个任务的快照
UBaseType_t uxArraySize, x; //数组的大小(任务总数)、循环索引
char cStatus; // 状态字符,如 'R'(运行)、'B'(阻塞)等
/*
* PLEASE NOTE:
*
* This function is provided for convenience only, and is used by many
* of the demo applications. Do not consider it to be part of the
* scheduler.
*
* vTaskList() calls uxTaskGetSystemState(), then formats part of the
* uxTaskGetSystemState() output into a human readable table that
* displays task: names, states, priority, stack usage and task number.
* Stack usage specified as the number of unused StackType_t words stack can hold
* on top of stack - not the number of bytes.
*
* vTaskList() has a dependency on the sprintf() C library function that
* might bloat the code size, use a lot of stack, and provide different
* results on different platforms. An alternative, tiny, third party,
* and limited functionality implementation of sprintf() is provided in
* many of the FreeRTOS/Demo sub-directories in a file called
* printf-stdarg.c (note printf-stdarg.c does not provide a full
* snprintf() implementation!).
*
* It is recommended that production systems call uxTaskGetSystemState()
* directly to get access to raw stats data, rather than indirectly
* through a call to vTaskList().
*/
/* 仅为便利提供:vTaskList 不是调度器核心部分,而是演示用途。
* 内部调用 uxTaskGetSystemState:首先获取所有任务的快照数据,再格式化为可读表格。
* 信息内容:表格列包括任务名、状态、优先级、栈高水位(未使用的栈字数)、任务编号。
* 依赖 sprintf:可能增加代码体积,不同平台表现可能不同。演示目录中提供了简化版 printf-stdarg.c。
* 生产系统的建议:直接调用 uxTaskGetSystemState 获取原始数据,而非通过 vTaskList 间接格式化。 */
/* Make sure the write buffer does not contain a string. */
*pcWriteBuffer = ( char ) 0x00; //初始化写入缓冲区
/* Take a snapshot of the number of tasks in case it changes while this
* function is executing. */
uxArraySize = uxCurrentNumberOfTasks; //记录任务数量快照
/* Allocate an array index for each task. NOTE! if
* configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
* equate to NULL. */
/* 使用 FreeRTOS 的内存分配函数(通常指向 pvPortMalloc)分配一个与当前任务数量匹配的 TaskStatus_t 数组 */
/* 如果 configSUPPORT_DYNAMIC_ALLOCATION 为 0,则 pvPortMalloc 可能返回 NULL */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */
if( pxTaskStatusArray != NULL ) //检查分配是否成功
{
/* Generate the (binary) data. */
/* 调用 uxTaskGetSystemState 获取所有任务信息
* 填充 pxTaskStatusArray,并返回实际填充的任务数 */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, NULL );
/* Create a human readable table from the binary data. */
/* 遍历所有任务,生成表格字符串 */
for( x = 0; x < uxArraySize; x++ )
{
/* 状态字符转换 */
switch( pxTaskStatusArray[ x ].eCurrentState )
{
case eRunning:
cStatus = tskRUNNING_CHAR;
break;
case eReady:
cStatus = tskREADY_CHAR;
break;
case eBlocked:
cStatus = tskBLOCKED_CHAR;
break;
case eSuspended:
cStatus = tskSUSPENDED_CHAR;
break;
case eDeleted:
cStatus = tskDELETED_CHAR;
break;
case eInvalid: /* Fall through. */
default: /* Should not get here, but it is included
* to prevent static checking errors. */
cStatus = ( char ) 0x00;
break;
}
/* Write the task name to the string, padding with spaces so it
* can be printed in tabular form more easily. */
/* 写入任务名称(固定宽度,用空格填充) */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
/* Write the rest of the string. */
/* 格式化并追加状态、优先级、栈高水位、任务编号 */
sprintf( pcWriteBuffer, "\t%c\t%u\t%u\t%u\r\n", cStatus, ( unsigned int ) pxTaskStatusArray[ x ].uxCurrentPriority, ( unsigned int ) pxTaskStatusArray[ x ].usStackHighWaterMark, ( unsigned int ) pxTaskStatusArray[ x ].xTaskNumber ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
/* 移动缓冲区指针到字符串末尾 */
pcWriteBuffer += strlen( pcWriteBuffer ); /*lint !e9016 Pointer arithmetic ok on char pointers especially as in this case where it best denotes the intent of the code. */
}
/* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION
* is 0 then vPortFree() will be #defined to nothing. */
vPortFree( pxTaskStatusArray ); //释放之前分配的 TaskStatus_t 数组
}
else
{
mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空
}
}
9.2 使用示例
/* 以表格形式显示系统中的任务信息 */
char task_info[500];
vTaskList(task_info);
printf("%s\r\n",task_info);
10.获取任务运行时间
-
功能:生成一个包含所有任务运行时间统计信息的字符串表格,写入用户提供的缓冲区。
-
参数 :
pcWriteBuffer是一个指向字符缓冲区的指针,由调用者分配且需足够大,以容纳所有任务的信息。 -
注意 :该函数与
vTaskList()类似,主要用于演示和调试,不建议在产品代码中使用 ,因为它会调用耗时的sprintf,并且会动态分配内存。
10.1 源码解析
void vTaskGetRunTimeStats( char * pcWriteBuffer )
{
TaskStatus_t * pxTaskStatusArray; //指向动态分配的 TaskStatus_t 数组,用于存储每个任务的运行时间快照
UBaseType_t uxArraySize, x; //数组的容量(任务总数), 循环索引
configRUN_TIME_COUNTER_TYPE ulTotalTime, ulStatsAsPercentage; //所有任务消耗的总运行时间(从 uxTaskGetSystemState 获得),前任务消耗的 CPU 时间百分比
/*
* PLEASE NOTE:
*
* This function is provided for convenience only, and is used by many
* of the demo applications. Do not consider it to be part of the
* scheduler.
*
* vTaskGetRunTimeStats() calls uxTaskGetSystemState(), then formats part
* of the uxTaskGetSystemState() output into a human readable table that
* displays the amount of time each task has spent in the Running state
* in both absolute and percentage terms.
*
* vTaskGetRunTimeStats() has a dependency on the sprintf() C library
* function that might bloat the code size, use a lot of stack, and
* provide different results on different platforms. An alternative,
* tiny, third party, and limited functionality implementation of
* sprintf() is provided in many of the FreeRTOS/Demo sub-directories in
* a file called printf-stdarg.c (note printf-stdarg.c does not provide
* a full snprintf() implementation!).
*
* It is recommended that production systems call uxTaskGetSystemState()
* directly to get access to raw stats data, rather than indirectly
* through a call to vTaskGetRunTimeStats().
*/
/* 仅为便利提供:vTaskGetRunTimeStats 不是调度器核心部分,而是演示用途。
* 内部调用 uxTaskGetSystemState:首先获取所有任务的快照数据(包括每个任务消耗的 CPU 时间),再格式化为可读表格。
* 依赖 sprintf:可能增加代码体积,不同平台表现可能不同。FreeRTOS 演示目录中提供了简化版 printf-stdarg.c。
* 生产系统建议:直接调用 uxTaskGetSystemState 获取原始统计数据,而非通过 vTaskGetRunTimeStats 间接格式化 */
/* Make sure the write buffer does not contain a string. */
/* 缓冲区的第一个字节置为 '\0'(空字符串)。这样即使后续没有写入任何任务数据,缓冲区仍是一个有效的空字符串 */
*pcWriteBuffer = ( char ) 0x00;
/* Take a snapshot of the number of tasks in case it changes while this
* function is executing. */
/* 读取当前任务总数作为一个快照值。因为在函数执行期间任务数量可能变化(例如其他任务删除或创建),后续操作以此快照为准 */
uxArraySize = uxCurrentNumberOfTasks;
/* Allocate an array index for each task. NOTE! If
* configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
* equate to NULL. */
/* 使用 FreeRTOS 的内存分配函数(通常指向 pvPortMalloc)分配一个与当前任务数量匹配的 TaskStatus_t 数组 */
/* 如果 configSUPPORT_DYNAMIC_ALLOCATION 为 0,则 pvPortMalloc 可能返回 NULL */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */
if( pxTaskStatusArray != NULL ) //检查分配是否成功
{
/* Generate the (binary) data. */
/* 调用 uxTaskGetSystemState 获取所有任务信息
* 填充 pxTaskStatusArray,并返回实际填充的任务数
* 第三个参数 &ulTotalTime:不同于 vTaskList 传 NULL,这里传入了一个有效指针,
* 以获取所有任务消耗的总 CPU 运行时间(即系统运行的总时间计数)*/
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );
/* For percentage calculations. */
/* 将总时间除以 100。这是为了简化后续的百分比计算:直接使用 ulRunTimeCounter / ulTotalTime 即可得到百分比(0~100),而无需再乘以 100 */
ulTotalTime /= 100UL;
/* Avoid divide by zero errors. */
/* 只有总时间大于零时才生成表格,确保后续除法是安全的,避免除0错误*/
if( ulTotalTime > 0UL )
{
/* Create a human readable table from the binary data. */
/* 遍历所有任务,生成表格字符串 */
for( x = 0; x < uxArraySize; x++ )
{
/* What percentage of the total run time has the task used?
* This will always be rounded down to the nearest integer.
* ulTotalRunTime has already been divided by 100. */
/* 计算当前任务消耗的 CPU 时间百分比 */
ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;
/* Write the task name to the string, padding with
* spaces so it can be printed in tabular form more
* easily. */
/* 写入任务名称(固定宽度,用空格填充) */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
/* 百分比大于0,正常输出绝对值和百分比 */
if( ulStatsAsPercentage > 0UL )
{
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t%lu%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter, ulStatsAsPercentage );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller
* printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t%u%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter, ( unsigned int ) ulStatsAsPercentage ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
}
#endif
}
else /* 百分比为零,说明任务消耗的时间不足总时间的1%,显示 "<1%" */
{
/* If the percentage is zero here then the task has
* consumed less than 1% of the total run time. */
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t<1%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller
* printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t<1%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
}
#endif
}
/* 移动缓冲区指针到字符串末尾 */
pcWriteBuffer += strlen( pcWriteBuffer ); /*lint !e9016 Pointer arithmetic ok on char pointers especially as in this case where it best denotes the intent of the code. */
}
}
else
{
mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空
}
/* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION
* is 0 then vPortFree() will be #defined to nothing. */
vPortFree( pxTaskStatusArray ); //释放之前分配的 TaskStatus_t 数组
}
else
{
mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空
}
}
10.2 使用示例
char task_time_info[500];
vTaskGetRunTimeStats(task_time_info);
printf("%s\r\n",task_time_info);
11.声明
(1)Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
(2)文中代码来自FreeRTOS,遵循MIT许可证,许可证可参考:https://opensource.org/licenses/MIT
/*
* FreeRTOS Kernel V10.5.1
* Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* https://www.FreeRTOS.org
* https://github.com/FreeRTOS
*
*/
【以上内容为个人在学习FreeRTOS过程中的源码解读笔记,欢迎大家在评论区讨论指正。】
【如果本篇内容对你有帮助,不妨点个关注,你的支持是我持续更新的动力!】