在前面的小节中,我们了解到当前正在执行的永远都是当前优先级最高且就绪的任务。在前面的小节中,每一个优先级下最多只有一个任务,那假如现在当前优先级数最高的优先级数下有多个任务,那此时这多个任务该如何执行。那此时这里就涉及到时间片的概念,时间片的概念简单说就是当某个优先级数下有多个就绪的任务时,这多个就绪的任务轮流执行单位时间,也就是轮流占用 C P U CPU CPU,这里的单位时间就是时间片,在 F r e e R T O S FreeRTOS FreeRTOS中时间片是固定的,就是一个 S y s t i c k Systick Systick周期,但是在 R T − T h r e a d RT-Thread RT−Thread和 μ C / O S μC/OS μC/OS 中时间片可以设置为多个 S y s t i c k Systick Systick周期。
在前面的小节中,每一个优先级下最多只有一个任务,这样就很难看出时间片的效果。为了看出时间片的效果,这里我们定义了四个任务: I d l e T a s k Idle\quad Task IdleTask和前一小节 的一样, T a s k 1 Task\quad 1 Task1和 T a s k 2 Task\quad 2 Task2和前一小节 任务基本一样,只不过不采用采用延时列表的延时接口,而是采用让 C P U CPU CPU干等待的延时接口。另外新建了一个任务 T a s k 3 Task\quad 3 Task3,这个任务和前一小节 的任务基本一样,采用采用延时列表的延时接口。这4个任务中, I d l e T a s k Idle\quad Task IdleTask的优先级最低, T a s k 1 Task\quad 1 Task1和 T a s k 2 Task\quad 2 Task2的优先级次之,优先级数都为2。 T a s k 3 Task\quad 3 Task3的优先级最高,优先级数为3。这样优先级数为2的任务就有了两个,这里4个任务对应的任务函数如下。
/*
*****************************************************************************
Idle Task
*****************************************************************************
*/
void IdleTask_Entry( void *p_arg )
{
while(1);
}
/*
*****************************************************************************
Task 1
*****************************************************************************
*/
void Task1_Entry( void *p_arg )
{
while(1)
{
flag1 = 1;
delay(100);
flag1 = 0;
delay(100);
}
}
/*
*****************************************************************************
Task 2
*****************************************************************************
*/
void Task2_Entry( void *p_arg )
{
while(1)
{
flag2 = 1;
delay(100);
flag2 = 0;
delay(100);
}
}
/*
*****************************************************************************
Task 3
*****************************************************************************
*/
void Task3_Entry( void *p_arg )
{
while(1)
{
flag3 = 1;
vTaskDelay(4);
flag3 = 0;
vTaskDelay(4);
}
}
其实到前一小节 基本上已经实现了时间片的功能,只是一直没有使用而已,现在再对代码做小的修改来跑一跑上面建立的4个任务,看看实际情况是什么样子的。主要的改变全部在接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick里面。主要是定义了局部变量 x S w i t c h R e q u i r e d xSwitchRequired xSwitchRequired以及其相关的部分,接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick不再固定返回 p d T R U E pdTRUE pdTRUE,而是返回 x S w i t c h R e q u i r e d xSwitchRequired xSwitchRequired。
/*
* THIS FUNCTION MUST NOT BE USED FROM APPLICATION CODE. IT IS ONLY
* INTENDED FOR USE WHEN IMPLEMENTING A PORT OF THE SCHEDULER AND IS
* AN INTERFACE WHICH IS FOR THE EXCLUSIVE USE OF THE SCHEDULER.
*
* Called from the real time kernel tick (either preemptive or cooperative),
* this increments the tick count and checks if any tasks that are blocked
* for a finite period required removing from a blocked list and placing on
* a ready list. If a non-zero value is returned then a context switch is
* required because either:
* + A task was removed from a blocked list because its timeout had expired,
* or
* + Time slicing is in use and there is a task of equal priority to the
* currently running task.
*/
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
/* Minor optimisation. The tick count cannot change in this
* block. */
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
/* Increment the RTOS tick, switching the delayed and overflowed
* delayed lists if it wraps to 0. */
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
{
taskSWITCH_DELAYED_LISTS();
}
/* See if this tick has made a timeout expire. Tasks are stored in
* the queue in the order of their wake time - meaning once one task
* has been found whose block time has not expired there is no need to
* look any further down the list. */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ; ; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* The delayed list is empty. Set xNextTaskUnblockTime
* to the maximum possible value so it is extremely
* unlikely that the
* if( xTickCount >= xNextTaskUnblockTime ) test will pass
* next time through. */
xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
break;
}
else
{
/* The delayed list is not empty, get the value of the
* item at the head of the delayed list. This is the time
* at which the task at the head of the delayed list must
* be removed from the Blocked state. */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
/* It is not time to unblock this item yet, but the
* item value is the time at which the task at the head
* of the blocked list must be removed from the Blocked
* state - so record the item value in
* xNextTaskUnblockTime. */
xNextTaskUnblockTime = xItemValue;
break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
}
/* It is time to remove the item from the Blocked state. */
uxListRemove( &( pxTCB->xStateListItem ) );
/* Place the unblocked task into the appropriate ready
* list. */
prvAddTaskToReadyList( pxTCB );
/* A task being unblocked cannot cause an immediate
* context switch if preemption is turned off. */
#if ( configUSE_PREEMPTION == 1 )
{
/* Preemption is on, but a context switch should
* only be performed if the unblocked task's
* priority is higher than the currently executing
* task.
* The case of equal priority tasks sharing
* processing time (which happens when both
* preemption and time slicing are on) is
* handled below.*/
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
/* Tasks of equal priority to the currently running task will share
* processing time (time slice) if preemption is on, and the application
* writer has not explicitly turned time slicing off. */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
return xSwitchRequired;
}
其实和时间片相关的主要是下面这一段,也就是如果发现在退出 S y s t i c k Systick Systick中断函数( S y s t i c k Systick Systick中断函数里面会调用接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick)的时候发现当前正在执行的任务所在的就绪列表不止一个任务,那么退出之后会进行一次任务的切换。
/* Tasks of equal priority to the currently running task will share
* processing time (time slice) if preemption is on, and the application
* writer has not explicitly turned time slicing off. */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
下面这一段的作用是如果某个任务刚刚结束延迟,也就是刚刚从延迟列表删除并加入到就绪列表,并发现它的优先级高于正在运行的任务的优先级,此时也需要进行一次任务的切换,切换到这个高优先级的任务。
/* A task being unblocked cannot cause an immediate
* context switch if preemption is turned off. */
#if ( configUSE_PREEMPTION == 1 )
{
/* Preemption is on, but a context switch should
* only be performed if the unblocked task's
* priority is higher than the currently executing
* task.
* The case of equal priority tasks sharing
* processing time (which happens when both
* preemption and time slicing are on) is
* handled below.*/
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
void xPortSysTickHandler( void )
{
/* The SysTick runs at the lowest interrupt priority, so when this interrupt
* executes all interrupts must be unmasked. There is therefore no need to
* save and then restore the interrupt mask value as its value is already
* known - therefore the slightly faster vPortRaiseBASEPRI() function is used
* in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
vPortRaiseBASEPRI();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* A context switch is required. Context switching is performed in
* the PendSV interrupt. Pend the PendSV interrupt. */
//portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
portYIELD();
}
}
vPortClearBASEPRIFromISR();
}
图1和图2就是运行的效果。从图1中可以看出在任务3延迟4个 S y s t i c k Systick Systick周期的时间区间内,任务1和任务2交替执行了4次。工程代码在这里。