免费且源码公开的FreeRTOS操作系统真的好吗?

您所在的位置:网站首页 freertosh 免费且源码公开的FreeRTOS操作系统真的好吗?

免费且源码公开的FreeRTOS操作系统真的好吗?

2023-01-29 01:35| 来源: 网络整理| 查看: 265

 

在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用 CPU 的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。由于 RTOS 需占用一定的系统资源(尤其是 RAM 资源),只有μC/OS-II、embOS、salvo、FreeRTOS 等少数实时操作系统能在小 RAM 单片机上运行。相对于 C/OS-II、embOS 等商业操作系统,FreeRTOS 操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行。

实时操作系统与非实时操作系统的区别   

他们之间的区别,详见下图:

在上面的图中右边的任务优先级高于左边的任务,先看实时操作系统的,当优先级更高的任务 2 就绪的时候,即便任务 1 正在运行中,也必须立刻交出 CPU 的使用权,就跟中断一样,先执行任务 2,等任务 2 执行完或者主动挂起(sleep)让出 CPU 的时候,任务 1 才能接着运行。

uCOS 就是这样的实时操作系统,它是可抢占性的内核。我曾跟很多同事争辩过 uCOS 高优先级任务就绪而低优先级任务正在执行没有 sleep 的时候,高优先级任务能否打断低优先级任务而立即得到执行,遗憾的是很多人仍然坚持必须要 sleep 才能切换任务,每次我都只能无奈的用实验来证明这个本来不应该争辩的东西。

再看看我们的 Linux/Windows/OSX 这些基于时间片轮转的操作系统遇到这种问题的时候会怎么样呢,毫无疑问它们都是非实时的操作系统,CPU 是不可抢占的,从上图可以看到,即便高优先级的任务就绪了,也不能马上中断低优先级任务而得到执行,必须要等到低优先级任务主动挂起(sleep)或者时间片结束才能得到执行。所以我们在使用 PC 的时候经常会遇到应用程序无响应的问题。即硬件资源被其他任务占用,本任务得不到立即执行。

我们平常娱乐办公用的都是非实时的操作系统,那么什么时候该使用实时操作系统呢?试想一下,一个射出的导弹如果要执行一个调整姿态的任务,这个时候刚好有其他无关紧要的任务在执行,如果是非实时操作系统,那么可能会等一会儿然后弹个窗告诉你应用程序无响应(如果它有窗可弹的话),那完了等弹窗出来导弹都射到外太空去了!毫无疑问这种高优先级任务片刻都不能等的设备就必须上实时操作系统,如果你不想你的导弹射到外太空去的话。

1、FreeRTOS 操作系统功能     作为一个轻量级的操作系统,FreeRTOS 提供的功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能等,可基本满足较小系统的需要。FreeRTOS 内核支持优先级调度算法,每个任务可根据重要程度的不同被赋予一定的优先级,CPU 总是让处于就绪态的、优先级最高的任务先运行。FreeRT0S 内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享 CPU 的使用时间。    

FreeRTOS 的内核可根据用户需要设置为可剥夺型内核或不可剥夺型内核。当 FreeRTOS 被设置为可剥夺型内核时,处于就绪态的高优先级任务能剥夺低优先级任务的 CPU 使用权,这样可保证系统满足实时性的要求;当 FreeRTOS 被设置为不可剥夺型内核时,处于就绪态的高优先级任务只有等当前运行任务主动释放 CPU 的使用权后才能获得运行,这样可提高 CPU 的运行效率。

  

2、FreeRTOS 操作系统的原理与实现

2.1 任务调度机制的实现     任务调度机制是嵌入式实时操作系统的一个重要概念,也是其核心技术。对于可剥夺型内核,优先级高的任务一旦就绪就能剥夺优先级较低任务的 CPU 使用权,提高了系统的实时响应能力。

FreeRTOS 支持的调度方式

FreeRTOS 操作系统支持三种调度方式:抢占式调度,时间片调度和合作式调度。 实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。

 抢占式调度 每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数, 比如 vTaskDelay。

 时间片调度 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如 vTaskDelay,才会执行同优先级任务之间的任务切换。

什么是调度器

简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的

特性:  调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起)。  调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。 当前正在执行的任务是运 行态的任务。  不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。

嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。下面我们主要了解一下抢占式调度器和时间片调度器。

抢占式调度器基本概念

在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和 LCD 显示。电机比键盘和 LCD 需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。

如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。 比如,当一个运行着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权并运行。 又比如,如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。

使用抢占式调度器,使得最高优先级的任务什么时候可以得到 CPU 的控制权并运行是可知的,同时使得任务级响应时间得以最优化。

总的来说,学习抢占式调度要掌握的最关键一点是:每个任务都被分配了不同的优先级,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任务。

FreeRTOS 抢占式调度器的实现

如果用户在 FreeRTOS 的配置文件 FreeRTOSConfig.h 中禁止使用时间片调度, 那么每个任务必须配置不同的优先级。当 FreeRTOS 多任务启动执行后,基本会按照如下的方式去执行:

 首先执行的最高优先级的任务 Task1, Task1 会一直运行直到遇到系统阻塞式的 API 函数,比如延迟,事件标志等待,信号量等待,Task1 任务会被挂起,也就是释放 CPU 的执行权,让低优先级的任务得到执行。  FreeRTOS 操作系统继续执行任务就绪列表中下一个最高优先级的任务 Task2,Task2 执行过程中有两种情况:  Task1 由于延迟时间到, 接收到信号量消息等方面的原因, 使得 Task1 从挂起状态恢复到就绪态,在抢占式调度器的作用下,Task2 的执行会被 Task1 抢占。  Task2 会一直运行直到遇到系统阻塞式的 API 函数,比如延迟,事件标志等待,信号量等待, Task2 任务会被挂起,继而执行就绪列表中下一个最高优先级的任务。  如果用户创建了多个任务并且采用抢占式调度器的话,基本都是按照上面两条来执行。 根据抢占式调度器,当前的任务要么被高优先级任务抢占,要么通过调用阻塞式 API 来释放 CPU 使用权让低优先级任务执行,没有用户任务执行时就执行空闲任务。

运行条件:  这里仅对抢占式调度进行说明。  创建 3 个任务 Task1,Task2 和 Task3。  Task1 的优先级为 1,Task2 的优先级为 2,Task3 的优先级为 3。 FreeRTOS 操作系统是设置的数值越小任务优先级越低,故 Task3 的优先级最高,Task1 的优先级最低。  此框图是 FreeRTOS 操作系统运行过程中的一部分。

运行过程描述如下:

 此时任务 Task1 在运行中,运行过程中由于 Task2 就绪,在抢占式调度器的作用下任务 Task2 抢占 Task1 的执行。 Task2 进入到运行态,Task1 由运行态进入到就绪态。  任务 Task2 在运行中,运行过程中由于 Task3 就绪,在抢占式调度器的作用下任务 Task3 抢占 Task2 的执行。 Task3 进入到运行态,Task2 由运行态进入到就绪态。  任务 Task3 运行过程中调用了阻塞式 API 函数,比如 vTaskDelay,任务 Task3 被挂起,在抢占式调度器的作用下查找到下一个要执行的最高优先级任务是 Task2,任务 Task2 由就绪态进入到运行态。  任务 Task2 在运行中,运行过程中由于 Task3 再次就绪,在抢占式调度器的作用下任务 Task3 抢占 Task2 的执行。 Task3 进入到运行态,Task2 由运行态进入到就绪态。

上面就是一个简单的不同优先级任务通过抢占式调度进行任务调度和任务切换的过程。

时间片调度器基本概念

在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-robin 调度算法。这种调度算法可以用于抢占式或者合作式的多任务中。另外,时间片调度适合用于不要求任务实时响应的情况。

实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。

FreeRTOS 时间片调度器的实现

在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度,另外还需要用户在 FreeRTOSConfig.h 文件中使能宏定义:

#define configUSE_TIME_SLICING 1 默认情况下,此宏定义已经在 FreeRTOS.h 文件里面使能了,用户可以不用在 FreeRTOSConfig.h 文件中再单独使能。

下面我们通过如下的框图来说明一下时间片调度在 FreeRTOS 中的运行过程,让大家有一个形象的认识。

运行条件:  这里仅对时间片调度进行说明。  创建 4 个同优先级任务 Task1,Task2,Task3 和 Task4。  每个任务分配的时间片大小是 5 个系统时钟节拍。 运行过程描述如下:  先运行任务 Task1,运行够 5 个系统时钟节拍后,通过时间片调度切换到任务 Task2。  任务 Task2 运行够 5 个系统时钟节拍后,通过时间片调度切换到任务 Task3。  任务 Task3 在运行期间调用了阻塞式 API 函数,调用函数时,虽然 5 个系统时钟节拍的时间片大小还没有用完,此时依然会通过时间片调度切换到下一个任务 Task4。 (注意,没有用完的时间片不会再使用,下次任务 Task3 得到执行还是按照 5 个系统时钟节拍运行)  任务 Task4 运行够 5 个系统时钟节拍后,通过时间片调度切换到任务 Task1。

上面就是一个简单的同优先级任务通过时间片调度进行任务调度和任务切换的过程。

总结:

时间片调度和抢占式调度我们一般都是开启了的,这样优先级相同时,使用时间片调度,优先级不同时,使用抢占式调度。默认情况下,在 freertos.h 中使能了时间片调度:

而抢占式调度需要我们用户自己开启,一般在 freertosconfig.h 中使能:

不同于μC/OS-II,FreeRTOS 对系统任务的数量没有限制,既支持优先级调度算法也支持轮换调度算法,因此 FreeRTOS 采用双向链表而不是采用查任务就绪表的方法来进行任务调度。系统定义的链表和链表节点数据结构如下所示:

  

FreeRTOS 定义就绪任务链表数组为 xList pxReady—TasksLists[portMAX_PRIORITIES]。其中 portMAX_PRIORITIES 为系统定义的最大优先级。若想使优先级为 n 的任务进入就绪态,需要把此任务对应的 TCB 中的结点 xGenericListltem 插入到链表 pxReadyTasksLiStS[n]中,还要把 xGenericListItem 中的 pvContainer 指向 pxReadyTasksLists[n]方可实现。    

当进行任务调度时,调度算法首先实现优先级调度。系统按照优先级从高到低的顺序从就绪任务链表数组中寻找 usNumberOfItems 第一个不为 0 的优先级,此优先级即为当前最高就绪优先级,据此实现优先级调度。若此优先级下只有一个就绪任务,则此就绪任务进入运行态;若此优先级下有多个就绪任务,则需采用轮换调度算法实现多任务轮流执行。    

若在优先级 n 下执行轮换调度算法,系统先通过执行(pxReadyTasksLists[n])→pxIndex=( pxReadyTasks-Lists[n ]) → pxlndex→pxNext 语句得到当前结点所指向的下一个结点,再通过此结点的 pvOwner 指针得到对应的任务控制块,最后使此任务控制块对应的任务进入运行态。由此可见,在 FreeRTOS 中,相同优先级任务之间的切换时间为一个时钟节拍周期。    

以图 1 为例,设系统的最大任务数为 pottMAX_PRIORITIES,在某一时刻进行任务调度时,得到 pxReadyTasksLists[i].usNumberOfItems=O(i=2...portMAX_PRIORITIES)以及 pxReadyTasksLists[1]。usNumberOfItems=3。由此内核可知当前最高就绪优先级为 l,且此优先级下已有三个任务已进入就绪态.由于最高就绪优先级下有多个就绪任务,系统需执行轮换调度算法实现任务切换;通过指针 pxlndex 可知任务 l 为当前任务,而任务 l 的 pxNext 结点指向任务 2,因此系统把 pxIndex 指向任务 2 并执行任务 2 来实现任务调度。当下一个时钟节拍到来时,若最高就绪优先级仍为 1,由图可见,系统会把 pxIndex 指向任务 3 并执行任务 3。

图 1 任务调度示意

为了加快任务调度的速度,FrecRTOS 通过变量 ucTopReadyPriotity 跟踪当前就绪的最高优先级。当把一个任务加入就绪链表时,如果此任务的优先级高于 ucTopReadyPriority,则把这个任务的优先级赋予 ucTopReadyPriority。这样当进行优先级调度时,调度算法不是从 portMAX_PRIORITIES 而是从 ucTopReady-Priority 开始搜索。这就加快了搜索的速度,同时缩短了内核关断时间。    

为了加快任务调度的速度,FrecRTOS 通过变量 ucTopReadyPriotity 跟踪当前就绪的最高优先级。当把一个任务加入就绪链表时,如果此任务的优先级高于 ucTopReadyPriority,则把这个任务的优先级赋予 ucTopReadyPriority。这样当进行优先级调度时,调度算法不是从 portMAX_PRIORITIES 而是从 ucTopReady-Priority 开始搜索。这就加快了搜索的速度,同时缩短了内核关断时间。    

2.3 时间管理的实现

FreeRTOS 提供的典型时间管理函数是 vTaskDelay(),调用此函数可以实现将任务延时一段特定时间的功能。在 FreeRT0S 中,若一个任务要延时 xTicksToDelay 个时钟节拍,系统内核会把当前系统已运行的时钟节拍总数(定义为 xTickCount,32 位长度)加上 xTicksToDelay 得到任务下次唤醒时的时钟节拍数 xTimeToWake。然后,内核把此任务的任务控制块从就绪链表中删除,把 xTimeToWake 作为结点值赋予任务的 xItemValue,再根据 xTimeToWake 的值把任务控制块按照顺序插入不同的链表。若 xTimeToWake > xTickCount,即计算中没有出现溢出,内核把任务控制块插入到 pxDelayedTaskList 链表;若 xTimeToWak e< xTickCount,即在计算过程中出现溢出,内核把任务控制块插入到 pxOverflowDelayed-Taskust 链表。

    每发生一个时钟节拍,内核就会把当前的 xTick-Count 加 1。若 xTickCount 的结果为 0,即发生溢出,内核会把 pxOverflowDelayedTaskList 作为当前链表;否则,内核把 pxDelaycdTaskList 作为当前链表。内核依次比较 xTickCotlrtt 和链表各个结点的 xTimcToWake。若 xTick-Count 等于或大于 xTimeToWake,说明延时时间已到,应该把任务从等待链表中删除,加入就绪链表。    

由此可见,不同于μC/OS—II,FreeRTOS 采用“加”的方式实现时间管理。其优点是时间节拍函数的执行时间与任务数量基本无关,而μC/OS—II 的 OSTimcTick()的执行时间正比于应用程序中建立的任务数。因此当任务较多时,FreeRTOS 采用的时间管理方式能有效加快时钟节拍中断程序的执行速度。        

2.4 内存分配策略     每当任务、队列和信号量创建的时候,FreeRTOS 要求分配一定的 RAM。虽然采用 malloc()和 free()函数可以实现申请和释放内存的功能,但这两个函数存在以下缺点:并不是在所有的嵌入式系统中都可用,要占用不定的程序空间,可重人性欠缺以及执行时间具有不可确定性。为此,除了可采用 malloc()和 free()函数外,FreeRTOS 还提供了另外两种内存分配的策略,用户可以根据实际需要选择不同的内存分配策略。    

第 1 种方法是,按照需求内存的大小简单地把一大块内存分割为若干小块,每个小块的大小对应于所需求内存的大小。这样做的好处是比较简单,执行时间可严格确定,适用于任务和队列全部创建完毕后再进行内核调度的系统;这样做的缺点是,由于内存不能有效释放,系统运行时应用程序并不能实现删除任务或队列。        

第 2 种方法是,采用链表分配内存,可实现动态的创建、删除任务或队列。系统根据空闲内存块的大小按从小到大的顺序组织空闲内存链表。当应用程序申请一块内存时,系统根据申请内存的大小按顺序搜索空闲内存链表,找到满足申请内存要求的最小空闲内存块。为了提高内存的使用效率,在空闲内存块比申请内存大的情况下,系统会把此空闲内存块一分为二。一块用于满足申请内存的要求,一块作为新的空闲内存块插入到链表中。        

2.3 时间管理的实现

FreeRTOS 提供的典型时间管理函数是 vTaskDelay(),调用此函数可以实现将任务延时一段特定时间的功能。在 FreeRT0S 中,若一个任务要延时 xTicksToDelay 个时钟节拍,系统内核会把当前系统已运行的时钟节拍总数(定义为 xTickCount,32 位长度)加上 xTicksToDelay 得到任务下次唤醒时的时钟节拍数 xTimeToWake。然后,内核把此任务的任务控制块从就绪链表中删除,把 xTimeToWake 作为结点值赋予任务的 xItemValue,再根据 xTimeToWake 的值把任务控制块按照顺序插入不同的链表。若 xTimeToWake > xTickCount,即计算中没有出现溢出,内核把任务控制块插入到 pxDelayedTaskList 链表;若 xTimeToWak e< xTickCount,即在计算过程中出现溢出,内核把任务控制块插入到 pxOverflowDelayed-Taskust 链表。      

每发生一个时钟节拍,内核就会把当前的 xTick-Count 加 1。若 xTickCount 的结果为 0,即发生溢出,内核会把 pxOverflowDelayedTaskList 作为当前链表;否则,内核把 pxDelaycdTaskList 作为当前链表。内核依次比较 xTickCotlrtt 和链表各个结点的 xTimcToWake。若 xTick-Count 等于或大于 xTimeToWake,说明延时时间已到,应该把任务从等待链表中删除,加入就绪链表。        

由此可见,不同于μC/OS—II,FreeRTOS 采用“加”的方式实现时间管理。其优点是时间节拍函数的执行时间与任务数量基本无关,而μC/OS—II 的 OSTimcTick()的执行时间正比于应用程序中建立的任务数。因此当任务较多时,FreeRTOS 采用的时间管理方式能有效加快时钟节拍中断程序的执行速度。        

2.4 内存分配策略

每当任务、队列和信号量创建的时候,FreeRTOS 要求分配一定的 RAM。虽然采用 malloc()和 free()函数可以实现申请和释放内存的功能,但这两个函数存在以下缺点:并不是在所有的嵌入式系统中都可用,要占用不定的程序空间,可重人性欠缺以及执行时间具有不可确定性。为此,除了可采用 malloc()和 free()函数外,FreeRTOS 还提供了另外两种内存分配的策略,用户可以根据实际需要选择不同的内存分配策略。

    第 1 种方法是,按照需求内存的大小简单地把一大块内存分割为若干小块,每个小块的大小对应于所需求内存的大小。这样做的好处是比较简单,执行时间可严格确定,适用于任务和队列全部创建完毕后再进行内核调度的系统;这样做的缺点是,由于内存不能有效释放,系统运行时应用程序并不能实现删除任务或队列。        

第 2 种方法是,采用链表分配内存,可实现动态的创建、删除任务或队列。系统根据空闲内存块的大小按从小到大的顺序组织空闲内存链表。当应用程序申请一块内存时,系统根据申请内存的大小按顺序搜索空闲内存链表,找到满足申请内存要求的最小空闲内存块。为了提高内存的使用效率,在空闲内存块比申请内存大的情况下,系统会把此空闲内存块一分为二。一块用于满足申请内存的要求,一块作为新的空闲内存块插入到链表中。

   

下面以图 2 为例介绍方法 2 的实现。假定用于动态分配的 RAM 共有 8KB,系统首先初始化空闲内存块链表,把 8KB RAM 全部作为一个空闲内存块。当应用程序分别申请 1KB 和 2KB 内存后,空闲内存块的大小变为 5KB3。2KB 的内存使用完毕后,系统需要把 2KB 插入到现有的空闲内存块链表。由于 2 KB



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3