Toriyung

导航

freertos-刘火良:延时列表(链表)

前面几章的学习中,任务从创建后一直位于就绪列表中,延时、优先级等操作全部在就绪列表进行,这是不太方便的。根据任务的几个状态知,还需要一个延时列表,当任务进入延时状态时,则移出就绪列表进入延时列表。当有了延时列表后,前面的几个操作需要相应进行修改

1. 定义延时列表

  就绪列表为一个带有多条链表的数组,而延时列表仅为一个链表结构,如图示意

  需要注意的是,由于可能存在唤醒时间超过数值类型的最大值,如本实验定义的TickType_t类型最大为0xffff_ffff,假如唤醒时间超过了这个值,则需要进入另一个列表(相当于用多个列表弥补不足),所以定义两个延时列表,代码如下

static List_t xDelayedTaskList1;                                            //时基计数器未溢出时用的列表
static List_t xDelayedTaskList2;                                            //时基计数器溢出时用的列表
static List_t *volatile pxDelayedTaskList = NULL;                        //指向xDelayedTaskList1
static List_t *volatile pxOverflowDelayedTaskList = NULL;        //指向xDelayedTaskList2

 

2. 初始化延时列表

  由上面定义可知,初始化要做的事就是进行指针的指向,同时链表必然需要进行链表的初始化。所以在最初的只初始化了就绪列表的函数中,添加初始化延时列表

void prvInitialiseTaskLists(void)
{
    /* brief:初始化链表组 */
    UBaseType_t uxPriority;
    
    for(uxPriority = (UBaseType_t) 0U; uxPriority < (UBaseType_t) configMAX_PRIORITIES; uxPriority++)        //遍历每个元素(链表),进行链表初始化
    {
        vListInitialise( &(pxReadyTasksLists[uxPriority]));
    }
    
    // 初始化延时列表并定好指针指向
    vListInitialise( &xDelayedTaskList1);
    vListInitialise( &xDelayedTaskList2);
    pxDelayedTaskList = &xDelayedTaskList1;
    pxOverflowDelayedTaskList = &xDelayedTaskList2;
}

 

3. 下一个唤醒时刻xNextTaskUnblockTime

  与前面的延时和扫描方式不同,本次使用的就是通过对比当前时刻和唤醒时刻,当两者一致时,就唤醒任务,所以用到变量xNextTaskUnblockTime

  其初始化在调度任务函数vTaskStartScheduler中进行,将其初始化为最大延时时间。同时调度任务函数中初始化当前时刻xTickCount为0

void vTaskStartScheduler(void)
{
    /* 启动任务调度(含有空闲任务创建) */
    TaskHandle_t xIdleTaskHandle;                                            //空闲任务句柄
    TCB_t *pxIdleTaskTCBBuffer = NULL;                                //空闲任务TCB的指针
    StackType_t *pxIdleTaskStackBuffer = NULL;                //空闲任务栈指针
    uint32_t ulIdleTaskStackSize;                                            //空闲任务栈大小
    
    extern TickType_t xTickCount;
    
    vApplicationGetIdleTaskMemory(&pxIdleTaskTCBBuffer,&pxIdleTaskStackBuffer,&ulIdleTaskStackSize);        //获取空闲任务信息,注意用二重指针
    
    //创建空闲任务
    xIdleTaskHandle = xTaskCreateStatic((TaskFunction_t)prvIdleTask,(char *)"IDLE", (uint32_t)ulIdleTaskStackSize,(void *)NULL, (UBaseType_t)tskIDLE_PRIORITY, (StackType_t *)pxIdleTaskStackBuffer,(TCB_t *)pxIdleTaskTCBBuffer);
    
    xNextTaskUnblockTime = portMAX_DELAY;                            //初始化为最大延时时间
    
    xTickCount = (TickType_t) 0U;
    
    if(xPortStartScheduler() != pdFALSE)
    {
        //调度器启动失败则进入这里
    }
}

 

4. TaskDelay

  以往的方法是通过对任务TCB附加一个TickToDelay的延时,并对所有任务的其进行扫描递减,这样子大大增加了CPU的使用负担;而本次是对任务TCB的辅助排序值进行赋值,然后直接放入延时列表,定一个唤醒时刻,当时间到时再进行列表任务的判断取出,这样子,只有部分满足条件的任务需要进行比对而无需比对所有任务。

  TaskDelay函数将延时任务交给prvAddCurrentTaskToDelayedList函数,最后触发PendSV中断

void vTaskDelay(const TickType_t xTicksToDelay)
{
    TCB_t *pxTCB = NULL;
    
    //设置当前任务的延时计数
    pxTCB = pxCurrentTCB;
    
    //加入延时列表
    prvAddCurrentTaskToDelayedList(xTicksToDelay);

    //任务切换    
    taskYIELD();        
}

  prvAddCurrentTaskToDelayedList函数完成三件事:将任务移出就绪列表;将任务加入延时列表;给任务装填排序值(即唤醒时刻)

static void prvAddCurrentTaskToDelayedList(TickType_t xTickToWait)
{
    /* 
        brief:将当前任务移出就绪列表,加入延时列表 
    */
    TickType_t xTimeToWake;    
    extern TickType_t xTickCount;
    const TickType_t xConstTickCount = xTickCount;
    
    //移出就绪列表
    //同时如果当前任务所在优先级就绪链表上没有节点了,消除这一优先级
    if(uxListRemove(&(pxCurrentTCB->xStateListItem)) == (UBaseType_t) 0)
    {
        taskRESET_READY_PRIORITY(pxCurrentTCB->uxPriority);            //注意这里和书上不同,书上用的是portRESET,这里用的是宏定义
    }
    
    //计算当前任务唤醒时刻
    xTimeToWake = xConstTickCount + xTickToWait;    
    //将唤醒时间作为排序值
    listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);
    //如果唤醒时间溢出则加入溢出列表(溢出指的是唤醒时刻比当前时刻还早,明显就是出错了);如果没有溢出则加入正常延时列表
    if(xTimeToWake < xConstTickCount)        
    {
        vListInsert(pxOverflowDelayedTaskList, &(pxCurrentTCB->xStateListItem));
    }
    else
    {
        vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));        
        
        //更新唤醒时刻值,如果这个任务的唤醒时刻比当前下一个唤醒时刻快,则表示下一个唤醒时刻应改成这个任务的唤醒时刻
        if(xTimeToWake < xNextTaskUnblockTime)
        {
            xNextTaskUnblockTime = xTimeToWake;
        }
    }    
}

 

5. xTaskIncrementTick

  和之前的递减计数值进行扫描不同,本次是进行系统计数值的递增,每次中断产生则计数值加一,同时判断其是否到达唤醒时刻。同时判断是否计数值溢出,如果溢出,则要进行延时列表切换和计数值重置(因为溢出实质上是计数超过了程序变量能记录的值,所以要用另一个列表装下对应任务和将数值重置)

void xTaskIncrementTick(void)
{
    /* 每次中断进行计数加一,并判断是否到达唤醒时刻 */
    TCB_t *pxTCB = NULL;
    TickType_t xItemValue;
    
    extern TickType_t xTickCount;
        
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;
        
    //如果计时溢出(回到0)则切换列表和重设唤醒时刻
    if(xConstTickCount == (TickType_t) 0U)
    {
        taskSWITCH_DELAYED_LISTS();
    }

    //判断是否到达唤醒时刻
    if(xConstTickCount >= xNextTaskUnblockTime)
    {
        for(;;)
        {
            //如果延时列表已经空了,则不用进行循环判断了
            if(listLIST_IS_EMPTY(pxDelayedTaskList) != pdFALSE)
            {
                xNextTaskUnblockTime = portMAX_DELAY;    //因为没有延时任务了,自然也没有下一个唤醒时刻了,所以初始化为最大时间
                break;
            }
            else
            {
                pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(pxDelayedTaskList);    //获取延时列表第一个任务,即最近一个将要唤醒的任务
                xItemValue = listGET_LIST_ITEM_VALUE(&(pxTCB->xStateListItem));        //得到延时时刻
                
         //这个任务唤醒时间还没到,则进行唤醒时间更新  if(xConstTickCount < xItemValue) { xNextTaskUnblockTime = xItemValue; break; } //从延时列表中删除 (void)uxListRemove(&(pxTCB->xStateListItem)); //加入就绪列表 prvAddTaskToReadyList(pxTCB); } } } portYIELD(); }

  其中taskSWITCH_DELAYED_LISTS为宏定义,完成的事情有:1. 列表对调;2. 溢出次数加1(暂时不知道有什么作用);3. 重置唤醒时刻

#define taskSWITCH_DELAYED_LISTS() \
{ \
    /* 唤醒时间溢出,需要用另外一个列表辅助,此时切换为另外一个列表 */ \
    List_t *pxTemp = NULL; \
    /* 两个列表对调 */ \
    pxTemp = pxDelayedTaskList; \
    pxDelayedTaskList = pxOverflowDelayedTaskList; \
    pxOverflowDelayedTaskList = pxTemp; \
    /* 溢出次数加一 */ \
    xNumOfOverflows++; \
    /* NextTime已经溢出了,任务列表也切换了,所以更新NextTime */ \
    prvResetNextTaskUnblockTime(); \
}

  其中prvResetNextTaskUnblockTime根据情况:如果延时列表没有任务,则重置为最大值;如果延时列表有任务,则设置为最近一个任务的唤醒时刻

static void prvResetNextTaskUnblockTime(void)
{
    TCB_t *pxTCB;
    //如果延时列表空了,则唤醒时间重置为最大
    if(listLIST_IS_EMPTY(pxDelayedTaskList) != pdFALSE)
    {
        xNextTaskUnblockTime = portMAX_DELAY;
    }
    //如果列表还没空,则更新唤醒时间
    else
    {
        (pxTCB) = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(pxDelayedTaskList);
        xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE(&((pxTCB)->xStateListItem));
    }
}

 

6. taskRESET_READY_PRIORITY

  本次引入了延时列表,则就绪列表有可能出现空白(即没有任务)的情况,所以,这次可以实现:当某一优先级的就绪链表为空白时,才可以进行相对应的优先级位图置零

    #define    taskRECORD_READY_PRIORITY( uxPriority ) \
    { \
        /* 如果对应优先级链表为空则在优先级位图置0 */ \
        if(listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[uxPriority])) == (UBaseType_t) 0) \
        { \
            portRECORD_READY_PRIORITY( uxPriority, (uxTopReadyPriority)); \
        } \
    }

 

posted on 2022-11-24 21:55  Toriyung  阅读(184)  评论(0编辑  收藏  举报