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

目录

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

在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                                                        \                                                                                        \         /* The delayed tasks list should beempty when the lists are switched. */       \         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系列第15篇---使用任务通知实现命令行解释器》一文中,我们在串口接收中断中调用了带中断保护的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) {      /*任务具有更高的优先级,返回pdTRUE。告诉调用这个函数的任务,它需要强制切换上下文。*/      xReturn= pdTRUE;       /*带中断保护的API函数的都会有一个参数参数"xHigherPriorityTaskWoken",如果用户没有使用这个参数,这里设置任务切换标志。在下个系统中断服务例程中,会检查xYieldPending的值,如果为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;     /* 每当系统节拍定时器中断发生,移植层都会调用该函数.函数将系统节拍中断计数器加1,       然后检查新的系统节拍中断计数器值是否解除某个任务.*/    if(uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )    {   /* 调度器正常情况 */        const TickType_txConstTickCount = xTickCount + 1;         /* 系统节拍中断计数器加1,如果计数器溢出(为0),交换延时列表指针和溢出延时列表指针 */        xTickCount = xConstTickCount;        if( xConstTickCount == ( TickType_t ) 0U )        {            taskSWITCH_DELAYED_LISTS();        }         /* 查看是否有延时任务到期.任务按照唤醒时间的先后顺序存储在队列中,这意味着只要队列中的最先唤醒任务没有到期,其它任务一定没有到期.*/        if( xConstTickCount >=xNextTaskUnblockTime )        {            for( ;; )            {                if( listLIST_IS_EMPTY( pxDelayedTaskList) != pdFALSE )                {                    /* 如果延时列表为空,设置xNextTaskUnblockTime为最大值 */                   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 /*configUSE_PREEMPTION */                }            }        }         /* 如果有其它任务与当前任务共享一个优先级,则这些任务共享处理器(时间片) */        #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 ) ) */         #if (configUSE_TICK_HOOK == 1 )        {            /* 调用时间片钩子函数*/            if( uxPendedTicks == ( UBaseType_t ) 0U )            {                vApplicationTickHook();            }        }        #endif /*configUSE_TICK_HOOK */    }    else    {   /* 调度器挂起状态,变量uxPendedTicks用于统计调度器挂起期间,系统节拍中断次数.           当调用恢复调度器函数时,会执行uxPendedTicks次本函数(xTaskIncrementTick()):           恢复系统节拍中断计数器,如果有任务阻塞到期,则删除阻塞状态 */        ++uxPendedTicks;         /* 调用时间片钩子函数*/        #if (configUSE_TICK_HOOK == 1 )        {            vApplicationTickHook();        }        #endif    }     #if (configUSE_PREEMPTION == 1 )    {   /* 如果在中断中调用的API函数唤醒了更高优先级的任务,并且API函数的参数pxHigherPriorityTaskWoken为NULL时,变量xYieldPending用于上下文切换标志 */        if( xYieldPending!= pdFALSE )        {            xSwitchRequired = pdTRUE;        }    }    #endif /*configUSE_PREEMPTION */     return xSwitchRequired;}

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

(0)

相关推荐

  • FreeRTOS进阶任务通知示例分析

    目录 在FreeRTOS版本V8.2.0中推出了全新的功能:任务通知.在大多数情况下,任务通知可以替代二进制信号量.计数信号量.事件组,可以替代长度为1的队列(可以保存一个32位整数或指针值),并且任务通知速度更快.使用的RAM更少!我在< FreeRTOS系列第14篇---FreeRTOS任务通知>一文中介绍了任务通知如何使用以及局限性,今天我们将分析任务通知的实现源码,看一下任务通知是如何做到效率与RAM消耗双赢的.        在<FreeRTOS高级篇6---FreeRTOS信

  • FreeRTOS进阶内存管理示例完全解析

    内存管理对应用程序和操作系统来说都非常重要.现在很多的程序漏洞和运行崩溃都和内存分配使用错误有关. FreeRTOS操作系统将内核与内存管理分开实现,操作系统内核仅规定了必要的内存管理函数原型,而不关心这些内存管理函数是如何实现的.这样做大有好处,可以增加系统的灵活性:不同的应用场合可以使用不同的内存分配实现,选择对自己更有利的内存管理策略.比如对于安全型的嵌入式系统,通常不允许动态内存分配,那么可以采用非常简单的内存管理策略,一经申请的内存,甚至不允许被释放.在满足设计要求的前提下,系统越简单

  • FreeRTOS进阶列表和列表项示例分析

    目录 前言 1.初始化列表 2.初始化列表项 4.将列表项插入到列表末端 前言 FreeRTOS内核调度大量使用了列表(list)和列表项(list item)数据结构.我们如果想一探FreeRTOS背后的运行机制,首先遇到的拦路虎就是列表和列表项.对于FreeRTOS内核来说,列表就是它最基础的部分.我们在这一章集中讲解列表和列表项的结构以及操作函数,在下一章讲解任务创建时,会用到本章的知识点. 列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪.挂起.延时的任务,都会被挂接到各自的列表

  • FreeRTOS信号量API函数基础教程

    目录 前言 1创建二进制信号量 1.1函数描述 2创建计数信号量 3创建互斥量 4创建递归互斥量 5删除信号量 6获取信号量 7获取信号量(带中断保护) 8获取递归互斥量 9释放信号量 10释放信号量(带中断保护) 11释放递归互斥量 12获取互斥量持有任务的句柄 前言 FreeRTOS的信号量包括二进制信号量.计数信号量.互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量).我们可以把互斥量和递归互斥量看成特殊的信号量. 信号量API函数实际上都是宏,它使用现有的队列机制.这些宏

  • FreeRTOS进阶之队列示例分析

    目录 FreeRTOS提供了多种任务间通讯方式,包括:任务通知(版本V8.2以及以上版本)队列二进制信号量计数信号量互斥量递归互斥量      其中,二进制信号量.计数信号量.互斥量和递归互斥量都是使用队列来实现的,因此掌握队列的运行机制,是很有必要的.      队列是FreeRTOS主要的任务间通讯方式.可以在任务与任务间.中断和任务间传送信息.发送到队列的消息是通过拷贝实现的,这意味着队列存储的数据是原数据,而不是原数据的引用.先看一下队列的数据结构: typedef struct Que

  • FreeRTOS实时操作系统信号量基础

    目录 前言 1.信号量简介 2.二进制信号量 3.计数信号量 4.互斥量 5.递归互斥量 前言 本文介绍信号量的基础知识,详细源码分析见<FreeRTOS进阶FreeRTOS信号量分析> 1.信号量简介 FreeRTOS的信号量包括二进制信号量.计数信号量.互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量). 我们可以把互斥量和递归互斥量看成特殊的信号量.互斥量和信号量在用法上不同: 信号量用于同步,任务间或者任务和中断间同步:互斥量用于互锁,用于保护同时只能有一个任务访问的资

  • FreeRTOS实时操作系统队列的API函数讲解

    目录 FreeRTOS为操作队列提供了非常丰富的API函数,包括队列的创建.删除,灵活的入队和出队方式.带中断保护的入队和出队等等.下面就来详细讲述这些API函数. 1.获取队列入队信息数目1.1函数描述 UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue ); 返回队列中存储的信息数目.具有中断保护的版本为uxQueueMessagesWaitingFromISR(),原型为:UBaseType_t uxQueueMessagesW

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

    目录 操作系统的运行是由系统节拍时钟驱动的. 在FreeRTOS中,我们知道系统延时和阻塞时间都是以系统节拍时钟周期为单位.在配置文件FreeRTOSConfig.h,改变宏configTICK_RATE_HZ的值,可以改变系统节拍时钟的中断频率,也间接的改变了系统节拍时钟周期(T=1/f).比如设置宏configTICK_RATE_HZ为100,则系统节拍时钟周期为10ms,设置宏configTICK_RATE_HZ为1000,则系统节拍时钟周期为1ms. 系统节拍中断服务程序会调用函数xTa

  • FreeRTOS进阶之空闲任务示例完全解析

    目录 当RTOS调度器开始工作后,为了保证至少有一个任务在运行,空闲任务被自动创建,占用最低优先级(0优先级). xReturn = xTaskCreate( prvIdleTask, "IDLE",configMINIMAL_STACK_SIZE, (void * ) NULL, (tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle); 空闲任务是FreeRTOS不可缺少的任务,因为FreeRTOS设计要求必须至少

  • FreeRTOS进阶之任务通知示例完全解析

    目录 前言 1.发送通知 1.1 xTaskGenericNotify() 1.2 vTaskNotifyGiveFromISR() 1.3 xTaskGenericNotifyFromISR() 2.等待通知 2.1 ulTaskNotifyTake() 2.2 xTaskNotifyWait() 前言 在FreeRTOS版本V8.2.0中推出了全新的功能:任务通知.在大多数情况下,任务通知可以替代二进制信号量.计数信号量.事件组,可以替代长度为1的队列(可以保存一个32位整数或指针值),并且

  • FreeRTOS进阶之系统延时完全解析

    目录 1. 相对延时函数vTaskDelay() 2. 绝对延时函数vTaskDelayUntil() 3.小结 FreeRTOS提供了两个系统延时函数:相对延时函数vTaskDelay()和绝对延时函数 vTaskDelayUntil().相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束: 绝对延时是指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务.换句话说:任务以固定的频率执行. 在<FreeRTOS任务控制>一文中,已经介绍

  • FreeRTOS实时操作系统的内核控制示例解析

    目录 前言 1.强制上下文切换宏 2.进入临界区宏 3.退出临界区宏 4.禁止可屏蔽中断宏 5.使能可屏蔽中断宏 6.启动调度器 6.1函数描述 7.停止调度器 7.1函数描述 8.挂起调度器 8.1函数描述 9.恢复被挂起的调度器 9.1函数描述 9.2返回值 9.3用法举例 10.调整系统节拍 10.1函数描述 10.2参数描述 10.3用法举例 前言 内核控制的一些功能需要移植层提供,为了方便移植,这些API函数用宏来实现,比如上下文切换.进入和退出临界区.禁止和使能可屏蔽中断.内核控制函

  • FreeRTOS进阶之调度器启动过程分析

    目录 使用FreeRTOS,一个最基本的程序架构如下所示: int main(void) { 必要的初始化工作; 创建任务1; 创建任务2; ... vTaskStartScheduler(); /*启动调度器*/ while(1); } 任务创建完成后,静态变量指针pxCurrentTCB(见<FreeRTOS进阶之任务创建完全解析>第7节内容)指向优先级最高的就绪任务.但此时任务并不能运行,因为接下来还有关键的一步:启动FreeRTOS调度器. 调度器是FreeRTOS操作系统的核心,主要

  • FreeRTOS进阶之队列示例完全解析

    目录 前言 1.队列创建函数 2.入队 2.1 xQueueGenericSend() 2.2 xQueueGenericSendFromISR () 3.出队 前言 FreeRTOS提供了多种任务间通讯方式,包括: 任务通知(版本V8.2以及以上版本) 队列 二进制信号量 计数信号量 互斥量 递归互斥量 其中,二进制信号量.计数信号量.互斥量和递归互斥量都是使用队列来实现的,因此掌握队列的运行机制,是很有必要的. 队列是FreeRTOS主要的任务间通讯方式.可以在任务与任务间.中断和任务间传送

随机推荐