跟着野火学FreeRTOS:第一段(时间片)

     在前面的小节中,我们了解到当前正在执行的永远都是当前优先级最高且就绪的任务。在前面的小节中,每一个优先级下最多只有一个任务,那假如现在当前优先级数最高的优先级数下有多个任务,那此时这多个任务该如何执行。那此时这里就涉及到时间片的概念,时间片的概念简单说就是当某个优先级数下有多个就绪的任务时,这多个就绪的任务轮流执行单位时间,也就是轮流占用 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 RTThread μ 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次。工程代码在这里。

  跟着野火学FreeRTOS:第一段(时间片)_第1张图片
图1.
  跟着野火学FreeRTOS:第一段(时间片)_第2张图片
图2.

你可能感兴趣的:(stm32,单片机)