下载

1下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 FreeRTOS实时内核使用指南-中文

FreeRTOS实时内核使用指南-中文.pdf

FreeRTOS实时内核使用指南-中文

卷帘风
2017-07-27 0人阅读 举报 0 0 暂无简介

简介:本文档为《FreeRTOS实时内核使用指南-中文pdf》,可适用于IT/计算机领域

http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedUSINGTHEFREERTOSREALTIMEKERNELAPracticalGuideRichardBarryFREERTOS实时内核实用指南http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited这篇文章的英文原版我是在wwwFreeRTOSnet上下载得到的。其实我并没有决定是否要在系统中使用FreeRTOS虽然我想要的也仅仅是一个实时内核当然更重要的是免费。之所以翻译这篇文章倒不是因为FreeRTOS有多么优秀完全是因为这篇文章还不算太长。而且FreeRTOSnet仿佛致力于这个内核在国内的推广也做了不少中文化的工作。所以我是打算利用工作之余边看边译到读完这篇文档也就有个中文版了。如果FreeRTOSnet不弃的话我倒是情愿放到这个网站上与大家共享。另外我本人很懒没有翻译附录而且译完正文后也没有做过任何检查。所以如果有任何问题请不要骂我。ZouChangjunyisfxcomhttp:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited第一章任务管理http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited概览附录中提供了使用FreeRTOS源代码的实用信息小型多任务嵌入式系统简介不同的多任务系统有不同的侧重点。以工作站和桌面电脑为例:早期的处理器非常昂贵所以那时的多任务用于实现在单处理器上支持多用户。这类系统中的调度算法侧重于让每个用户rdquo公平共享rdquo处理器时间。随着处理器功能越来越强大价格却更偏宜所以每个用户都可以独占一个或多个处理器。这类系统的调度算法则设计为让用户可以同时运行多个应用程序而计算机也不会显得反应迟钝。例如某个用户可能同时运行了一个字处理程序一个电子表格一个邮件客户端和一个WEB浏览器并且期望每个应用程序任何时候都能对输入有足够快的响应时间。桌面电脑的输入处理可以归类为rdquo软实时rdquo。为了保证用户的最佳体验计算机对每个输入的响应应当限定在一个恰当的时间范围mdashmdash但是如果响应时间超出了限定范围并不会让人觉得这台电脑无法使用。比如说键盘操作必须在键按下后的某个时间内作出明显的提示。但如果按键提示超出了这个时间会使得这个系统看起来响应太慢而不致于说这台电脑不能使用。仅仅从单处理器运行多线程这一点来说实时嵌入式系统中的多任务与桌面电脑的多任务从概念上来讲是相似的。但实时嵌入式系统的侧重点却不同于桌面电脑mdashmdash特别是当嵌入式系统期望提供rdquo硬实时rdquo行为的时候。硬实时功能必须在给定的时间限制之内完成mdashmdash如果无法做到即意味着整个系统的绝对失败。汽车的安全气囊触发机制就是一个硬实时功能的例子。安全气囊在撞击发生后给定时间限制内必须弹出。如果响应时间超出了这个时间限制会使得驾驶员受到伤害而这原本是可以避免的。大多数嵌入式系统不仅能满足硬实时要求也能满足软实时要求。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited术语说明在FreeRTOS中每个执行线程都被称为rdquo任务rdquo。在嵌入式社区中对此并没有一个公允的术语但我更喜欢用rdquo任务rdquo而不是rdquo线程rdquo因为从以前的经验来看线程具有更多的特定含义。本章的目的是让读者充分了解:在应用程序中FreeRTOS如何为各任务分配处理时间。在任意给定时刻FreeRTOS如何选择任务投入运行。任务优先级如何影响系统行为。任务存在哪些状态。此外还期望能够让读者解:如何实现一个任务。如何创建一个或多个任务的实例。如何使用任务参数。如何改变一个已创建任务的优先级。如何删除任务。如何实现周期性处理。空闲任务何时运行可以用来干什么。本章所介绍的概念是理解如何使用FreeRTOS的基础也是理解基于FreeRTOS的应用程序行为方式的基础mdashmdash因此本章也是这本书中最为详尽的一章。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited任务函数任务是由C语言函数实现的。唯一特别的只是任务的函数原型其必须返回void而且带有一个void指针参数。其函数原型参见程序清单。voidATaskFunction(void*pvParameters)程序清单任务函数原型每个任务都是在自己权限范围内的一个小程序。其具有程序入口通常会运行在一个死循环中也不会退出。一个典型的任务结构如程序清单所示。FreeRTOS任务不允许以任何方式从实现函数中返回mdashmdash它们绝不能有一条rdquoreturnrdquo语句也不能执行到函数末尾。如果一个任务不再需要可以显式地将其删除。这也在程序清单展现。一个任务函数可以用来创建若干个任务mdashmdash创建出的任务均是独立的执行实例拥有属于自己的栈空间以及属于自己的自动变量(栈变量)即任务函数本身定义的变量。voidATaskFunction(void*pvParameters){*可以像普通函数一样定义变量。用这个函数创建的每个任务实例都有一个属于自己的iVarialbleExample变量。但如果iVariableExample被定义为static这一点则不成立ndash这种情况下只存在一个变量所有的任务实例将会共享这个变量。*intiVariableExample=*任务通常实现在一个死循环中。*for(){*完成任务功能的代码将放在这里。*}*如果任务的具体实现会跳出上面的死循环则此任务必须在函数运行完之前删除。传入参数表示删除的是当前任务*vTaskDelete()}程序清单典型的任务函数结构http:wwwFreeRTOSorg顶层任务状态应用程序可以包含多个任务。如果运行应用程序的微控制器只有一个核(core)那么在任意给定时间实际上只会有一个任务被执行。这就意味着一个任务可以有一个或两个状态即运行状态和非运行状态。我们先考虑这种最简单的模型mdashmdash但请牢记这其实是过于简单我们稍后将会看到非运行状态实际上又可划分为若干个子状态。当某个任务处于运行态时处理器就正在执行它的代码。当一个任务处于非运行态时该任务进行休眠它的所有状态都被妥善保存以便在下一次调试器决定让它进入运行态时可以恢复执行。当任务恢复执行时其将精确地从离开运行态时正准备执行的那一条指令开始执行。图顶层任务状态及状态转移任务从非运行态转移到运行态被称为rdquo切换入或切入(switchedin)rdquo或rdquo交换入(swappedin)rdquo。相反任务从运行态转移到非运行态被称为rdquo切换出或切出(switchedout)rdquo或rdquo交换出(swappedout)rdquo。FreeRTOS的调度器是能让任务切入切出的唯一实体。FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited创建任务xTaskCreate()API函数创建任务使用FreeRTOS的API函数xTaskCreate()。这可能是所有API函数中最复杂的函数但不幸的是这也是我们第一个遇到的API函数。但我们必须首先掌控任务因为它们是多任务系统中最基本的组件。本书中的所有示例程序都会用到xTaskCreate()所以会有大量的例子可以参考。附录:描述用到的数据类型和命名约定。portBASETYPExTaskCreate(pdTASKCODEpvTaskCode,constsignedportCHAR*constpcName,unsignedportSHORTusStackDepth,void*pvParameters,unsignedportBASETYPEuxPriority,xTaskHandle*pxCreatedTask)程序清单xTaskCreate()API函数原型表xTaskCreate()参数与返回值参数名描述pvTaskCode任务只是永不退出的C函数实现常通常是一个死循环。参数pvTaskCode只一个指向任务的实现函数的指针(效果上仅仅是函数名)。pcName具有描述性的任务名。这个参数不会被FreeRTOS使用。其只是单纯地用于辅助调试。识别一个具有可读性的名字总是比通过句柄来识别容易得多。应用程序可以通过定义常量configMAXTASKNAMELEN来定义任务名的最大长度mdashmdash包括rsquorsquo结束符。如果传入的字符串长度超过了这个最大值字符串将会自动被截断。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedusStackDepth当任务创建时内核会分为每个任务分配属于任务自己的唯一状态。usStackDepth值用于告诉内核为它分配多大的栈空间。这个值指定的是栈空间可以保存多少个字(word)而不是多少个字节(byte)。比如说如果是位宽的栈空间传入的usStackDepth值为则将会分配字节的栈空间(*bytes)。栈深度乘以栈宽度的结果千万不能超过一个sizet类型变量所能表达的最大值。应用程序通过定义常量configMINIMALSTACKSIZE来决定空闲任务任用的栈空间大小。在FreeRTOS为微控制器架构提供的Demo应用程序中赋予此常量的值是对所有任务的最小建议值。如果你的任务会使用大量栈空间那么你应当赋予一个更大的值。没有任何简单的方法可以决定一个任务到底需要多大的栈空间。计算出来虽然是可能的但大多数用户会先简单地赋予一个自认为合理的值然后利用FreeRTOS提供的特性来确证分配的空间既不欠缺也不浪费。第六章包括了一些信息可以知道如何去查询任务使用了多少栈空间。pvParameters任务函数接受一个指向void的指针(void*)。pvParameters的值即是传递到任务中的值。这篇文档中的一些范例程序将会示范这个参数可以如何使用。uxPriority指定任务执行的优先级。优先级的取值范围可以从最低优先级到最高优先级(configMAXPRIORITIESndash)。configMAXPRIORITIES是一个由用户定义的常量。优先级号并没有上限(除了受限于采用的数据类型和系统的有效内存空间)但最好使用实际需要的最小数值以避免内存浪费。如果uxPriority的值超过了(configMAXPRIORITIESndash)将会导致实际赋给任务的优先级被自动封顶到最大合法值。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedpxCreatedTaskpxCreatedTask用于传出任务的句柄。这个句柄将在API调用中对该创建出来的任务进行引用比如改变任务优先级或者删除任务。如果应用程序中不会用到这个任务的句柄则pxCreatedTask可以被设为。返回值有两个可能的返回值:pdTRUE表明任务创建成功。errCOULDNOTALLOCATEREQUIREDMEMORY由于内存堆空间不足FreeRTOS无法分配足够的空间来保存任务结构数据和任务栈因此无法创建任务。第五章将提供更多有关内存管理方面的信息。例创建任务附录:包含一些关于示例程序生成工具的信息。本例演示了创建并启动两个任务的必要步骤。这两个任务只是周期性地打印输出字符串采用原始的空循环方式来产生周期延迟。两者在创建时指定了相同的优先级并且在实现上除输出的字符串外完全一样mdashmdash程序清单和程序清单是这两个任务对应的实现代码。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedvoidvTask(void*pvParameters){constchar*pcTaskName=Taskisrunningrnvolatileunsignedlongul*和大多数任务一样该任务处于一个死循环中。*for(){*Printoutthenameofthistask*vPrintString(pcTaskName)*延迟以产生一个周期*for(ul=ulmainDELAYLOOPCOUNTul){*这个空循环是最原始的延迟实现方式。在循环中不做任何事情。后面的示例程序将采用delaysleep函数代替这个原始空循环。*}}}程序清单例中的第一个任务实现代码voidvTask(void*pvParameters){constchar*pcTaskName=Taskisrunningrnvolatileunsignedlongul*和大多数任务一样该任务处于一个死循环中。*for(){*Printoutthenameofthistask*vPrintString(pcTaskName)*延迟以产生一个周期*for(ul=ulmainDELAYLOOPCOUNTul){*这个空循环是最原始的延迟实现方式。在循环中不做任何事情。后面的示例程序将采用delaysleep函数代替这个原始空循环。*}}}程序清单例中的第二个任务实现代码main()函数只是简单地创建这两个任务然后启动调度器mdashmdash具体实现代码参见程序程单。http:wwwFreeRTOSorgintmain(void){*创建第一个任务。需要说明的是一个实用的应用程序中应当检测函数xTaskCreate()的返回值以确保任务创建成功。*xTaskCreate(vTask,*指向任务函数的指针*Task,*任务的文本名字只会在调试中用到*,*栈深度ndash大多数小型微控制器会使用的值会比此值小得多*,*没有任务参数*,*此任务运行在优先级上*)*不会用到任务句柄**Createtheothertaskinexactlythesamewayandatthesamepriority*xTaskCreate(vTask,Task,,,,)*启动调度器任务开始执行*vTaskStartScheduler()*如果一切正常main()函数不应该会执行到这里。但如果执行到这里很可能是内存堆空间不足导致空闲任务无法创建。第五章有讲述更多关于内存管理方面的信息*for()}程序清单启动例中的任务本例的运行输出如图所示图例的运行输出图中看到两个任务在同时运行但实际上这两个任务运行在同一个处理器上所以不可能会同时运行。事实上这两个任务都迅速地进入与退出运行态。由于这两个任务运行在同一个处理器上所以会平等共享处理器时间。真实的执行流程所图所示。FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorg图中底部的箭头表示从t起始的运行时刻。彩色的线段表示在每个时间点上正在运行的任务mdashmdash比如t与t之间运行的是任务。在任何时刻只可能有一个任务处于运行态。所以一个任务进入运行态后(切入)另一个任务就会进入非运行态(切出)。图例的实际执行流程例中main()函数在启动调度器之前先完成两个任务的创建。当然也可以从一个任务中创建另一个任务。我们可以先在main()中创建任务然后在任务中创建任务。如果我们需要这样做则任务代码就应当修改成程序清单所示的样子。这样在调度器启动之前任务还没有被创建但是整个程序运行的输出结果还是相同的。FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedvoidvTask(void*pvParameters){constchar*pcTaskName=Taskisrunningrnvolatileunsignedlongul*如果已经执行到本任务的代码表明调度器已经启动。在进入死循环之前创建另一个任务。*xTaskCreate(vTask,Task,,,,)for(){*Printoutthenameofthistask*vPrintString(pcTaskName)*Delayforaperiod*for(ul=ulmainDELAYLOOPCOUNTul){*ThisloopisjustaverycrudedelayimplementationThereisnothingtodoinhereLaterexampleswillreplacethiscrudeloopwithaproperdelaysleepfunction*}}}程序清单在一个任务中创建另一个任务mdashmdash在调度器启动之后例使用任务参数例中创建的两个任务几乎完全相同唯一的区别就是打印输出的字符串。这种重复性可以通过创建同一个任务代码的两个实例来去除。这时任务参数就可以用来传递各自打印输出的字符串。程序清单包含了例中用到的唯一一个任务函数代码(vTaskFunction)。这一个任务函数代替了例中的两个任务函数(vTask与vTask)。这个函数的任务参数被强制转化为char*以得到任务需要打印输出的字符串。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedvoidvTaskFunction(void*pvParameters){char*pcTaskNamevolatileunsignedlongul*需要打印输出的字符串从入口参数传入。强制转换为字符指针。*pcTaskName=(char*)pvParameters*Aspermosttasks,thistaskisimplementedinaninfiniteloop*for(){*Printoutthenameofthistask*vPrintString(pcTaskName)*Delayforaperiod*for(ul=ulmainDELAYLOOPCOUNTul){*ThisloopisjustaverycrudedelayimplementationThereisnothingtodoinhereLaterexerciseswillreplacethiscrudeloopwithaproperdelaysleepfunction*}}}程序清单例中用于创建两个任务实例的任务函数尽管现在只有一个任务实现代码(vTaskFunction)但是可以创建多个任务实例。每个任务实例都可以在FreeRTOS调度器的控制下独运行。传递给API函数xTaskCreate()的参数pvPrameters用于传入字符串文本。如程序清单所示。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited*定义将要通过任务参数传递的字符串。定义为const且不是在栈空间上以保证任务执行时也有效。*staticconstchar*pcTextForTask=ldquoTaskisrunningrnrdquostaticconstchar*pcTextForTask=ldquoTaskisrunningtnrdquointmain(void){*Createoneofthetwotasks*xTaskCreate(vTaskFunction,*指向任务函数的指针*Task,*任务名*,*栈深度*(void*)pcTextForTask,*通过任务参数传入需要打印输出的文本*,*此任务运行在优先级上*)*不会用到此任务的句柄**同样的方法创建另一个任务。至此由相同的任务代码(vTaskFunction)创建了多个任务仅仅是传入的参数不同。同一个任务创建了两个实例。*xTaskCreate(vTaskFunction,Task,,(void*)pcTextForTask,,)*Starttheschedulersoourtasksstartexecuting*vTaskStartScheduler()*Ifalliswellthenmain()willneverreachhereastheschedulerwillnowberunningthetasksIfmain()doesreachherethenitislikelythattherewasinsufficientheapmemoryavailablefortheidletasktobecreatedCHAPTERprovidesmoreinformationonmemorymanagement*for()}程序清单例中的main()函数实现代码例的运行输出结果与例完全一样参见图。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited任务优先级xTaskCreate()API函数的参数uxPriority为创建的任务赋予了一个初始优先级。这个侁先级可以在调度器启动后调用vTaskPrioritySet()API函数进行修改。应用程序在文件FreeRTOSConfigh中设定的编译时配置常量configMAXPRIORITIES的值即是最多可具有的优先级数目。FreeRTOS本身并没有限定这个常量的最大值但这个值越大则内核花销的内存空间就越多。所以总是建议将此常量设为能够用到的最小值。对于如何为任务指定优先级FreeRTOS并没有强加任何限制。任意数量的任务可以共享同一个优先级mdashmdash以保证最大设计弹性。当然如果需要的话你也可以为每个任务指定唯一的优先级(就如同某些调度算法的要求一样)但这不是强制要求的。低优先级号表示任务的优先级低优先级号表示最低优先级。有效的优先级号范围从到(configMAXPRIORITESndash)。调度器保证总是在所有可运行的任务中选择具有最高优先级的任务并使其进入运行态。如果被选中的优先级上具有不止一个任务调度器会让这些任务轮流执行。这种行为方式在之前的例子中可以明显看出来。两个测试任务被创建在同一个优先级上并且一直是可运行的。所以每个任务都执行一个rdquo时间片rdquo任务在时间片起始时刻进入运行态在时间片结束时刻又退出运行态。图中t与t之间的时段就等于一个时间片。要能够选择下一个运行的任务调度器需要在每个时间片的结束时刻运行自己本身。一个称为心跳(tick有些地方被称为时钟滴答本文中一律称为时钟心跳)中断的周期性中断用于此目的。时间片的长度通过心跳中断的频率进行设定心跳中断频率由FreeRTOSConfigh中的编译时配置常量configTICKRATEHZ进行配置。比如说如果configTICKRATEHZ设为(HZ)则时间片长度为ms。可以将图进行扩展将调度器本身的执行时间在整个执行流程中体现出来。请参见图。需要说明的是FreeRTOSAPI函数调用中指定的时间总是以心跳中断为单位(通常的提法为心跳rdquoticksrdquo)。常量portTICKRATEMS用于将以心跳为单位的时间值转化为以毫秒为单位的时间值。有效精度依赖于系统心跳频率。心跳计数(tickcount)值表示的是从调度器启动开始心跳中断的总数并假定心跳http:wwwFreeRTOSorg计数器不会溢出。用户程序在指定延迟周期时不必考虑心跳计数溢出问题因为时间连贯性在内核中进行管理。图对执行流程进行扩展以显示心跳中断的执行图中红色的线段表时内核本身在运行。黑色箭头表示任务到中断中断再到另一个任务的执行顺序。例优先级实验调度器总是在可运行的任务中选择具有最高优级级的任务并使其进入运行态。到目前为止的示例程序中两个任务都创建在相同的优先级上。所以这两个任务轮番进入和退出运行态。本例将改变例其中一个任务的优先级看一下倒底会发生什么。现在第一个任务创建在优先级上而另一个任务创建在优先级上。创建这两个任务的代码参见程序清单。这两个任务的实现函数没有任何改动还是通过空循环产生延迟来周期性打印输出字符串。FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorg*定义将要通过任务参数传递的字符串。定义为const且不是在栈空间上以保证任务执行时也有效。*staticconstchar*pcTextForTask=ldquoTaskisrunningrnrdquostaticconstchar*pcTextForTask=ldquoTaskisrunningtnrdquointmain(void){*第一个任务创建在优先级上。优先级是倒数第二个参数。*xTaskCreate(vTaskFunction,Task,,(void*)pcTextForTask,,)*第二个任务创建在优先级上。*xTaskCreate(vTaskFunction,Task,,(void*)pcTextForTask,,)*Starttheschedulersothetasksstartexecuting*vTaskStartScheduler()return}程序清单两个任务创建在不同的优先级上图是例的运行结果。图两个测试任务运行在不同的优先级上调度器总是选择具有最高优先级的可运行任务来执行。任务的优先级比任务高并且总是可运行因此任务是唯一一个一直处于运行态的任务。而任务不可能进入运行态所以不可能输出字符串。这种情况我们称为任务的执行时间被任务rdquo饿死(starved)rdquo了。任务之所以总是可运行是因为其不会等待任何事情mdashmdash它要么在空循环里打转要么往终端打印字符串。FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorg图展现了例的执行流程。图当一个任务优先比另一个高时的执行流程FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited扩充ldquo非运行态rdquo到目前为止所有用到的示例中创建的每个任务都只顾不停地处理自己的事情而没有其它任何事情需要等待mdashmdash由于它们不需要等待所以总是能够进入运行态。这种rdquo不停处理rdquo类型的任务限制了其有用性因为它们只可能被创建在最低优先级上。如何它们运行在其它任何优先级上那么比它们优先级更低的任务将永远没有执行的机会。为了使我们的任务切实有用我们需要通过某种方式来进行事件驱动。一个事件驱动任务只会在事件发生后触发工作(处理)而在事件没有发生时是不能进入运行态的。调度器总是选择所有能够进入运行态的任务中具有最高优先级的任务。一个高优先级但不能够运行的任务意味着不会被调度器选中而代之以另一个优先级虽然更低但能够运行的任务。因此采用事件驱动任务的意义就在于任务可以被创建在许多不同的优先级上并且最高优先级任务不会把所有的低优先级任务饿死。阻塞状态如果一个任务正在等待某个事件则称这个任务处于rdquo阻塞态(blocked)rdquo。阻塞态是非运行态的一个子状态。任务可以进入阻塞态以等待以下两种不同类型的事件:定时(时间相关)事件mdashmdash这类事件可以是延迟到期或是绝对时间到点。比如说某个任务可以进入阻塞态以延迟ms。同步事件mdashmdash源于其它任务或中断的事件。比如说某个任务可以进入阻塞态以等待队列中有数据到来。同步事件囊括了所有板级范围内的事件类型。FreeRTOS的队列二值信号量计数信号量互斥信号量(recursivesemaphore,递归信号量本文一律称为互斥信号量因为其主要用于实现互斥访问)和互斥量都可以用来实现同步事件。第二章和第三章涵盖了有关这些的详细内容。任务可以在进入阻塞态以等待同步事件时指定一个等待超时时间这样可以有效地实现阻塞状态下同时等待两种类型的事件。比如说某个任务可以等待队列中有数据到来但最多只等ms。如果ms内有数据到来或是ms过去了还没有数据到来这两种情况下该任务都将退出阻塞态。http:wwwFreeRTOSorg挂起状态ldquo挂起(suspended)rdquo也是非运行状态的子状态。处于挂起状态的任务对调度器而言是不可见的。让一个任务进入挂起状态的唯一办法就是调用vTaskSuspend()API函数而把一个挂起状态的任务唤醒的唯一途径就是调用vTaskResume()或vTaskResumeFromISR()API函数。大多数应用程序中都不会用到挂起状态。就绪状态如果任务处于非运行状态但既没有阻塞也没有挂起则这个任务处于就绪(ready准备或就绪)状态。处于就绪态的任务能够被运行但只是rdquo准备(ready)rdquo运行而当前尚未运行。完整的状态转移图图对之前那个过于简单的状态图进行了扩充包含了本节描述的非运行状态的子状态。目前为止所有用到的示例程序中创建的任务都还没有用到阻塞状态和挂起状态仅仅是在就绪状态和运行状态之间转移mdashmdash图中以粗线进行醒目提示。图完整的任务状态机FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited例利用阻塞态实现延迟之前的示例中所有创建的任务都是rdquo周期性rdquo的mdashmdash它们延迟一个周期时间打印输出字符串再一次延迟如此周而复始。而产生延迟的方法也相当原始地使用了空循环mdashmdash不停地查询并递增一个循环计数直至计到某个指定值。例明确的指出了这种方法的缺点。一直保持在运行态中执行空循环可能将其它任务饿死。其实以任何方式的查询都不仅仅只是低效还有各种其它方面的缺点。在查询过程中任务实际上并没有做任何有意义的事情但它依然会耗尽所有处理时间对处理器周期造成浪费。例通过调用vTaskDelay()API函数来代替空循环对这种rdquo不良行为rdquo进行纠正。vTaskDelay()的函数原型见程序清单而新的任务实现见程序清单。voidvTaskDelay(portTickTypexTicksToDelay)程序清单vTaskDelay()API函数原型表vTaskDelay()参数参数名描述xTicksToDelay延迟多少个心跳周期。调用该延迟函数的任务将进入阻塞态经延迟指定的心跳周期数后再转移到就绪态。举个例子当某个任务调用vTaskDelay()时心跳计数值为,则该任务将保持在阻塞态直到心跳计数计到,。常数portTICKRATEMS可以用来将以毫秒为单位的时间值转换为以心跳周期为单位的时间值。http:wwwFreeRTOSorgvoidvTaskFunction(void*pvParameters){char*pcTaskName*ThestringtoprintoutispassedinviatheparameterCastthistoacharacterpointer*pcTaskName=(char*)pvParameters*Aspermosttasks,thistaskisimplementedinaninfiniteloop*for(){*Printoutthenameofthistask*vPrintString(pcTaskName)*延迟一个循环周期。调用vTaskDelay()以让任务在延迟期间保持在阻塞态。延迟时间以心跳周期为单位常量portTICKRATEMS可以用来在毫秒和心跳周期之间相换转换。本例设定毫秒的循环周期。*vTaskDelay(portTICKRATEMS)}}程序清单调用vTaskDelay()来代替空循环实现延迟尽管两个任务实例还是创建在不同的优先级上但现在两个任务都可以得到执行。例的运行输出结果参见图。图所示的执行流程可以解释为什么此时不同优先级的两个任务竟然都可以得到执行。图中为了简便忽略了内核自身的执行时间。FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorg空闲任务是在调度器启动时自动创建的以保证至少有一个任务可运行(至少有一个任务处于就绪态)。本章第节会对空闲任务进行更详细的描述。图用vTaskDelay()代替空循环后的执行流程本例是只改变了两个任务的实现方式并没有改变其功能。对比图与图可以清晰地看到本例以更有效的方式实现了任务的功能。图展现的是当任务采用空循环进行延迟时的执行流程mdashmdash结果就是任务总是可运行并占用了大量的机器周期。从图中的执行流程中可以看到任务在整个延迟周期内都处于阻塞态只在完成实际工作的时候才占用处理器时间(本例中任务的实际工作只是简单地打印输出一条信息)。在图所示的情形中任务离开阻塞态后仅仅执行了一个心跳周期的一个片段然后又再次进入阻塞态。所以大多数时间都没有一个应用任务可运行(即没有应用任务处于就绪态)因此没有应用任务可以被选择进入运行态。这种情况下空闲任务得以执行。空间任务可以获得的执行时间量是系统处理能力裕量的一个度量指标。图中的粗线条表示例中任务的状态转移过程。现在每个任务在返回就绪态之前都会经过阻塞状态。FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorg图粗线条表示例中的状态转移过程vTaskDelayUntil()API函数vTaskDelayUntil()类似于vTaskDelay()。和范例中演示的一样函数vTaskDelay()的参数用来指定任务在调用vTaskDelay()到切出阻塞态整个过程包含多少个心跳周期。任务保持在阻塞态的时间量由vTaskDelay()的入口参数指定但任务离开阻塞态的时刻实际上是相对于vTaskDelay()被调用那一刻的。vTaskDelayUntil()的参数就是用来指定任务离开阻塞态进入就绪态那一刻的精确心跳计数值。API函数vTaskDelayUntil()可以用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时候)。由于调用此函数的任务解除阻塞的时间是绝对时刻比起相对于调用时刻的相对时间更精确(即比调用vTaskDelay()可以实现更精确的周期性)。voidvTaskDelayUntil(portTickType*pxPreviousWakeTime,portTickTypexTimeIncrement)程序清单vTaskDelayUntil()API函数原型FreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedhttp:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited表vTaskDelayUntil()参数参数名描述pxPreviousWakeTime此参数命名时假定vTaskDelayUntil()用于实现某个任务以固定频率周期性执行。这种情况下pxPreviousWakeTime保存了任务上一次离开阻塞态(被唤醒)的时刻。这个时刻被用作一个参考点来计算该任务下一次离开阻塞态的时刻。pxPreviousWakeTime指向的变量值会在API函数vTaskDelayUntil()调用过程中自动更新应用程序除了该变量第一次初始化外通常都不要修改它的值。程序清单展示了这个参数的使用方法。xTimeIncrement此参数命名时同样是假定vTaskDelayUntil()用于实现某个任务以固定频率周期性执行mdashmdash这个频率就是由xTimeIncrement指定的。xTimeIncrement的单位是心跳周期可以使用常量portTICKRATEMS将毫秒转换为心跳周期。例转换示例任务使用vTaskDelayUntil()例中的两个任务是周期性任务但是使用vTaskDelay()无法保证它们具有固定的执行频率因为这两个任务退出阻塞态的时刻相对于调用vTaskDelay()的时刻。通过调用vTaskDelayUntil()代替vTaskDelay()把这两个任务进行转换以解决这个潜在的问题。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibitedvoidvTaskFunction(void*pvParameters){char*pcTaskNameportTickTypexLastWakeTime*ThestringtoprintoutispassedinviatheparameterCastthistoacharacterpointer*pcTaskName=(char*)pvParameters*变量xLastWakeTime需要被初始化为当前心跳计数值。说明一下这是该变量唯一一次被显式赋值。之后xLastWakeTime将在函数vTaskDelayUntil()中自动更新。*xLastWakeTime=xTaskGetTickCount()*Aspermosttasks,thistaskisimplementedinaninfiniteloop*for(){*Printoutthenameofthistask*vPrintString(pcTaskName)*本任务将精确的以毫秒为周期执行。同vTaskDelay()函数一样时间值是以心跳周期为单位的可以使用常量portTICKRATEMS将毫秒转换为心跳周期。变量xLastWakeTime会在vTaskDelayUntil()中自动更新因此不需要应用程序进行显示更新。*vTaskDelayUntil(xLastWakeTime,(portTICKRATEMS))}}程序清单使用vTaskDelayUntil()实现示例任务例的运行输出与例完全相同请参考图。例合并阻塞与非阻塞任务之前的范例分别测试了任务以查询方式和阻塞方式工作的系统行为。本例通过合并这两种方案的执行流程再次实现具有既定预期的系统行为。在优先级上创建两个任务。这两个任务只是不停地打印输出字符串然它什么事情也不做。这两个任务没有调用任何可能导致它们进入阻塞态的API函数所以这两个任务要么处于就绪态要么处于运行态。具有这种性质的任务被称为rdquo不停处理(或持续处理continuousprocessing)rdquo任务因为它们总是有事情要做虽然在本例中的它们做的事情没什么意义。持续处理任务的源代码参见程序清单。http:wwwFreeRTOSorgFreeRTOSDesignedForMicrocontrollerscopyRichardBarryDistributionorpublicationinanyformisstrictlyprohibited第三个任务创建在优先级上高于另外两个任务的优先级。这个任务虽然也是打印输出字符串但它是周期性的所以调用了vTaskDelayUntil()在每两次打印之间让自己处于阻塞态。周期性任务的实现代码参见程序清单。voidvContinuousProcessingTask(void*pvParameters){char*pcTaskName*打

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/148

FreeRTOS实时内核使用指南-中文

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利