freeRTOS的任务抢占和时间片轮转

发布于:2023-01-04 ⋅ 阅读:(440) ⋅ 点赞:(0)

实时操作系统的一个特点就是可以任务抢占,高优先级的任务可以抢占比自己优先级低的任务,如果新任务优先级和当前人任务优先级一样,且在使能了时间片的方式的话,二者以时间片的方式共享cpu,时间片的粒度为一个sys tick心跳间隔。freeRTOS也支持这个性能,如果使能抢占,需要打开如下宏:

#define configUSE_PREEMPTION 1

时间片方式共享cpu

#define configUSE_TIME_SLICING 1

有2个问题:

  • 系统如何知道有比当前任务优先级高任务在等待执行
  • 系统在何时检测切换到高优先级的任务上。

要解答上面2个问题,首先freeRTOS的系统心跳system tick定时中断,可以触发任务切换,先看system tick的ISR函数核心代码,这里也是freeRTOS的核心:

if( xConstTickCount >= xNextTaskUnblockTime )
    {
        for( ; ; )
        {
            /*延时等待的任务都在pxDelayedTaskList中,如果该list为空,说明没有任务要待转入readyList中,直接将xNextTaskUnblockTime设置为最大,
            下一个tick到来时,也就不会在判断了*/
            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;
            } /*pxDelayedTaskList不为空,有任务在等待调度,这样任务有2种情况,一个是超时了,一个是还未超时*/
            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. */
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                /*任务的定时器已经到时,则需要将该任务放到readyList中*/
                /* It is time to remove the item from the Blocked state. */
                listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
                /* Is the task waiting on an event also?  If so remove
                 * it from the event list. */
                if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                {
                    listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                /* 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 has a
                     * priority that is equal to or higher than the
                     * currently executing task. */
                     /*定义了抢占的话,如果到时的任务优先级大于等于当前任务的优先级,则置位xSwitchRequired,触发一次任务切换*/
                     if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                     {
                        xSwitchRequired = pdTRUE;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                #endif /* configUSE_PREEMPTION */
            }
        }
    }
    /*走到这里,以下几种可能的情况
    1> pxDelayedTaskList为空
    2> pxDelayedTaskList不为空,但是任务的定时时间还没有到
    3> pxDelayedTaskList不为空,任务的定时时间已经到了,同时,把到时的任务也放入了readyList中
    如果定义了抢占和cpu时间片,如果readyList中的任务数大于1,则置位xSwitchRequired,触发一次任务切换。
     */
    /* 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. */
     /*如果定义了configUSE_TIME_SLICING,也只是置位xSwitchRequired为true,那么相同优先级的任务如何切换的呢?*/
    #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
    {
        if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
        {
            xSwitchRequired = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
    /*开启了抢占的话,如果有task yield pending了也会触发一次任务切换*/
    #if ( configUSE_PREEMPTION == 1 )
    {
        if( xYieldPending != pdFALSE )
        {
            xSwitchRequired = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_PREEMPTION */

system tick定时任务干了的事情有:

  • 判断是否有任务在等待执行
  • 任务超时处理
  • 任务的抢占
  • 时间片调度

不管任务超时与否,system tick到来后,都会判断是否需要抢占和时间片轮换。

关于时间片,相同优先级的任务如何切换的呢?system tick的ISR只是把任务放入readyList中,任务切换执行是在另外 一个函数中vTaskSwitchContext。

freeRTOS中system tick到来之后都会调用一次vTaskSwitchContext。

所以查看vTaskSwitchContext的代码,找出答案。

void vTaskSwitchContext( void )
{
    if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
    {
        ....
    }
    else
    {
        xYieldPending = pdFALSE;
        traceTASK_SWITCHED_OUT();

        #if ( configGENERATE_RUN_TIME_STATS == 1 )
            ......
        #endif /* configGENERATE_RUN_TIME_STATS */

        /* Check for stack overflow, if configured. */
        taskCHECK_FOR_STACK_OVERFLOW();


        /* Select a new task to run using either the generic C or port
         * optimised asm code. */
        taskSELECT_HIGHEST_PRIORITY_TASK(); /*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. */
        traceTASK_SWITCHED_IN();
        ........
        ........
    }
}

核心代码为taskSELECT_HIGHEST_PRIORITY_TASK();它的实现在:

    #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                  \
    {                                                                                           \
        UBaseType_t uxTopPriority;                                                              \
                                                                                                \
        /* Find the highest priority list that contains ready tasks. */                         \
        portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                          \
        configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );   \
    } /* taskSELECT_HIGHEST_PRIORITY_TASK() */

最后在这 listGET_OWNER_OF_NEXT_ENTRY()

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           \
    {                                                                                          \
        List_t * const pxConstList = ( pxList );                                               \
        /* Increment the index to the next item and return the item, ensuring */               \
        /* we don't return the marker used at the end of the list.  */                         \
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                           \
        if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
        {                                                                                      \
            ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \
        }                                                                                      \
        ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                         \
    }

轮训当前优先级的链表,通过节点的pvOwer拿到TCB指针(具体任务的任务块入口)。

宏接口一进来就pxIndex指向了下一个节点,比如当前链表中有2个任务,那system tick到来之后,直接跳转到了下个节点,提取下个任务

( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;

如果同一优先级的任务只有2个而且没有其他任务抢占,在这样特定情况下,每到一个system tick,就会切换到另外一个任务,2个任务来回切换共享cpu,切换时间以system tick为基准。

结合项目中的pxReadyTasksLists数据结构如下:

pxReadyTasksLists是以优先级为索引的数组链表,同一优先级的任务挂在对应的链表上。

所有链表均是唤醒链表。

到这里就是整个任务切换的大致过程。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到