#创作者激励# 高能预警,一篇5k字长文带你认识RTOS系统 原创 精华
【本文正在参加2023年第一期优质创作者激励计划】
在学习openharmony南向开发的时候,很多小伙伴不明白学习liteos-m内核编程代表什么,是什么意思,以及为什么要学习这个,今天以rtos的角度为新手扫盲.
什么是LITEOS-M?
根据openharmony仓库的解释,文档地址:
OpenHarmony LiteOS-M内核是面向IoT领域构建的轻量级物联网操作系统内核,具有小体积、低功耗、高性能的特点,其代码结构简单,主要包括内核最小功能集、内核抽象层、可选组件以及工程目录等,分为硬件相关层以及硬件无关层,硬件相关层提供统一的HAL(Hardware Abstraction Layer)接口,提升硬件易适配性,不同编译工具链和芯片架构的组合分类,满足AIoT类型丰富的硬件和编译工具链的拓展。
LiteOS-M内核就是一款RTOS,关于RTOS内核是这样描述的:
实时操作系统(Real-time operating system, RTOS),又称即时操作系统,它会按照排序运行、管理系统资源,并为开发应用程序提供一致的基础。
实时操作系统与一般的操作系统相比,最大的特色就是“实时性”,如果有一个任务需要执行,实时操作系统会马上(在较短时间内)执行该任务,不会有较长的延时。这种特性保证了各个任务的及时执行。
RTOS有什么特点?
维基百科上关于实时性的定义,地址
实时运算(Real-time computing)是计算机科学中对受到“实时约束”的计算机硬件和计算机软件系统的研究,实时约束像是从事件发生到系统回应之间的最长时间限制。实时程序必须保证在严格的时间限制内响应。
实时操作系统中都要包含一个实时任务调度器,这个任务调度器与其它操作系统的最大不同是强调:严格按照优先级来分配CPU时间,并且时间片轮转不是实时调度器的一个必选项。
提出实时操作系统的概念,可以至少解决两个问题:
- 一个是早期的CPU任务切换的开销太大,实时调度器可以避免任务频繁切换导致CPU时间的浪费;
- 另一个是在一些特殊的应用场景中,必须要保证重要的任务优先被执行。
在这样的背景下,实时操作系统就被设计出来了,典型的实时操作系统有VxWorks,RT-Thread,uCOS,QNX,WinCE等。
实时任务调度器是实时操作系统的一个必选项,但不代表只要设计出来一个实时调度器就足够了。事实上设计一个实时调度内核并不是一个多么复杂的任务,实时操作系统的特性是在整个操作系统的设计思路上都要时刻关注实时性。
这些设计思路包括:
为什么要使用RTOS系统?
为什么搞清楚为什么使用RTOS系统,首先来看一下传统的芯片上开发程序是如何的?
#include <stdio.h>
#include .....
//定义一些FALG变量
int flag_a=0,
int flag_b=0;
int timer_count=0;
....
//某个中断程序
int ISRxx_callback()
{
//把某个flag置位
flag_a = 1;
}
//另一个中断程序
int ISRxx_callback()
{
//把某个flag置位
flag_b = 1;
//把某个定时器的计数值赋值
timerCount = 100;
}
int main(){
//初始化某某某
Init_xxx();
Init_yyy();
//运行一个死循环处理事情
while(1){
if(flag_a == 1)
{
//TODO 执行出现a中断
}
if(flag_b == 1)
{
//TODO 执行出现b中断
}
if(timerCount > 0)
{
timerCount--;
if(tiemrCount == 0)
{
//TODO 执行定时器到期的操作
}
}
delayMS(100);系统休眠一定时间
}
}
可以看到,系统靠一个大while循环进行包裹,然后通过判断各种各样的flag或者计数值进行轮询处理,在这种编程框架上,如果我们的产品的功能比较简单,确实可以这样做,而且需要这么做,不建议引入rtos操作系统,因为那会带来额外的资源消耗以及封装.上面这种编程范式业界一般称为前后台系统.
但是在当今物联网时代,我们的产品功能已经比较复杂,而且出现了更多的交互方式比如触摸屏,无线网络数据处理等等,并且在某些情况下产品的响应也需要很快,如果采用这样的while循环的方式无疑就不满足要求了.仔细看,假如出现某个操作在flag_a里面比较耗时,那么整个系统的节奏也会被拉慢,那么这时候就需要一款RTOS系统来进行管理了.
那么我们来看一下通常RTOS系统的编程范式:
#include <stdio.h>
#include .....
....
//某个中断程序
int ISRxx_callback()
{
//发送一个消息
send_message(balabala);
}
//另一个中断程序
int ISRxx_callback()
{
//把某个flag置位
send_event(xx);
//启动某个软件定时器
start_timer(id,100);
}
int thread_aa_callback()
{
while(1)
{
//TODO 阻塞接受某个消息,并可指定超时时间
recv_message()
}
}
int thread_bb_callback()
{
while(1){
//TODO 阻塞等待某个事件,并可指定超时时间
wait_event()
}
}
while(1)
{
//初始化某某某
Init_xxx();
Init_yyy();
//启动线程或任务
start_thread(thread_aa_callback);
start_thread(thread_bb_callback);
}
可以看出在RTOS的编程框架下,不再是一个大的while去进行个种flag的轮询,产品所做的操作被分成了一个个的线程或任务,线程或任务又通过所谓的事件或者消息进行传递数据,整个系统的运行不再由一个flag去区分时间片,而且通过线程间的同步或通信机制去进行高效的配合,所以能够应对物联网时代产品功能日趋丰富,场景越来越复杂的情况.
RTOS这么好,好学吗?
答案:其实rtos系统是不好学的,因为RTOS相比传统的单片机裸机开发方式,多了很多概念,比如线程啊,线程同步啊,互斥锁啊,消息队列啊等等,听起来就很复杂,但是我们学习一定要掌握一定的思路,任何事物或者机制的出现都是有原因的,简单概括一下,RTOS系统间各概念是如下关系:
- 因为要实现RTOS系统,就必须引入多线程或多任务的概念
- 多任务编程比单线程复杂,多线程切换就产生了上下文的保存恢复和资源访问,
- 为了保存上下文就必须有线程栈,
- 在线程栈之外又有全局栈,多线程程序访问全局栈就产生了数据一致性问题,为了解决数据一致性问题就有了线程间同步
- 为了避免过度使用全局栈要让线程间共享资源就又有了线程间通信的方式
- 既然可以多线程了那中断就可以实现快如快出了,中断分为了上下片
可以看出来这主要的知识点都是一环套一环,新手如果不理解这其中的因果关系,而是一章一章的去看教程很容易产生云山雾罩的感觉,下面就几个主要知识点做简单总结,鉴于篇幅和理解深度的原因,有的地方只能是点到为止,有说错地方,还望各位踊跃指正
线程是什么?多线程又是什么?为什么要多线程?
通俗来讲,线程就是任务,系统为了实现多任务就要引入多线程.
那么怎么形容这个任务呢:领导叫你给客户倒杯水可以认为是一个任务,给孩子在网上买一包纸尿裤可以是一个任务,说人话就是事情,那么对应到计算机甚至是我们的嵌入式系统里面,任务就是:点个灯?发送一串串口数据或者是执行一次复位重启也能算一个任务.
那么多线程其实省略了"同时执行"这几个字,为什么多线程,显然是为了执行更多的任务.那有同学要说:以前我们不带系统的时候也可以做到一个板子做多个事情呀.哈哈,别忘了,这里面还有一个实时呢,关于这部分内容可以查看一下关于前后台系统和实时系统的解析
那多线程的程序是怎么运行的?中断是什么线程?
其实在单核芯片内部多线程并不是同时运行的,而是根据系统调度规则保持在某一个时刻只有一个线程在运行,那有同学就问了,什么是调度?我们可以理解为调控和分配,打个比方,春晚有一个小品叫<<装修>>,黄大锤先拿大锤破砖,再拿小锤抠缝,这个从大锤变到小锤就是一个调度,发现墙破的差不多了,停止大锤活动,小锤上场继续.对应到计算机上,就是一个任务执行完了,系统自动将当前任务停止,转而去执行另一个线程.
中断不属于任何一个线程,中断的优先级大于所有的线程,拿手机去对比:哪怕你马上要吃鸡成功或者王者荣耀正在进行激烈的团战,只要有电话进来,你的手机都会把游戏停止,让你决定是否接听电话,因为在手机的设定中,接打电话是最高优先级事件,所有的其他任务都得给这个事件让路.
什么是抢占式优先级调度和时间片轮转调度?
很好理解,抢占式就是某一任务很紧急,必须要打断其他线程的运行,举例:你正在吃饭或者正在走路的时候,突然肚子痛要拉肚子,那这时候吃饭和走路的优先级就没有这个要去上厕所的优先级高,那么我们说:吃饭和走路被上厕所抢占运行.
而时间片轮转调度就更好理解:给某一线程分配一个最大运行时间,时间一到,不管现在的任务进行的如何,如果还有同等优先级的任务要执行,就转去执行这个同等优先级的任务,可以结合下面写作业的例子去理解
再举个不形象的例子:语文课和数学课地位同等重要,所以在学校不会有语文老师去抢数学老师的课时,顶多拖下堂.—>这就是时间片轮转调度,而体育课相比之下数学语文这种"正课"显得重要,所以很多时候数学老师会跟你们说,这节体育课改上数学.–>优先级抢占
调度理解了,那线程栈是什么,上下文又是什么?
打个比方,上学的时候我们学习语文数学英语,老师规定作业要这样写:写一分钟语文,一分钟的数学,然后再去写一分钟英语,这样不段循环,直到所有的作业都写完.好等到你开始写了,先打开语文作业本,找到对应的页码和题目,开始写作业,嘀嘀嘀一分钟很快到了,你不得不把语文作业本合起来,标记一下写到了哪里,然后打开数学作业本,找到对应的页码和题目,开始写作业,嘀嘀嘀一分钟又到了,你又不得不合上数学作业本,把页码和题目标记起来,开始同样的动作去写英语,等到英语的时间到了之后,又得去把语文作业本打开,找到刚刚记录的页码和题目继续写.那么我们想象一下有三个竹筒,里面分别装有语文,数学,英语的作业相关的信息,这个筒子就是线程栈,那这个筒子里记录的页码和题目就是上下文信息.简单说来:上下文用于保存和恢复线程运行的状态和结果等信息,是多线程程序的基石,要实现多任务,就必要有上下文.
线程同步是什么,线程间通信又是什么,他们是干嘛的?
上文说了,每一个线程的栈区和上下文是分别独立存储的.现在有个问题:怎么确定线程执行的顺序,和对资源访问的一致性?就好比不对里有严格的纪律和军衔来限制士兵的活动一样,rtos系统基于优先级来控制线程的运行顺序,那总不能高优先级程序一直在运行吧,低优先级任务得不到执行叫什么多线程.
放心,这些问题早都有办法了,高优先级的任务可以通过调用定时器来达到阻塞当前运行,交出cpu的控制权,当控制权交出后,其他线程得以运行,那么用定时器的阻塞有一个问题:假如线程是要获取某个东西,当时当定时器到期了还没获取到或者说不知道这个东西是不是有效的,那怎么办?
此时,就要用到信号量或者消息队列等线程同步或者线程通信等手段了.
区别在于:线程同步不携带数据,而线程通信携带数据.
举个线程间同步的例子,当某个线程正打算要读取某个变量的值,突然被一个高优先级的线程抢占,这个高优先级的任务正好会改变这个变量的值,然后高优先级线程执行完毕,又轮到这个线程去执行,然后这个变量因为被变掉了,所以可能造成该线程的错误运行,这个时候就需要用到线程同步了,在当前程序在访问或者修改某个变量或者外设的时候,用信号量等手段把操作保护起来,这样当正好发生了高优先级线程抢占式运行的时候,能够提示一下:嘿哥们我还没完事呢,你等会哈,这时候等待还是不等,等多久就由高优先级去选择了.通俗的比方:现在有一个公厕只有一个坑,列兵小王正在蹲坑,这时候团长来了,因为不敢得罪团长,小王不得不让出坑位让团长先上,有了线程同步之后,相当于公厕上加了一个锁,小王上厕所的时候,把门一锁,管你是多大的领导也得给我等着,哈哈
再举个线程间通信的例子:食堂有王姨和张姨,王姨管打饭,张姨管打菜,去食堂吃饭先打饭再打菜,如果不建立起一个良好的线程通信机制,可能就是这样的:张姨不知道王姨现在是在打菜还是等自己的饭,王姨打完一个菜就不知道干嘛了,玩会手机或者跟别人唠唠闲嗑都有可能.或者是张姨不知道王姨打饭速度不够快,使劲装饭把王姨的工作台都占满了,或者张姨打菜慢王姨打完饭老来问饭好了没有(不停去访问某一变量看是否有值),这样的情况显然是不合理的.那么用线程间通信,王姨打完饭就在那边阻塞等饭送过来,张姨装完饭就放在某个地方,等王姨打完一个菜再装下一个,这里面的盘子就相当与消息队列或者邮箱里的一份数据,双方都需要这个数据,然后通过线程间通信机制正确的执行自己的任务.
中断的上半部和下半部又是什么?不带rtos系统的程序怎么没有这说法?
通常在嵌入式编程中,中断的处理需要遵循快进快出的原则,而不带rtos系统因为没有多任务编程的概念,几乎不会采用上半部和下半部编程(某些状态机框架可以实现),通俗来说:上半部就是得到数据,下半部就是处理数据,像这样把数据的产生和数据的处理分成上下两层的方式就叫上半部和下半部,所以要是现在上半部和下半部编程,最好就是采用多线程编程,因为可以让一个线程用消息队列或者邮箱等方式阻塞在数据接收的地方去等待数据过来,在阻塞期间不影响其他线程的运行.
RTOS这么好,一定要学吗,一定要用吗?
前面写了很多字了,那么以这个问题来结尾吧:在当今物联网时代,从事单片机或者嵌入式编程一定要学会一款RTOS系统,而且基于RTOS的这些特性,一个很明显的特点是:会了一款,其他的自然就会了,因为RTOS就那么多东西,不同的RTOS只是API接口长的不一样,在实现机制上也有些许差别,但是思路不统一的,所以,一定要学会RTOS编程,即使在工作中用不上,学习的过程中用来扩展思维也是不一样的,学编程,思维很重要.
本新人表示收货满满
线程部分讲解的很好
从这个角度了解openharmony也很有意思
了解一下啥是即时操作系统
学习最好还是从这种根源来学,后面也更容易掌握