FreeRTOS学习笔记02

FreeRTOS

FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。

FreeRTOS是为小型嵌入式系统设计的可裁剪实时内核。其主要特点有:

  • 调度器支持抢占式调度,协助式调度,或者两者混合。时间片可选
  • 占用空间小,简单,易用

FreeRTOS的源码结构

在FreeRTOS v9.0版本中,FreeRTOS的代码主要包含两个文件夹:FreeRTOS_CORE和FreeRTOS_PORTABLE。这两个文件夹下包含多个.C文件。

  • port.c: 针对不同硬件平台的接口,定义与硬件接口相关的代码
  • heap_4.c: 内存管理相关
  • croutine.c:协程相关
  • event_groups.c:事件标志组相关
  • list.c:管理系统实际会应用到list,是FreeRTOS的一种基础数据结构
  • tasks.c:任务创建、挂起、恢复、调度相关
  • timers.c:软件定时器相关
  • queue.c:管理tasks之间的通信(message queue的概念)
  • FreeRTOSConfig.h:宏定义,配置RTOS所需要的资源

FreeRTOS的任务间通信机制

裸机编程中,一个复杂的功能通常需要多个子函数来实现,不同的子函数之间的通常采用一些全局变量来实现联系。在RTOS中,我们不仅可以使用全局变量,还可以采用系统自带的任务间通信机制。这种机制更加受推荐。其原因是:

  1. 阻塞等待机制比轮询等待更加高效:全局变量当用作某种事件的标志是,获取该标志的任务需要轮询检测标志位的状态是否变化。这样会产生大量的无效判断。如果使用任务间通信阻塞等待的机制,CPU可以转而处理其他事情,当标志变化时解除阻塞。又可以及时执行后续的处理。
  2. 全局变量会产生不可重入函数导致逻辑混乱:RTOS运行时,CPU需要调用不同的函数,如果全局变量使用不恰当,会导致原本设计的逻辑产生混乱。比如某个低优先级任务正在访问某个公共函数,并对函数中的全局变量进行了修改。还未退出函数时,更高优先级的任务抢占了CPU的使用权,并对该函数的全局变量进行了修改。此时低优先级任务若认为自己修改变量成功,执行后续逻辑时,就会发生错误。

FreeRTOS任务间的通信方式:

  • 信号量(Semaphore): 用于任务间的同步,一个任务以阻塞方式灯带另一个任务释放信号量。
  • 互斥量(Mutex):用于任务间共享资源的互斥访问,使用前获取锁,使用后释放锁。
  • 事件标志组(EventGroup): 用于任务间的同步,相比信号量,事件标志组可以等待多个事件的发生。
  • 消息队列(Queue): 类比全局数据,它可以一次发送多个数据(一般将数据定义成结构体发送),每次数据的大小固定不变。
  • 流缓冲区(SteamBuffer):在队列的基础上,优化的一种更加适合的数据结构,可以一次写入任意数量的字节,并且可以一次读取任意数量的字节。
  • 消息缓冲区(MessageBuffer):在流式缓冲区的基础上实现的,可以对消息进行设计改进。每一条消息的写入增加了一个字节用来表示该消息的长度,读取时需要至少一次性读出一条消息,否则会返回0.
  • 任务通知(Notify): 不同于上面的任务通信方式(使用某种通信对象,通信对象是独立于任务的实体,有单独的存储空间,可以实现数据的传递和较复杂的同步,互斥功能), 通知是发向一个指定的任务的,直接改变该任务 TCB的某些变量。

FreeRTOS队列

在实际应用中,一个任务或中断服务函数经常需要和另一个任务进行消息传递。裸机情况下通常通过全局变量实现。但是操作系统使用全局变量的方式会涉及“资源管理”问题。FreeRTOS中采用队列机制完成任务与任务,和任务与中断之间的消息传递。 队列可以存储有限的,大小固定的数据项目。

内存管理

FreeRTOS创建任务、队列、信号量有两种方法:

第一种是由用户自行定义所需的RAM,这种方法也叫静态的方法。静态方法的函数一般由Static结尾,比如任务创建 xTaskCreateStatic()。 使用此函数创建任务的时候需要用户定义任务堆栈。

另一种是动态的申请所需的RAM,使用动态内存管理时,FreeRTOS内核在创建任务、队列、信号量的时候会动态的申请RAM。C语言标准库的malloc()和free()也可以实现动态内存管理,但这种方法在小型嵌入式系统中效率不高,会占用很多代码空间,并且他们的线程不是安全的,程序执行的时间也是不确定的,此外还会导致内存碎片。因此在FreeRTOS中,内核采用 pvPortMalloc()代替 malloc() 申请内存,采用 vPortFree()函数释放内存。关于内存分配,FreeRTOS提供了5这种内存分配的方法: 也就是在5个.c文件,heap_1.c, heap_2.c, heap_3.c, heap_4.c, heap_5.c。

内存碎片:
内存分配与管理的方法中需要解决的问题之一就是内存碎片,其产生过程如下图所示,一个新的内存堆被系统按照应用需求分成多个大小不同的内存块。应用在使用完内存后就会进行释放,同时新的应用产生也需要分配新的可用该内存。经过多次申请和释放后,内存块被不断地分割,导致内存中存在大量的很小的内存块。这些内存块太小导致大多数应用无法使用,因此就形成了内存碎片。这些内存碎片的不断增加会导致实际可用内存越来越少。最终应用程序因为分配不到合适的内存而崩溃。而FreeRTOS的heap_4.c就提供了一个解决内存碎片的方法,即将内存碎片进行合并组成一个新的可用的大内存块。

heap_1.c简介
动态内存分配需要一个内存堆,在FreeRTOS中的内存堆为ucHeap[],大小为configTOTAL_HEAP_SIZE.

heap_1特性如下:

  1. 使用一旦创建好任务,信号量和队列就再也不会删除的应用,实际上大多数的FreeRTOS的应用都是这样的。
  2. 具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。
  3. 代码实现和内存分配的过程都非常简单,内存是从一个静态的数组中分配的,也就是适合与那些不需要动态分配内存的应用。

heap_2.c简介

heap_3.c简介

heap_4.c简介
heap_4提供了一个最优的内存分配方法,不像heap_2, heap_4会将内存中的碎片合并成一个大的可用内存块,他提供了内存合并算法。内存堆为ucHeap[], 大小同样为configTOTAL_HEAP_SIZE。可以通过函数xPortGetFreeHeapSize()获取剩余内存大小。

heap_4特性如下:

  1. 可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等应用中。
  2. 不会像heap_2那样产生严重的内存分配碎片,即使分配的内存大小是随机的。
  3. 具有不确定性,但是远比C标准库的malloc()和free()效率高。

heap_5简介
heap_5使用了heap_4相同的合并算法,内存管理实现基本相同,但是heap_5允许内存堆跨越多个不连续的内存段。如果使用heap_5需要调用函数xPortDefineHeapRegions()来对内存堆做初始化处理,该函数执行完之前禁止调用任何会调用pvPortMalloc()的函数。

参考资料:
[1] FreeRTOS源码探析之——任务调度相关

作者

John Doe

发布于

2021-05-09

更新于

2021-05-10

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论