FreeRTOS——TCB任务控制块、任务句柄、任务栈详解

任务控制块结构体

任务控制块是 FreeRTOS 中用于描述和管理任务的数据结构,包含了任务的状态、优先级、堆栈等信息。

TCB_t的全称为Task Control Block,也就是任务控制块,这个结构体包含了一个任务所有的信息,它的定义以及相关变量的解释如下:

typedef struct tskTaskControlBlock             
    {
        // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
        volatile StackType_t    *pxTopOfStack;    

        // MPU相关暂时不讨论
        #if ( portUSING_MPU_WRAPPERS == 1 )
            xMPU_SETTINGS    xMPUSettings;        
        #endif

        // 表示任务状态,不同的状态会挂接在不同的状态链表下
        ListItem_t            xStateListItem;    
        // 事件链表项,会挂接到不同事件链表下
        ListItem_t            xEventListItem;        
        // 任务优先级,数值越大优先级越高
        UBaseType_t            uxPriority;            
        // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
        StackType_t            *pxStack;            
        // 任务名
        char                pcTaskName[ configMAX_TASK_NAME_LEN ];

        // 指向栈尾,可以用来检测堆栈是否溢出
        #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
            StackType_t        *pxEndOfStack;        
        #endif

        // 记录临界段的嵌套层数
        #if ( portCRITICAL_NESTING_IN_TCB == 1 )
            UBaseType_t        uxCriticalNesting;    
        #endif

        // 跟踪调试用的变量
        #if ( configUSE_TRACE_FACILITY == 1 )
            UBaseType_t        uxTCBNumber;        
            UBaseType_t        uxTaskNumber;        
        #endif

        // 任务优先级被临时提高时,保存任务原本的优先级
        #if ( configUSE_MUTEXES == 1 )
            UBaseType_t        uxBasePriority;        
            UBaseType_t        uxMutexesHeld;
        #endif

        // 任务的一个标签值,可以由用户自定义它的意义,例如可以传入一个函数指针可以用来做Hook    函数调用
        #if ( configUSE_APPLICATION_TASK_TAG == 1 )
            TaskHookFunction_t pxTaskTag;
        #endif

        // 任务的线程本地存储指针,可以理解为这个任务私有的存储空间
        #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
            void            *pvThreadLocalStoragePointers[     configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
        #endif

        // 运行时间变量
        #if( configGENERATE_RUN_TIME_STATS == 1 )
            uint32_t        ulRunTimeCounter;    
        #endif

        // 支持NEWLIB的一个变量
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
            struct    _reent xNewLib_reent;
        #endif

        // 任务通知功能需要用到的变量
        #if( configUSE_TASK_NOTIFICATIONS == 1 )
            // 任务通知的值 
            volatile uint32_t ulNotifiedValue;
            // 任务通知的状态
            volatile uint8_t ucNotifyState;
        #endif

        // 用来标记这个任务的栈是不是静态分配的
        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) 
            uint8_t    ucStaticallyAllocated;         
        #endif

        // 延时是否被打断
        #if( INCLUDE_xTaskAbortDelay == 1 )
            uint8_t ucDelayAborted;
        #endif

        // 错误标识
        #if( configUSE_POSIX_ERRNO == 1 )
            int iTaskErrno;
        #endif

    } tskTCB;
    typedef tskTCB TCB_t;

栈顶指针必须为TCB结构体的第一项,原因是栈顶指针与我们的任务上下文保存、任务切换和任务恢复等操作息息相关。 

每个任务都有自己的任务控制块,类似身份证。

TCB结构体中portSTACK_GROWTH>0表示栈是向上生长,<0表示栈是向下生长,我们STM32的栈是高地址往低地址存储,是向下生长的;而堆是低地址往高地址生长,是向上生长的。

 

任务句柄

        任务句柄(Task Handle)是在 FreeRTOS 中用于标识和引用任务的数据类型。每个创建的任务都会分配一个唯一的任务句柄,通过该句柄可以对任务进行操作和管理。

任务句柄是一个指向任务控制块(Task Control Block,TCB)的指针

        使用任务句柄,可以通过 FreeRTOS 提供的 API 函数对任务进行操作,例如挂起(suspend)、恢复(resume)、删除(delete)任务,或者查询任务的状态等。另外,任务句柄还可以用于任务通信和同步的机制,例如向任务发送信号量或消息。

        在创建任务时,通过调用 FreeRTOS 提供的任务创建函数(例如 xTaskCreate())可以获取到相应任务的句柄。你可以将该句柄保存在一个变量中,以便后续对该任务进行操作或引用。

任务句柄提供了一种有效的方式来管理和操作 FreeRTOS 中的任务。通过使用任务句柄,可以方便地对任务进行控制和监视。

任务栈

图解

任务创建 static void prvInitialiseNewTask(…)
这个函数用于创建新的任务,其中的 “prv” 表示该函数是一个私有函数,只用于内部处理和初始化新任务的操作。对于外部使用者来说,应该使用公开的 API 函数来创建和管理任务,而不是直接调用 “prvInitialiseNewTask”。
这个函数被 TaskHandle_t xTaskCreateStatic() 函数调用
函数源代码如下:

static void prvInitialiseNewTask( 	TaskFunction_t pxTaskCode,              /* 任务入口 */
									const char * const pcName,              /* 任务名称,字符串形式 */
									const uint32_t ulStackDepth,            /* 任务栈大小,单位为字 */
									void * const pvParameters,              /* 任务形参 */
									TaskHandle_t * const pxCreatedTask,     /* 任务句柄 */
									TCB_t *pxNewTCB )                       /* 任务控制块指针 */

{
	StackType_t *pxTopOfStack; //栈顶
	UBaseType_t x;	
	
	/* 获取栈顶地址 */
	pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
	//pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
	/* 向下做8字节对齐 */
	pxTopOfStack = ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) & ( ~( ( uint32_t ) 0x0007 ) ) );	

	/* 将任务的名字存储在TCB中 */
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];

		if( pcName[ x ] == 0x00 )
		{
			break;
		}
	}
	/* 任务名字的长度不能超过configMAX_TASK_NAME_LEN */
	pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';

    /* 初始化TCB中的xStateListItem节点 */
    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
    /* 设置xStateListItem节点的拥有者 */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
    
    
    /* 初始化任务栈 */
	pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );   


	/* 让任务句柄指向任务控制块 */
    if( ( void * ) pxCreatedTask != NULL )
	{		
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
	}
}

初始化任务栈 StackType_t *pxPortInitialiseStack(…)

详细存储见下图:

初始化任务栈的作用是保存当前上下文的任务信息,以供我们后续任务切换所使用。

总结:

任务控制块是存储任务信息的载体,每个任务都有对应自己的任务控制块,我们在创建完任务之后,生成了任务对应的任务控制块,我们所定义的对应任务句柄在任务创建完成之后,就会指向我们的任务控制块,相当于我们可以通过任务句柄,间接去控制和管理我们的任务,而任务栈则是一些寄存器的载体,任务栈通过栈顶地址的加减,去存储一些寄存器信息,进而去设置任务的模式以及一些任务切换时所需要的信息,例如将任务栈中的PC寄存器存储了该任务的函数指针,在进行任务切换时,就可以调出PC寄存器存储的信息,进而找到相应的任务函数去执行,任务栈也为该任务预留了一些寄存器的空间,方便后面的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值