FreeRTOS 任务调度及相关函数详解(二)

news2024/9/17 7:24:21

文章目录

  • 一、任务创建函数 xTaskCreate()
  • 二、任务初始化函数 prvInitialiseNewTask()
  • 三、任务堆栈初始化函数 pxPortInitialiseStack()
  • 四、添加任务到就绪列表 prvAddNewTaskToReadyList()
  • 五、任务删除 vTaskDelete()
  • 六、任务挂起 vTaskSuspend()
  • 七、任务恢复 vTaskResume()


一、任务创建函数 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 ) 
{
	TCB_t *pxNewTCB;
	BaseType_t xReturn;
	/********************************************************************/
	/***************使用条件编译的向上增长堆栈相关代码省略***************/
	/********************************************************************/
	StackType_t *pxStack;
	pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) *\ (1)
	sizeof( StackType_t ) ) ); 
	if( pxStack != NULL )
	{
		pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); (2)
		if( pxNewTCB != NULL )
		{
			pxNewTCB->pxStack = pxStack; (3)
		}
		else
		{
			vPortFree( pxStack ); (4)
		}
	}
	else
	{
		pxNewTCB = NULL;
	}
	if( pxNewTCB != NULL )
	{
		#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
		{
			pxNewTCB->ucStaticallyAllocated =\ (5)
			tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
		}
		#endif /* configSUPPORT_STATIC_ALLOCATION */
	prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, \ (6)
	pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
	prvAddNewTaskToReadyList( pxNewTCB ); (7)
	xReturn = pdPASS;
	}
	else
	{
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
	}
	return xReturn;
}

(1)、使用函数 pvPortMalloc()给任务的任务堆栈申请内存,申请内存的时候会做字节对齐处理。

(2)、如果堆栈的内存申请成功的话就接着给任务控制块申请内存,同样使用函数pvPortMalloc()。

(3)、任务控制块内存申请成功的话就初始化内存控制块中的任务堆栈字段 pxStack,使用(1)中申请到的任务堆栈。

(4)、如果任务控制块内存申请失败的话就释放前面已经申请成功的任务堆栈的内存。

(5)、标记任务堆栈和任务控制块是使用动态内存分配方法得到的。

(6)、使用函数 prvInitialiseNewTask()初始化任务,这个函数完成对任务控制块中各个字段的初始化工作!

(7)、使用函数 prvAddNewTaskToReadyList()将新创建的任务加入到就绪列表中。

二、任务初始化函数 prvInitialiseNewTask()

static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
								  const char * const pcName,
							      const uint32_t ulStackDepth,
								  void * const pvParameters,
								  UBaseType_t uxPriority,
								  TaskHandle_t * const pxCreatedTask,
								  TCB_t * pxNewTCB,
								  const MemoryRegion_t * const xRegions ) 
{
	StackType_t *pxTopOfStack;
	UBaseType_t x;
	#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY ==1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
	{
		( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE,\ (1)
		( size_t ) ulStackDepth * sizeof( StackType_t ) );
	}
	#endif 
	
	pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 ); (2)
	pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) &\
	( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); 
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxNewTCB->pcTaskName[ x ] = pcName[ x ]; (3)
		if( pcName[ x ] == 0x00 )
		{
			break;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0'; (4)
	if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ) (5)
	{
		uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
	pxNewTCB->uxPriority = uxPriority; (6)
	#if ( configUSE_MUTEXES == 1 ) (7)
	{
		pxNewTCB->uxBasePriority = uxPriority;
		pxNewTCB->uxMutexesHeld = 0;
	}
	#endif /* configUSE_MUTEXES */
	vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); (8)
	vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); (9)
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB ); (10)
	listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), \ (11)
	( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); 
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); (12)
	#if ( portCRITICAL_NESTING_IN_TCB == 1 ) //使能临界区嵌套
	{
		pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
	}
	#endif /* portCRITICAL_NESTING_IN_TCB */
	#if ( configUSE_APPLICATION_TASK_TAG == 1 ) //使能任务标签功能
	{
		pxNewTCB->pxTaskTag = NULL;
	}
	#endif /* configUSE_APPLICATION_TASK_TAG */
	#if ( configGENERATE_RUN_TIME_STATS == 1 ) //使能时间统计功能
	{
		pxNewTCB->ulRunTimeCounter = 0UL;
	}
	#endif /* configGENERATE_RUN_TIME_STATS */
	#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
	{
		for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS;x++ )
		{
			pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL; (12)
		}
	}
	#endif
	#if ( configUSE_TASK_NOTIFICATIONS == 1 ) //使能任务通知功能
	{
		pxNewTCB->ulNotifiedValue = 0;
		pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
	}
	#endif
	#if ( configUSE_NEWLIB_REENTRANT == 1 ) //使能 NEWLIB
	{
		_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
	}
	#endif
	#if( INCLUDE_xTaskAbortDelay == 1 ) //使能函数 xTaskAbortDelay()
	{
		pxNewTCB->ucDelayAborted = pdFALSE;
	}
	#endif
	pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode,\ (13) 
	 pvParameters );
	if( ( void * ) pxCreatedTask != NULL )
	{
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB; (14)
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

(1) 、 如 果 使 能 了 堆 栈 溢 出 检 测 功 能 或 者 追 踪 功 能 的 话 就 使 用 一 个 定 值tskSTACK_FILL_BYTE 来填充任务堆栈,这个值为 0xa5U。

(2)、计算堆栈栈顶 pxTopOfStack,后面初始化堆栈的时候需要用到。

(3)、保存任务的任务名。

(4)、任务名数组添加字符串结束符’\0’。

(5)、判断任务优先级是否合法,如果设置的任务优先级大于 configMAX_PRIORITIES,则将优先级修改为 configMAX_PRIORITIES-1。

(6)、初始化任务控制块的优先级字段 uxPriority。

(7)、使能了互斥信号量功能,需要初始化相应的字段。

(8)和(9)、初始化列表项 xStateListItem 和 xEventListItem,任务控制块结构体中有两个列表项,这里对这两个列表项做初始化。

(10)和(12)、设置列表项 xStateListItem 和 xEventListItem 属于当前任务的任务控制块,也就是设置这两个列表项的字段 pvOwner 为新创建的任务的任务控制块。

(11)、设置列表项xEventListItem的字段xItemValue为configMAX_PRIORITIES- uxPriority,比如当前任务优先级 3,最大优先级为 32,那么 xItemValue 就为 32-3=29,这就意味着 xItemValue值越大,优先级就越小。上一章学习列表和列表项的时候我们说过,列表的插入是按照xItemValue 的值升序排列的。

(12)、初始化线程本地存储指针,如果使能了这个功能的话。

(13)、调用函数 pxPortInitialiseStack()初始化任务堆栈。

(14)、生成任务句柄,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务控制块

三、任务堆栈初始化函数 pxPortInitialiseStack()

StackType_t *pxPortInitialiseStack( StackType_t * pxTopOfStack, TaskFunction_t pxCode, 
void * pvParameters )
{
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_XPSR; (1)
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; (2)
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError; (3)
	pxTopOfStack -= 5; (4)
	*pxTopOfStack = ( StackType_t ) pvParameters; (5)
	pxTopOfStack -= 8; (6)
	return pxTopOfStack;
}

堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对 Cortex-M 内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照:xPSR、R15(PC)、R14(LR)、R12、R3R0、R11R14。

(1)、寄存器 xPSR 值为 portINITIAL_XPSR,其值为 0x01000000。xPSR 是 Cortex-M 的一个内核寄存器,叫做程序状态寄存器,0x01000000 表示这个寄存器的 bit24 为 1,表示处于 Thumb状态,即使用的 Thumb 指令。

(2)、寄存器 PC 初始化为任务函数 pxCode。

(3)、寄存器 LR 初始化为函数 prvTaskExitError。

(4)、跳过 4 个寄存器,R12,R3,R2,R1,这四个寄存器不初始化。

(5)、寄存器 R0 初始化为 pvParameters,一般情况下,函数调用会将 R0~R3 作为输入参数,R0 也可用作返回结果,如果返回值为 64 位,则 R1 也会用于返回结果,这里的 pvParameters 是作为任务函数的参数,保存在寄存器 R0 中。

(6)、跳过 8 个寄存器,R11、R10、R8、R7、R6、R5、R4。
经过上面的初始化之后,此时的堆栈结果如下图 所示:
在这里插入图片描述
图中以 STM32 为例,堆栈为向下增长模式。

四、添加任务到就绪列表 prvAddNewTaskToReadyList()

任务创建完成以后就会被添加到就绪列表中,FreeRTOS 使用不同的列表表示任务的不同状态,在文件 tasks.c 中就定义了多个列表来完成不同的功能,这些列表如下:

PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
PRIVILEGED_DATA static List_t xPendingReadyList;

列表数组 pxReadyTasksLists[]就是任务就绪列表,数组大小为 configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。将一个新创建的任务添加到就绪列表中通过函数 prvAddNewTaskToReadyList()来完成,函数如下:

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
	taskENTER_CRITICAL();
	{
		uxCurrentNumberOfTasks++; (1)
		if( pxCurrentTCB == NULL )//正在运行任务块为 NULL,说明没有任务运行!
		{
			pxCurrentTCB = pxNewTCB;//将新任务的任务控制块赋值给 pxCurrentTCB
			//新创建的任务是第一个任务!!!
		if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
		{
			prvInitialiseTaskLists(); (2)
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
		if( xSchedulerRunning == pdFALSE )
		{
			 //新任务的任务优先级比正在运行的任务优先级高。
			if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
			{
				pxCurrentTCB = pxNewTCB; (3)
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	uxTaskNumber++; //uxTaskNumber 加一,用作任务控制块编号。
	#if ( configUSE_TRACE_FACILITY == 1 )
	{
		pxNewTCB->uxTCBNumber = uxTaskNumber;
	}
	#endif /* configUSE_TRACE_FACILITY */
	prvAddTaskToReadyList( pxNewTCB ); (4)
	}
	taskEXIT_CRITICAL();
	if( xSchedulerRunning != pdFALSE )
	{
		 //新任务优先级比正在运行的任务优先级高
		if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
		{
			taskYIELD_IF_USING_PREEMPTION(); (5)
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

(1)、变量 uxCurrentNumberOfTasks 为全局变量,用来统计任务数量。

(2)、变量 uxCurrentNumberOfTasks 为 1 说明正在创建的任务是第一个任务!那么就需要先初始化相应的列表,通过调用函数 prvInitialiseTaskLists()来初始化相应的列表。

(3)、新创建的任务优先级比正在运行的任务优先级高,所以需要修改 pxCurrentTCB 为新建任务的任务控制块。

(4)、调用函数 prvAddTaskToReadyList()将任务添加到就绪列表中,这个其实是个宏,如下:

#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), \
 &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )

其中宏 portRECORD_READY_PRIORITY()用来记录处于就绪态的任务,具体是通过操作全局变量 uxTopReadyPriority 来实现的。这个变量用来查找处于就绪态的优先级最高任务,具体操作过程后面讲解任务切换的时候会讲。接下来使用函数 vListInsertEnd()将任务添加到就绪列表末尾。

(5)、如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换。

五、任务删除 vTaskDelete()

前面我们已经学习了如何使用 FreeRTOS 的任务删除函数 vTaskDelete(),本节我们来详细的学习一下 vTaskDelete()这个函数的具体实现过程,函数源码如下:

void vTaskDelete( TaskHandle_t xTaskToDelete )
{
	TCB_t *pxTCB;
	taskENTER_CRITICAL();
	{
		//如果参数为 NULL 的话那么说明调用函数 vTaskDelete()的任务要删除自身。
		pxTCB = prvGetTCBFromHandle( xTaskToDelete ); (1)
		//将任务从就绪列表中删除。
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (2)
		{
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	//任务是否在等待某个事件?
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) (3)
		{
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		uxTaskNumber++;
		if( pxTCB == pxCurrentTCB ) (4)
		{
			vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->\ (5)
xStateListItem ) );
			++uxDeletedTasksWaitingCleanUp; (6)
			portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending ); (7)
		}
		else
		{
			--uxCurrentNumberOfTasks; (8)
			prvDeleteTCB( pxTCB ); (9)
			prvResetNextTaskUnblockTime(); (10)
		}
		traceTASK_DELETE( pxTCB );
	}
	taskEXIT_CRITICAL();
	//如果删除的是正在运行的任务那么就需要强制进行一次任务切换。
	if( xSchedulerRunning != pdFALSE )
	{
		if( pxTCB == pxCurrentTCB )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			portYIELD_WITHIN_API(); (11)
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}	

(1)、调用函数 prvGetTCBFromHandle()获取要删除任务的任务控制块,参数为任务句柄。如果参数为当前正在执行的任务句柄那么返回值就为 NULL。

(2)、将任务从任务就绪列表中删除。

(3)、查看任务是否正在等待某个事件(如信号量、队列等),因为如果任务等待某个事件的话这个任务会被放到相应的列表中,这里需要将其从相应的列表中删除掉。

(4)、要删除的是当前正在运行的任务

(5)、要删除任务,那么任务的任务控制块和任务堆栈所占用的内存肯定要被释放掉(如果使用动态方法创建的任务),但是当前任务正在运行,显然任务控制块和任务堆栈的内存不能被立即释放掉!必须等到当前任务运行完成才能释放相应的内存,所以需要打一个“标记”,标记出有任务需要处理。这里将当前任务添加到列表 xTasksWaitingTermination 中,如果有任务要删除自身的话都会被添加到列表 xTasksWaitingTermination 中。那么问题来了?内存释放在哪里完成呢?空闲任务!空闲任务会依次将需要释放的内存都释放掉。

(6)、uxDeletedTasksWaitingCleanUp 是一个全局变量,用来记录有多少个任务需要释放内存。

(7)、调用任务删除钩子函数,钩子函数的具体内容需要用户自行实现。

(8)、删除的是别的任务,变量 uxCurrentNumberOfTasks 减一,也就是当前任务数减一。

(9)、因为是删除别的任务,所以可以直接调用函数 prvDeleteTCB()删除任务控制块。

(10)、重新计算一下还要多长时间执行下一个任务,也就是下一个任务的解锁时间,防止有任务的解锁时间参考了刚刚被删除的那个任务。

(11)、如果删除的是正在运行的任务那么删除完以后肯定需要强制进行一次任务切换。

六、任务挂起 vTaskSuspend()

挂起任务使用函数 vTaskSuspend(),函数源码如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
	TCB_t *pxTCB;
	taskENTER_CRITICAL();
	{
		//如果参数为 NULL 的话说明挂起自身
		pxTCB = prvGetTCBFromHandle( xTaskToSuspend ); (1)
		traceTASK_SUSPEND( pxTCB );
		//将任务从就绪或者延时列表中删除,并且将任务放到挂起列表中
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (2)
		{
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		//任务是否还在等待其他事件
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) (3)
		{
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) ); (4)
	}
	taskEXIT_CRITICAL();
	if( xSchedulerRunning != pdFALSE )
	{
		taskENTER_CRITICAL();
		{
			prvResetNextTaskUnblockTime(); (5)
		}
		taskEXIT_CRITICAL();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
	if( pxTCB == pxCurrentTCB )
	{
		if( xSchedulerRunning != pdFALSE )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			portYIELD_WITHIN_API(); (6)
		}
		else
		{
			if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) ==\ (7)
 uxCurrentNumberOfTasks )
			{
				pxCurrentTCB = NULL; (8)
			}
			else
			{
				vTaskSwitchContext(); (9)
			}
		}
	}
	else
	{
	mtCOVERAGE_TEST_MARKER();
	}
}
		

(1)、通过函数 prvGetTCBFromHandle()获取要删除任务的任务控制块。

(2)、将任务从任务就绪列表延时列表中删除。

(3)、查看任务是否正在等待某个事件(如信号量、队列等),如果任务还在等待某个事件的话就将其从相应的事件列表中删除。

(4)、将任务添加到挂起任务列表尾,挂起任务列表为 xSuspendedTaskList,所有被挂起的任务都会被放到这个列表中。

(5)、重新计算一下还要多长时间执行下一个任务,也就是下一个任务的解锁时间。防止有任务的解锁时间参考了刚刚被挂起的那个任务。

(6)、如果刚刚挂起的任务是正在运行的任务,并且任务调度器运行正常,那么这里就需要调用函数 portYIELD_WITHIN_API()强制进行一次任务切换。

(7)、pxCurrentTCB 指向正在运行的任务,但是正在运行的任务要挂起了,所以必须给pxCurrentTCB 重新找一个“对象”。也就是查找下一个将要运行的任务,本来这个工作是由任务切换函数来完成的,但是程序运行到这一行说明任务调度器被挂起了,任务切换函数也无能为力了,必须手动查找下一个要运行的任务了。调用函数 listCURRENT_LIST_LENGTH()判断一下系统中所有的任务是不是都被挂起了,也就是查看列表 xSuspendedTaskList 的长度是不是
等于 uxCurrentNumberOfTasks。如果等于的话就说明系统中所有的任务都被挂起了(实际上不存在这种情况,因为最少都有一个空闲任务是可以运行的,空闲任务执行期间不会调用任何可以阻塞或者挂起空闲任务的 API 函数,为的就是保证系统中永远都有一个可运行的任务)。

(8)、如果所有任务都被挂起的话 pxCurrentTCB 就只能等于 NULL 了,这样当有新任务被创建的时候 pxCurrentTCB 就可以指向这个新任务。

(9)、有其他的没有被挂起的任务,调用 vTaskSwitchContext()获取下一个要运行的任务。

七、任务恢复 vTaskResume()

任务恢复函数有两个 vTaskResume()和 xTaskResumeFromISR(),一个是用在任务中的,一个是用在中断中的,但是基本的处理过程都是一样的,我们就以函数 vTaskResume()为例来讲解一下任务恢复详细过程。

void vTaskResume( TaskHandle_t xTaskToResume )
{
	TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume; (1)
	configASSERT( xTaskToResume );
	//函数参数不可能为 NULL。
	if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ) (2)
	{
		taskENTER_CRITICAL(); (3)
		{
			if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) (4)
			{
				traceTASK_RESUME( pxTCB );
				( void ) uxListRemove( &( pxTCB->xStateListItem ) ); (5)
				prvAddTaskToReadyList( pxTCB ); (6)
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) (7)
					{
						taskYIELD_IF_USING_PREEMPTION(); (8)
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			taskEXIT_CRITICAL(); (9)
		}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

(1)、根据参数获取要恢复的任务的任务控制块,因为不存在恢复正在运行的任务这种情况所以参数也不可能为 NULL(你强行给个为 NULL 的参数那也没办法),这里也就不需要使用函数 prvGetTCBFromHandle()来获取要恢复的任务控制块,prvGetTCBFromHandle()会处理参数为NULL 这种情况。

(2)、任务控制块不能为 NULL 和 pxCurrentTCB,因为不存在说恢复当前正在运行的任务。

(3)、调用函数 taskENTER_CRITICAL()进入临界段

(4)、调用函数 prvTaskIsTaskSuspended()判断要恢复的任务之前是否已经被挂起了,恢复的肯定是被挂起的任务,没有挂起就不用恢复。

(5)、首先将要恢复的任务从原来的列表中删除,任务被挂起以后都会放到任务挂起列表xSuspendedTaskList 中。

(6)、将要恢复的任务添加到就绪任务列表中。

(7)、要恢复的任务优先级高于当前正在运行的任务优先级。

(8) 、因为要恢复的任务其优先级最高,所以需要调用函数
taskYIELD_IF_USING_PREEMPTION()来完成一次任务切换。

(9)、调用函数 taskEXIT_CRITICAL()退出临界区。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417417.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

shadow机械手臂系统

机械手臂系统 Shadow机械手臂系统是由美国Shadow Robot Company开发的一款高精度机械手臂系统&#xff0c;主要用于工业自动化、医疗器械、科学研究等领域。Shadow机械手臂系统采用了多自由度的设计&#xff0c;可以实现高精度的三维运动和灵活的操作&#xff0c;其控制系统还支…

ds18b20-温度传感器-linux驱动-混杂设备

文章目录ds18b20读取温度数据步骤ds18b20时序图&#xff1a;初始化时序DS18B20初始化时序的步骤&#xff1a;读/写时序DS18B20写步骤&#xff1a;DS18B20读步骤&#xff1a;DS18B20驱动实现结果如下&#xff1a;参考&#xff1a;ds18b20读取温度数据步骤 初始化&#xff1a;将…

对话ChatGPT:Prompt是普通人“魔法”吗?

在ChatGPT、Midjourney、Stable Diffusion等新事物的作用下&#xff0c;不少人或多或少听说过Prompt的概念。 虽然OpenAI掀起的大模型浪潮再度刷新了人们对AI的认知&#xff0c;但现阶段的AI终归还不是强人工智能&#xff0c;大模型里的“知识”存储在一个隐性空间里&#xff0…

工地高空作业安全带穿戴识别 python

工地高空作业安全带穿戴识别系统通过pythonopencv网络模型分析技术&#xff0c;工地高空作业安全带穿戴识别算法模型对现场监控画面中人员安全绳安全带穿戴进行检测&#xff0c;不需人为干预立即触发告警存档。OpenCV的全称是Open Source Computer Vision Library&#xff0c;是…

【Ruby 2D】【unity learn】抬头显示血条

说起游戏开发&#xff0c;大家一般会觉得控制角色移动和制作血条哪个难呢&#xff1f; 或许都会觉得血条比较难吧。 是的&#xff0c;正是如此。 那么我们让我们来看看血条该怎么做吧 这是效果图 受伤后是这样的 首先是创建一张Canvas画布 这个画布会很大 相比之下我们的小…

【redis】BigKey

【redis】BigKey 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章…

ChatGPT云桌面:无需科技挂载,即点即用

ChatGPT是一个由OpenAI开发的人工智能对话语言模型。它被设计为对话式人工智能代理&#xff0c;用于客户服务、个人助理和文娱等任务。它可以理解并生成多种语言的文本&#xff0c;包括中文、英语、西班牙语、德语等。但从某些地方访问ChatGPT可能很困难&#xff0c;特别是在注…

实验4 Matplotlib数据可视化

1. 实验目的 ①掌握Matplotlib绘图基础&#xff1b; ②运用Matplotlib&#xff0c;实现数据集的可视化&#xff1b; ③运用Pandas访问csv数据集。 2. 实验内容 ①绘制散点图、直方图和折线图&#xff0c;对数据进行可视化&#xff1b; ②下载波士顿数房价据集&#xff0c;并…

机器学习 -- 过拟合与欠拟合以及应对过拟合的方法 神经网络中的超参数如何选择

前言 在学习机器学习的过程中&#xff0c;训练模型时常遇到的问题就是模型的过拟合和欠拟合&#xff0c;下文我将解释过拟合和欠拟合的概念&#xff0c;并且学习应对过拟合以及神经网络中的超参数如何选择的方法。 过拟合和与欠拟合 过拟合&#xff1a;是指学习时选择的模型…

基于 Git 的开发工作流——主干开发特性总结

在参与开发的过程&#xff0c;得益与平台提供便捷的开发流程&#xff0c;简化很多开发过程操作分支的步骤&#xff1b;也就很好奇&#xff0c;为什么研发平台怎么设计&#xff0c;考虑的点是为什么&#xff0c;便有了这次对主干研发的学习与记录。当我们是构建软件项目的唯一开…

【计算机网络-传输层】TCP 协议

文章目录1 传输层概述1.1 传输层的功能1.2 端口号2 TCP 报文段2.1 TCP 报文段首部格式2.2 TCP 数据传送的过程3 TCP 连接管理3.1 TCP 连接的建立——三次握手3.1.1 客户机向服务器发送 TCP 连接请求报文段3.1.2 服务器向客户机发送 TCP 连接请求确认报文段3.1.3 客户机向服务器…

python数据可视化玩转Matplotlib subplot子图操作,四个子图(一包四),三个子图,子图拉伸

目录 一、创建子图 1.1 下图是绘制的子图&#xff1a; 1.2 代码释义&#xff1a; 二、绘制子图 2.1 代码引入 2.2 图形绘制 三、子图布局 3.1 子图布局说明 四、子图大小 4.1 子图大小调整 五、子图间距 5.1 子图代码调整 六、子图位置 6.1 代码引入 6.2 完整代码…

如何在 Windows10 下运行 Tensorflow 的目标检测?

前言 看过很多博主通过 Object Detection 实现了一些皮卡丘捕捉&#xff0c;二维码检测等诸多特定项的目标检测。而我跟着他们的案例来运行的时候&#xff0c;不是 Tensorflow 版本冲突&#xff0c;就是缺少什么包&#xff0c;还有是运行官方 object_detection_tutorial 不展示…

算法记录 | Day30 回溯算法

332.重新安排行程 思路&#xff1a; 1.确定回溯函数参数&#xff1a;定义全局遍历存放path&#xff0c; 2.终止条件&#xff1a;遍历完所有路径&#xff0c;机场个数&#xff0c;如果达到了&#xff08;航班数量1&#xff09;&#xff0c;即path的长度应当为字符串二维数组长…

教程 | 多通道fNIRS数据的预处理和平均(下)

前言 前文近红外数据的预处理和平均&#xff08;上&#xff09;提到fNIRS是一种评估氧和脱氧血红蛋白浓度变化的方法&#xff0c;可与fMRI相媲美。fNIRS的不足是它的空间分辨率比fMRI差&#xff0c;但其优点是有更高的时间分辨率&#xff0c;并允许测量无法通过fMRI扫描仪测试…

GPT-4 API 接口调用及价格分析

GPT-4 API 接口调用及价格分析 15日凌晨&#xff0c;OpenAI发布了万众期待的GPT-4&#xff01;新模型支持多模态&#xff0c;具备强大的识图能力&#xff0c;并且推理能力和回答准确性显著提高。在各种专业和学术基准测试上的表现都媲美甚至超过人类。难怪OpenAI CEO Sam Altm…

动态规划专题(明天继续)

动态规划求最大值&#xff1a; 题目描述 小蓝在一个 nn 行 mm 列的方格图中玩一个游戏。 开始时&#xff0c;小蓝站在方格图的左上角&#xff0c;即第 11 行第 11 列。 小蓝可以在方格图上走动&#xff0c;走动时&#xff0c;如果当前在第 rr 行第 cc 列&#xff0c;他不能…

ASIC-WORLD Verilog(3)第一个Verilog代码

写在前面 在自己准备写一些简单的verilog教程之前&#xff0c;参考了许多资料----asic-world网站的Verilog教程即是其一。这套教程写得极好&#xff0c;奈何没有中文&#xff0c;在下只好斗胆翻译过来&#xff08;加了自己的理解&#xff09;分享给大家。 这是网站原文&#xf…

Windows应急响应 -Windows日志排查,系统日志,Web应用日志,

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 Windows日志分析一、查看日志二、日志分类三、筛选日志四、事件ID1、安全日志1.1、登录类…

基于Java+SSM+Vue的旅游资源网站设计与实现【源码(完整源码请私聊)+论文+演示视频+包运行成功】

博主介绍&#xff1a;专注于Java技术领域和毕业项目实战 &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 Java项目精品实战案例&#xff08;200套&#xff09; 目录 一、效果演示 二、…