FreeRTOS系列第6篇---内核控制函数

前言

FreeRTOS中有一些函数只供系统内核使用,用户应用程序 一般不允许使用,这些函数就是内核控制函数。

预览

函数描述
taskYIELD()任务切换。
taskENTER_CRITICAL()进入临界区,用于任务中。
taskEXIT_CRITICAL()退出临界区,用于任务中。
taskENTER_CRITICAL_FROM_ISR()进入临界区,用于中断服务函数中。
taskEXIT_CRITICAL_FROM_ISR()退出临界区,用于中断服务函数中
taskDISABLE_INTERRUPTS()关闭中断。
taskENABLE_INTERRUPTS()打开中断。
vTaskStartScheduler()开启任务调度器。
vTaskEndScheduler()关闭任务调度器。
vTaskSuspendAll()挂起任务调度器。
xTaskResumeAll()恢复任务调度器。
vTaskStepTick()设置系统节拍值。

详解

1、taskYIELD()

在task.h文件中

/**
 * task. h
 *
 * Macro for forcing a context switch.
 *
 * \defgroup taskYIELD taskYIELD
 * \ingroup SchedulerControl
 */
#define taskYIELD()					portYIELD()

函数 portYIELD()也是个宏,在文件 portmacro.h 中有如下定义:

/* Scheduler utilities. */
#define portYIELD()																\
{																				\
	/* Set a PendSV to request a context switch. */								\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;		(1)						\
																				\
	/* Barriers are normally not required but do ensure the code is completely	\
	within the specified behaviour for the architecture. */						\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}

(1)、通过向中断控制和状态寄存器 ICSR 的 bit28 写入 1 挂起 PendSV 来启动 PendSV 中断。这样就可以在 PendSV 中断服务函数中进行任务切换了。中断服务函数如下:

// 汇编代码,完全看懂有点费力。
__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* Get the location of the current TCB. */
	ldr	r2, [r3]

	stmdb r0!, {r4-r11}			/* Save the remaining registers. */
	str r0, [r2]				/* Save the new top of stack into the first member of the TCB. */

	stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r3, r14}

	ldr r1, [r3]
	ldr r0, [r1]				/* The first item in pxCurrentTCB is the task top of stack. */
	ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
	msr psp, r0
	isb
	bx r14
	nop
}

中断级的任务切换函数 portYIELD_FROM_ISR()如下:

#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD()
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )

可以看出 portYIELD_FROM_ISR()最终也是通过调用函数 portYIELD()来完成任务切换的。

2 临界段代码

临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。FreeRTOS 与临界段代码保护有关的函数有 4 个。分别是两个任务级的临界段代码保护和中断级的临界段代码保护。

2.1 taskENTER_CRITICAL() 、taskEXIT_CRITICAL()

taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个函数是成对使用的,函数的定义如下:

#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()

而 portENTER_CRITICAL()和 portEXIT_CRITICAL()也是宏定义,在文件 portmacro.h 中有
定义,如下:

void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;
	if( uxCriticalNesting == 1 )
	{
	configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	} 
}
void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
	if( uxCriticalNesting == 0 )
	{
		portENABLE_INTERRUPTS();
	} 
}

在进入函数 vPortEnterCritical()以后会首先关闭中断,然后给变量 uxCriticalNesting加一,uxCriticalNesting 是个全局变量,用来记录临界段嵌套次数的。函数 vPortExitCritical()是 退出临界段调用的,函数每次将 uxCriticalNesting 减一,只有当 uxCriticalNesting 为 0 的时候才会调用函数 portENABLE_INTERRUPTS()使能中断。这样保证了在有多个临界段代码的时候不会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后才会使能中断!
因此临界区的代码一定要精简。因为进入临界区会关闭中断,这样会导致优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及时的响应!

2.2 taskENTER_CRITICAL_FROM_ISR() 、taskEXIT_CRITICAL_FROM_ISR()

taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中断级别临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY!
这两个函数在文件 task.h中有如下定义:

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

portSET_INTERRUPT_MASK_FROM_ISR() 和portCLEAR_INTERRUPT_MASK_FROM_ISR(),这两个在文件portmacro.h 中有如下定义:

#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

vPortSetBASEPRI(x)实现如下:

/*函数 vPortSetBASEPRI()是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传递进来,值为0的话,由 BASEPRI寄存器可知,结果就是开中断。
*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		msr basepri, ulBASEPRI
	} 
}

使用方法如下:

void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
	{
		status_value=taskENTER_CRITICAL_FROM_ISR(); (1) // 退出临界区保护的时候需要使用status_value
		total_num+=1;
		printf("float_num 的值为: %d\r\n",total_num);
		taskEXIT_CRITICAL_FROM_ISR(status_value); (2)
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}

函数 ulPortRaiseBASEPRI()在文件 portmacro.h 中定义的,如下:

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
	__asm
	{
		mrs ulReturn, basepri (1)
		msr basepri, ulNewBASEPRI (2)
		dsb
		isb
	}
	return ulReturn; (3) }

(1)、先读出 BASEPRI 的值,保存在 ulReturn 中。
(2)、将 configMAX_SYSCALL_INTERRUPT_PRIORITY 写入到寄存器 BASEPRI 中,这样优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断就会被屏蔽。
(3)、返回 ulReturn,退出临界区代码保护的时候要使用到此值!

3、taskDISABLE_INTERRUPTS()、 taskENABLE_INTERRUPTS()

FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS(),这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)

可以看出开关中断实际上是通过函数 vPortSetBASEPRI(0)和 vPortRaiseBASEPRI()来实现
的,这两个函数如下:

/*-----------------------------------------------------------*/

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		/* Barrier instructions are not used as this function is only used to
		lower the BASEPRI value. */
		msr basepri, ulBASEPRI
	}
}
/*-----------------------------------------------------------*/

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}

函数 vPortSetBASEPRI()是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传递进来,portENABLE_INTERRUPTS()是开中断,它传递了个 0 给 vPortSetBASEPRI()。
函 数 vPortRaiseBASEPRI() 是 向 寄 存 器 BASEPRI 写入宏configMAX_SYSCALL_INTERRUPT_PRIORITY , 那 么 优 先 级 低 于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断就会被屏蔽!

4、vTaskStartScheduler()

在编写程序时,我们经常创建开始任务,然后调用函数vTaskStartScheduler()。这个函数功能就是开启任务调度器。函数在task.c中有定义。如下所示:

void vTaskStartScheduler( void )
{
BaseType_t xReturn;

	/* Add the idle task at the lowest priority. */
	#if( configSUPPORT_STATIC_ALLOCATION == 1 ) // 判断如果是静态方法创建的任务
	{
		StaticTask_t *pxIdleTaskTCBBuffer = NULL;
		StackType_t *pxIdleTaskStackBuffer = NULL;
		uint32_t ulIdleTaskStackSize;

		/* The Idle task is created using user provided RAM - obtain the
		address of the RAM then create the idle task. */
		vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
		xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
												"IDLE",
												ulIdleTaskStackSize,
												( void * ) NULL,
												( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
												pxIdleTaskStackBuffer,
												pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

		if( xIdleTaskHandle != NULL )
		{
			xReturn = pdPASS;
		}
		else
		{
			xReturn = pdFAIL;
		}
	}
	#else // 否则是动态方法创建的任务
	{
		/* The Idle task is being created using dynamically allocated RAM. */
		// 系统自动申请堆栈去创建空闲任务
		xReturn = xTaskCreate(	prvIdleTask,
								"IDLE", configMINIMAL_STACK_SIZE,
								( void * ) NULL,
								( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
								&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 )
		{
			xReturn = xTimerCreateTimerTask(); // 创建定时器任务
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */

	if( xReturn == pdPASS ) // 空闲任务和定时器任务创建成功
	{
		/* Interrupts are turned off here, to ensure a tick does not occur
		before or during the call to xPortStartScheduler().  The stacks of
		the created tasks contain a status word with interrupts switched on
		so interrupts will automatically get re-enabled when the first task
		starts to run. */
		/*中断在此处关闭,以确保在调用 xPortStartScheduler() 之前或期间不会发生滴答中断。 创建的任务的堆栈包含一个关于中断打开状态字,因此当第一个任务开始运行时,中断将自动重新启用*/
		portDISABLE_INTERRUPTS();// 关闭中断

		#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. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */

		xNextTaskUnblockTime = portMAX_DELAY;
		xSchedulerRunning = pdTRUE; // 表示调度器开始运行
		xTickCount = ( TickType_t ) 0U;

		/* If configGENERATE_RUN_TIME_STATS is defined then the following
		macro must be defined to configure the timer/counter used to generate
		the run time counter time base. */
		/*当宏 configGENERATE_RUN_TIME_STATS 为 1 的时候说明使能时间统计功能,此时
		需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器。
		*/
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		/* Setting up the timer tick is hardware specific and thus in the
		portable interface. */
 	 	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 );
	}

	/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
	meaning xIdleTaskHandle is not used anywhere else. */
	( void ) xIdleTaskHandle;
}
/*-----------------------------------------------------------*/

5、vTaskEndScheduler

关闭任务调度器,此函数仅用于X86架构的处理器。调用次函数后系统时钟会停止运行。所有创建的任务都会自动的删除掉。然后可以通过调用函数vTaskStartScheduler()来重新开启任务调度器。函数定义如下:

void vTaskEndScheduler( void )
{
	portDISABLE_INTERRUPTS(); //关闭中断
	xSchedulerRunning = pdFALSE; //标记任务调度器停止运行
	vPortEndScheduler(); //调用硬件层关闭中断的处理函数
}

6、vTaskSuspendAll()

挂起任务调度器,调用此函数不需要关闭可屏蔽中断即可挂起任务调度器,此函数在文件tasks.c 中有如下定义:

void vTaskSuspendAll( void )
{
	/* A critical section is not required as the variable is of type
	BaseType_t.  Please read Richard Barry's reply in the following link to a
	post in the FreeRTOS support forum before reporting this as a bug! -
	http://goo.gl/wu4acr */
	++uxSchedulerSuspended;
}

可看出,此函数只是简单的将变量 uxSchedulerSuspended 加一,uxSchedulerSuspended 是挂起嵌套计数器,调度器挂起是支持嵌套的。使用函数 xTaskResumeAll()可以恢复任务调度器,调用了几次 vTaskSuspendAll()挂起调度器,同样的也得调用几次 xTaskResumeAll()才会最终恢复任务调度器。
假设现在有这样一种情况,任务 1 的优先级为 10,此时任务 1 由于等待队列TestQueue 而处于阻塞态。但是有段其他的代码调用函数vTaskSuspendAll()挂起了任务调度器,在还没有调用 xTaskResumeAll()恢复任务调度器之前,有个在外部中断发生了,在中断服务程序里面调用函数 xQueueSendFromISR()向任务 1 发送了队列 TestQueue。如果任务调度器没有阻塞的话函数 xQueueSendFromISR()会使任务 1 进入就绪态,也就是将任务 1 添加到优先级 10 对应的就绪列表 pxReadyTasksLists[10]中,这样当任务切换的时候任务 1 就会运行。但是现在任务调度器由于函数vTaskSuspendAll()而挂起,这个时候任务 1 就不是添加到任务就绪列表 pxReadyTasksLists[10]中了,而是添加到另一个叫做xPendingReadyList 的列表中,xPendingReadyList 是个全局变量,在文件 tasks.c 中有定义。当调用函数xTaskResumeAll()恢复调度器的时候就会将挂到列表 xPendingReadyList 中的任务重新移动到它们所对应的就绪列表 pxReadyTasksLists 中。

7、xTaskResumeAll()

此函数用于将任务调度器从挂起壮态恢复
函数描述如下

BaseType_t xTaskResumeAll( void )
{
	TCB_t *pxTCB = NULL;
	BaseType_t xAlreadyYielded = pdFALSE;

	/* If uxSchedulerSuspended is zero then this function does not match a
	previous call to vTaskSuspendAll(). */
	configASSERT( uxSchedulerSuspended );

	/* It is possible that an ISR caused a task to be removed from an event
	list while the scheduler was suspended.  If this was the case then the
	removed task will have been added to the xPendingReadyList.  Once the
	scheduler has been resumed it is safe to move all the pending ready
	tasks from this list into their appropriate ready list. */ 
	taskENTER_CRITICAL(); // 临界区代码保护
	{
		--uxSchedulerSuspended;// 调度器挂起嵌套计数器减一。

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) // 所有挂起已经解除,调度器可以开始运行。
		{
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{
				/* Move any readied tasks from the pending list into the
				appropriate ready list. */
				// 循环处理列表xPendingReadyList,如果不为空
				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
				{
				// 获取挂到xPendingReadyList列表上的任务对应的任务控制块
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
					// 将任务从事件列表上移除
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					// 将任务从状态列表上移除。
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					// 将任务添加到就绪列表上。
					prvAddTaskToReadyList( pxTCB );

					/* If the moved task has a priority higher than the current
					task then a yield must be performed. */
					// 任务的优先级高于正在运行的任务,将xYieldPending置为pdTRUE。表示需要进行任务切换。
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( pxTCB != NULL )
				{
					/* A task was unblocked while the scheduler was suspended,
					which may have prevented the next unblock time from being
					re-calculated, in which case re-calculate it now.  Mainly
					important for low power tickless implementations, where
					this can prevent an unnecessary exit from low power
					state. */
					prvResetNextTaskUnblockTime();
				}

				/* If any ticks occurred while the scheduler was suspended then
				they should be processed now.  This ensures the tick count does
				not	slip, and that any delayed tasks are resumed at the correct
				time. */
				{
					UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */

					if( uxPendedCounts > ( UBaseType_t ) 0U )
					{
						do
						{
							if( xTaskIncrementTick() != pdFALSE )
							{
								xYieldPending = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
							--uxPendedCounts;
						} while( uxPendedCounts > ( UBaseType_t ) 0U );

						uxPendedTicks = 0;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( xYieldPending != pdFALSE )
				{
					#if( configUSE_PREEMPTION != 0 )
					{
						xAlreadyYielded = pdTRUE; // 标记在函数 xTaskResumeAll()中进行了任务切换
					}
					#endif
					taskYIELD_IF_USING_PREEMPTION(); // 任务切换
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();

	return xAlreadyYielded;
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值