FreeRTOS系列教程(一):内核关键概念

目录

一. 前言

二. 内核关键概念

2.1  链表与节点

2.2 任务

2.3 堆与栈

2.4 临界段

2.5 阻塞延时

2.6 优先级

2.7 时间片

三. 小结


一. 前言

在上一篇文章中,我们论述了学习FreeRTOS操作系统的重要意义,链接如下。相信看到这一节的同学,都已经抱着深入了解FreeRTOS的信念,为大家的坚持点赞!

FreeRTOS系列教程(前言):为什么要用FreeRTOS?icon-default.png?t=N7T8https://blog.csdn.net/weixin_42434952/article/details/137378466?spm=1001.2014.3001.5502

本文介绍了FreeRTOS内核的一些关键概念,有助于我们后续在自己的项目中使用FreeRTOS的时候,能更得心应手。这些概念在初次理解的时候可能有些晦涩难懂,我尽量用一些形象的语言给大家展示出来。学有余力的同学可以看一些FreeRTOS内核源码,源码虽然更晦涩,但非常有助于对内核运行架构的了解。

二. 内核关键概念

2.1  链表与节点

链表与节点的解释是:

节点(Node):在链表中,每一个节点通常包含两部分的信息:数据域和指针域。数据域用于存储数据,指针域用于存储指向不同节点的指针。

链表(Linked List):链表是由节点按照一定的顺序链接在一起。

乍一听有些难懂,我们可以这样理解,链表就好比一个圆形的晾衣架,晾衣架上面有好多我们平时用来夹衣服的夹子,这些夹子就是链表上的节点。通过夹子,我们就可以方便的在链表上挂很多数据。然后每个夹子上,不仅能挂数据,还标记了夹子自身的编号及其前一个夹子或后一个夹子的位置信息。这样的话,比如我们要把一只袜子挂在晾衣架的第5个夹子上,就可以很快找到编号为5的夹子。对于数据也是,通过指针指向链表里面的不同节点,就可以方便的把数据放在不同的节点上。

如下图所示即为一个List_Test的双向链表,这个链表(晾衣架)上挂了三个节点(夹子),分别是List_Item1,List_Item2和List_Item3。

链表作为 C 语言中一种基础的数据结构,在平时写程序的时候用的并不多,但在操作系统里面使用的非常多。链表分为单向链表和双向链表,单向链表很少用,使用最多的还是双向链表。双向链表与单向链表的区别就是节点中有两个节点指针,分别指向前后两个节点,其它完全一样。 FreeRTOS中使用的是双向链表。

链表的主要优点是插入和删除元素时,不需要移动和重排其他元素,只需要修改相应的指针即可。这使得链表在处理大量数据并且需要频繁插入和删除操作时非常高效。当然链表也有缺点,其主要缺点是访问元素时需要从头节点开始,逐个遍历链表直到找到目标元素,这在链表很长时,可能会很慢。

在很多公司的嵌入式面试中,会问到链表和数组的区别。敲黑板划重点,下面的回答是满分答案:在内存分配上,链表的节点可以分散在内存中的任何位置,只要节点之间的指针正确,就可以正确访问整个链表。链表的大小可以在运行时动态改变。而数组在创建时,必须分配一块连续的内存空间来存储所有元素。数组的大小在创建后就固定了,不能再改变。同时,在插入和删除操作时,数组在插入或删除元素时,可能需要移动大量元素来填补空位或者创建空位,这使得数组在需要频繁插入和删除操作时可能会很慢。而链表只需改变相应节点的指针,就可以在链表的任意位置插入或删除节点,这使得链表在需要频繁插入和删除操作时非常高效。

2.2 任务

任务这个概念并不陌生。学过裸机编程的伙伴都知道,在裸机系统中,任务就是main函数里面的无限循环任务,相当于这里面只有一个任务主体。而FreeRTOS操作系统是一个多任务操作系统,根据不同需要实现的功能,我们可以把整个系统分割成多个任务,且每个任务都是一个无限循环且无法返回的函数。这个时候大家可能会有疑问,单片机本身只有一个CPU核心,每个时钟周期只能执行一个指令,那是如何实现多任务操作的呢?这就归功于FreeRTOS操作系统的任务调度机制的优势,调度器可以在每次系统时钟中断后进行任务调度,然后根据任务的优先级和状态,决定下一个要执行的任务。当一个任务正在执行时,如果有更高优先级的任务变为就绪状态,调度器将保存当前任务的状态,然后加载并执行新的任务。当新的任务完成或被阻塞时,调度器再次保存其状态,然后加载并恢复之前的任务。通过这种方式,虽然单片机在任何时刻都只在执行一个任务,但由于任务切换的速度非常快,它可以看起来像是在同时执行多个任务。

下图演示了使用“基于时间片的固定优先级抢占式调度”算法抢占调度任务的调度过程,大部分FreeRTOS操作系统都使用这种任务调度策略。

这里面,task1是最高优先级的任务,task2是中等优先级的任务,task3是最低优先级的任务,Idle task是空闲任务。

系统启动后,task1的时间还没到,运行task2,等task2进入阻塞态后运行空闲任务,当task3的事件到达就会抢占空闲任务,task3运行期间,如果task2的周期到了,因为task2优先级高就会抢占task3,task2运行期间,由于task1优先级高,一旦task1等待的事件到了就会抢占task2,如此往复。

如果task1和task2的优先级相同呢?在其他任务不干扰的情况下,那么系统就会在每个时钟tick,往复的切换task1和task2,让这两个任务轮流执行,雨露均沾。

2.3 堆与栈

堆与栈的概念很容易造成困惑,在裸机编程中使用的不多所以对工程影响不大。但在FreeRTOS操作系统中,我们需要给每个任务定义栈空间,就需要对堆与栈有一个清晰的了解。

首先,栈是程序运行时用于存储局部变量、函数参数和返回地址的内存区域。栈的主要特点是后进先出,即最后进入栈的元素会被首先取出。栈上的内存分配和释放由编译器自动管理,当函数调用结束时,其在栈上的局部变量会自动被释放。

在裸机编程时,在startup启动文件中可以看到默认配置的系统栈空间,一般不需要更改。如果我们在裸机编程时函数嵌套太多,导致栈需要保存多个函数参数与返回地址,栈空间就可能不够用,需要进行适当调整。在FreeRTOS多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空间可以是一个预先定义好的全局变量, 也可以是动态分配的一段内存空间,但它们都存在于 RAM 中而不是裸机中存储于系统栈空间。在发生任务切换时,CPU首先会把当前任务的栈指针、程序计数器、以及其他重要的CPU寄存器比如R0到R12通用寄存器的值推入(push)到当前任务的栈中。然后在新任务的栈中,通过弹出(pop)之前保存的值来恢复新任务的栈指针、程序计数器、以及其他重要的CPU寄存器的值。然后,新任务就可以从上次停止的地方继续执行。

那堆是什么呢?同样作为内存区域,堆空间则用于存储动态分配的对象和复杂数据结构。堆空间一般较大,而且堆的生命周期贯穿整个程序运行期间,所以需要由程序员手动进行分配和释放,比如通常我们习惯用的malloc和free函数,都是堆对空间进行操作,这里面需要注意,如果使用malloc函数动态分配内存用完之后忘记使用free函数释放内存,则会导致内存泄漏,引发程序运行灾难。

嵌入式实时操作系统中,内存是十分有限而且珍贵的,再加上对分配内存的时间必须是确定的,所以FreeRTOS 有针对性的提供了不同的内存分配管理算法,通过对内存的申请、释放操作,来管理用户和系统对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统可能产生的内存碎片问题。

FreeRTOS中有两种任务创建方式,一种是使用静态内存创建任务,相当于全局变量,在编译时分配,其大小在编译时确定,且在程序运行期间不会改变,然后在任务的上下文中,然后在这块内存里存放任务栈空间;还有一种是使用动态内存创建任务,这就需要用到堆,也就是堆内存来进行动态分配,虽然这个任务的内存块是从堆上分配的,但是在这个任务的上下文中,这个内存块被当作栈来使用。不论使用静态内存创建任务,或者使用动态内存即堆内存创建任务,就像我们租了一块土地(堆内存或者静态内存),在上面建了一座楼(任务栈),虽然这块土地是租来的(属于堆内存或者静态内存),但是我们进行的办公活动都是在自己的楼里面(任务使用栈内存方式)。

如下图所示则为使用动态内存创建两个任务后的RAM占用情况。

2.4 临界段

临界段就是一段在执行的时候不能被中断的代码段。那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中断。在FreeRTOS,系统调度最终也是产生 PendSV 中断,在 PendSV Handler 里面实现任务的切换,所以最终可以归结为中引起。当多个中断对同一个全局变量进行读写时,如果没有适当的保护机制,就可能会出现数据不一致的问题。

加入临界段保护之后,在执行某个代码段之前先关中断,执行完之后再打开中断,这样就可以确保数据的一致性。其中,开关中断函数又可以分为带返回值和不带返回值的。带返回值的开关中断函数可以在关中断时返回原来中断状态并在执行完临界段打开中断的时候恢复原始中断状态,可用于中断函数之中。而不带返回值的开关中断函数只是关闭或打开全局中断,不返回任何信息,不能用于中断函数之中。

2.5 阻塞延时

我们在裸机运行延时函数的时候,任务就会陷入死等状态,在延时时间内,CPU不能做任何其他的工作,严重影响其他任务的执行。而在FreeRTOS操作系统中就很好的解决了这个问题,在任务中可以大胆放心的使用阻塞延时函数,在调用该延时函数后, 任务会被剥离 CPU 使用权,然后进入阻塞状态,而CPU不会陷入死等,它会转头去执行其他任务。

在每个SysTick时基中断,系统都会扫描一遍,看所有任务的延时时间有没有到,如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务,直到延时结束,任务才可以申请获得CPU 使用权。这里面需要注意的是,空闲任务启动后不能阻塞,因为系统至少得保证有一个任务可以运行。

2.6 优先级

在阻塞延时的概念中我们讲到,在每个SysTick时基中断,系统都会扫描一遍,看所有任务的延时时间有没有到,那么问题来了,假设系统有两个任务,且这两个任务的延时同一时刻到,那么该执行哪个任务呢?这时候就体现出优先级的作用。在FreeRTOS中,优先级在我们创建任务的时候就已经定义好,数字越大代表优先级越高。空闲任务的优先级最低,为0。这样,在任务切换产生PendSV中断后,会寻找这两个任务中优先级最高的就绪任务开始执行,当优先级最高的任务进入阻塞状态后,才会执行另一个任务。

2.7 时间片

接着上面的对于优先级的论述,我们再深入一点。仍然假设系统有两个任务,这两个任务的延时同一时刻到,且优先级相同,那么该执行哪个任务呢?这种情况FreeRTOS也已经替我们想好了。这就引出了时间片的概念,所谓时间片就是同一个优先级下可以有多个任务,每个任务轮流地享有相同的 CPU 时间,在 FreeRTOS 中,时间片固定为一个 tick,即 SysTick 的中断周期。所以,当这两个任务都处于就绪态且优先级相同的时候,系统会在每个tick轮询的执行。

三. 小结

FreeRTOS 内核的这些概念,其中我们在裸机编程的时候也有碰到过,只是在FreeRTOS 中给这些概念赋予了独特的特点,带着对这些概念的理解,接下来我们就进入FreeRTOS的应用开发,下一篇文章将会介绍在FreeRTOS 中进行任务管理,敬请期待!

原创不易,欢迎交流。大家可以关注我的微信公众号,里面不定期会有干货知识分享和项目实战经验总结。

  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值