文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

FreeRTOS进阶系统节拍时钟示例的完全解析

2024-04-02 19:55

关注

前言

操作系统的运行是由系统节拍时钟驱动的。

在FreeRTOS中,我们知道系统延时和阻塞时间都是以系统节拍时钟周期为单位。在配置文件FreeRTOSConfig.h,改变宏configTICK_RATE_HZ的值,可以改变系统节拍时钟的中断频率,也间接的改变了系统节拍时钟周期(T=1/f)。比如设置宏configTICK_RATE_HZ为100,则系统节拍时钟周期为10ms,设置宏configTICK_RATE_HZ为1000,则系统节拍时钟周期为1ms。

系统节拍中断服务程序会调用函数xTaskIncrementTick()来完成主要工作,如果该函数返回值为真(不等于pdFALSE),说明处于就绪态任务的优先级比当前运行的任务优先级高。这会触发一次PendSV中断,进行上下文切换。我们重点看一下函数xTaskIncrementTick()做了哪些事情,以及什么情况下返回真值。

1.调度器正常情况

调度器正常(没有挂起),即变量uxSchedulerSuspended的值为pdFALSE。变量uxSchedulerSuspended是定义在tasks.c文件中的静态变量,记录调度器运行状态。当调用API函数vTaskSuspendAll()挂起调度器时,会将变量uxSchedulerSuspended增1。所以变量uxSchedulerSuspended为真时,表示调度器被挂起。

调度器正常情况下,首先将变量xTickCount增1。变量xTickCount也是在tasks.c文件中定义的静态变量,它在启动调度器时被清零,在每次系统节拍时钟发生中断后加1,用来记录系统节拍时钟中断的次数。内核会将所有阻塞的任务跟这个变量比较,以判断是否超时(超时意味着可以解除阻塞)。

变量xTickCount的数据类型跟具体硬件有关,32位架构硬件一般是无符号32位变量、8位或16位架构一般是无符号16位变量。即便是32位变量,xTickCount累加到0xFFFFFFFF后也会溢出。因此,在程序中要判断变量xTickCount是否溢出。如果溢出(xTickCount为0),则调用宏taskSWITCH_DELAYED_LISTS()交换延时列表指针和溢出延时列表指针。这个牵扯的有点广,我们慢慢说明。

为了解决xTickCount溢出问题,FreeRTOS使用了两个延时列表:xDelayedTaskList1和xDelayedTaskList2。并使用延时列表指针pxDelayedTaskList和溢出延时列表指针pxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)。顺便说一下,上面的两个延时列表指针变量和两个延时列表变量都是在tasks.c中定义的静态局部变量。

比如我们使用API延时函数vTaskDelay(xTicksToDelay)将任务延时xTicksToDelay个系统节拍周期,延时函数会以当前的系统节拍中断次数xTickCount为参考,这个值加上参数规定的延时时间xTicksToDelay,即xTickCount+ xTicksToDelay,就是下次唤醒任务的时间。

xTickCount+xTicksToDelay会被记录到任务TCB中,随着任务一起挂接到延时列表。如果内核判断出xTickCount+ xTicksToDelay溢出(大于32位可以表示的最大值),就将当前任务挂接到列表指针pxOverflowDelayedTaskList指向的列表中,否则就挂接到列表指针pxDelayedTaskList指向的列表中。任务按照延时时间,顺序的插入到延时列表中。

所以当系统节拍中断次数计数器xTickCount溢出时,必须将延时列表指针pxDelayedTaskList和溢出延时列表指针pxOverflowDelayedTaskList交换以便正确处理延时的任务。宏taskSWITCH_DELAYED_LISTS()的代码如下所示:

#definetaskSWITCH_DELAYED_LISTS()                                                       \
{                                                                                       \
         List_t *pxTemp		                                                        \
                                                                                        \
                \
         configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList) ) );                     \
                                                                                        \
         pxTemp = pxDelayedTaskList;                                                    \
         pxDelayedTaskList = pxOverflowDelayedTaskList;                                 \
         pxOverflowDelayedTaskList = pxTemp;                                            \
         xNumOfOverflows++;                                                             \
         prvResetNextTaskUnblockTime                                                    \
}

这段代码完成两部分工作,第一是将延时列表指针pxDelayedTaskList和溢出延时列表指针pxOverflowDelayedTaskList交换;第二是调用函数prvResetNextTaskUnblockTime()重新获取下一次解除阻塞的时间,这个时间保存在静态变量xNextTaskUnblockTime中,该变量也是定义在tasks.c中。下面检查延时列表任务是否到期时,会用到这个变量。
接下来函数会检查延时列表,查看延时的任务是否到期。前面我们说过,延时的任务根据延时时间先后,顺序的插入到延时列表中,延时时间短的在前,延时时间长的在后,并且下一个要被唤醒任务的时间数值保存在变量xNextTaskUnblockTime中。所以使用xTickCount与xNextTaskUnblockTime比较就可以知道是否有任务可以被唤醒。

if( xConstTickCount >=xNextTaskUnblockTime )
{
   
}

如果任务被唤醒,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务优先级,则会触发一次上下文切换。

FreeRTOS支持多个任务共享同一个优先级,如果设置为抢占式调度(宏configUSE_PREEMPTION设置为1)并且宏configUSE_TIME_SLICING也为1(或未定义),则相同优先级的多个任务间进行任务切换。

最后还会调用时间片钩子函数vApplicationTickHook()。可以看到时间片钩子函数实在中断服务函数中调用的,所以这个钩子函数必须简洁、不可以调用不带中断保护的API函数。

2.调度器挂起情况

如果调度器挂起,正在执行的任务会一直继续执行,内核不再调度(意味着当前任务不会被切换出去),直到该任务调用了xTaskResumeAll()函数。

在调度器挂起阶段内,FreeRTOS使用静态变量uxPendedTicks记录挂起期间,系统节拍中断的次数。当调用恢复调度器函数xTaskResumeAll()时,会执行uxPendedTicks次本函数(xTaskIncrementTick())。变量uxPendedTicks同样是在tasks.c中定义的。

3.自动任务切换

函数的最后几行代码颇让人难以理解,其中局部变量xSwitchRequired是本函数的返回值,在文章开始也说过:“如果该函数返回值为真,说明处于就绪态任务的优先级高于当前运行任务的优先级,则会触发一次PendSV中断,进行上下文切换”,现在如果变量xYieldPending为真,则返回值也会为真,函数结束后会进行上下文切换。这个变量xYieldPending的作用是什么?又是在什么时候被赋值为真呢?还真要从头说起。

if( xYieldPending != pdFALSE )
{
    xSwitchRequired = pdTRUE;
}

带中断保护的API函数,都会有一个参数pxHigherPriorityTaskWoken。如果API函数导致一个任务解锁,并且解锁的任务优先级高于当前运行的任务,则API函数将*pxHigherPriorityTaskWoken设置成pdTRUE。在中断退出前,老版本的FreeRTOS需要手动触发一次任务切换。比如在FreeRTOS使用任务通知实现命令行解释器一文中,我们在串口接收中断中调用了带中断保护的API函数vTaskNotifyGiveFromISR(),在函数执行完后,会使用代码portYIELD_FROM_ISR(xHigherPriorityTaskWoken)判断参数xHigherPriorityTaskWoken是否为真,为真则手动强制上下文切换。

   BaseType_txHigherPriorityTaskWoken = pdFALSE;        
    
   vTaskNotifyGiveFromISR(xCmdAnalyzeHandle,&xHigherPriorityTaskWoken); 
    
   portYIELD_FROM_ISR(xHigherPriorityTaskWoken );  

从FreeRTOSV7.3.0起,pxHigherPriorityTaskWoken成为一个可选参数,并可以设置为NULL。如果将参数xHigherPriorityTaskWoken设置为NULL,并且带中断保护的API函数导致更高优先级任务解锁,任务什么时候、怎么切换呢?

原来从FreeRTOSV7.3.0起,内核增加了一个静态变量xYieldPending,这个变量也是在tasks.c中定义的。如果将变量xYieldPending设置为pdTRUE,则会在下一次系统节拍中断服务函数中,触发一次任务切换,见本小节第一段代码描述。

让我们看一下这个过程是如何实现的。

对于队列以及使用队列机制的信号量、互斥量等,在中断服务程序中调用了这些API函数,将任务从阻塞中解除,则需要调用函数xTaskRemoveFromEventList()将任务的事件列表项从事件列表中移除。在移除事件列表项的过程中,会判断解除的任务优先级是否大于当前任务的优先级,如果解除的任务优先级更高,会将变量xYieldPending设置为pdTRUE。在下一次系统节拍中断服务函数中,触发一次任务切换。代码如下所示:

  if(pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority)
 {
      
      xReturn= pdTRUE;
      
      xYieldPending= pdTRUE;
 }        

对于FreeRTOSV8.2.0新推出的任务通知,也提供了带中断保护版本的API函数。按照逻辑推断,这些API函数的参数xHigherPriorityTaskWoken也可以不使用,变量xYieldPending也应该作用于这些API函数。但事实是,在FreeRTOSV9.0之前的版本,FreeRTOS都没有实现这个功能,如果使用这些API函数解除了一个更高优先级任务,必须手动的进行上下文切换。这可能是一个BUG,因为在FreeRTOS V9.0版本中,已经修复了这个问题,可以使用变量xYieldPending自动切换上下文。这个BUG由QQ昵称为“所长”的网友遇到。

在V9.0以及以上版本中,如果在中断中释放的通知引起更高优先级的任务解锁,API函数会判断参数xHigherPriorityTaskWoken是否有效,有效则将*xHigherPriorityTaskWoken设置为pdTRUE,此时需要手动切换上下文;否则,将变量xYieldPending设置为pdTRUE,在下一次系统节拍中断服务函数中,触发一次任务切换。代码如下所示:

if( pxTCB->uxPriority >pxCurrentTCB->uxPriority ) 
{ 
          
         if(pxHigherPriorityTaskWoken != NULL ) 
         { 
                   *pxHigherPriorityTaskWoken= pdTRUE;     
         } 
         else 
         { 
                   xYieldPending= pdTRUE;                  
         } 
}

函数xTaskIncrementTick()完整代码如下所示,根据上面的讲解以及代码的注释,理解这些代码应该不是难事。

BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
    
    if(uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {   
        const TickType_txConstTickCount = xTickCount + 1;
        
        xTickCount = xConstTickCount;
        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 ) );
                    
                    if(listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
                    }
                    
                   prvAddTaskToReadyList( pxTCB );
 
                    #if (  configUSE_PREEMPTION == 1 )
                    {
                        
                        if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                        {
                            xSwitchRequired= pdTRUE;
                        }
                    }
                    #endif 
                }
            }
        }
        
        #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 
        #if (configUSE_TICK_HOOK == 1 )
        {
            
            if( uxPendedTicks == ( UBaseType_t ) 0U )
            {
                vApplicationTickHook();
            }
        }
        #endif 
    }
    else
    {   
        ++uxPendedTicks;
 
        
        #if (configUSE_TICK_HOOK == 1 )
        {
            vApplicationTickHook();
        }
        #endif
    }
    #if (configUSE_PREEMPTION == 1 )
    {   
        if( xYieldPending!= pdFALSE )
        {
            xSwitchRequired = pdTRUE;
        }
    }
    #endif 
    return xSwitchRequired;
}

以上就是FreeRTOS进阶系统节拍时钟示例的完全解析的详细内容,更多关于FreeRTOS进阶系统节拍时钟的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯