前言:
目前市面上有不少的 rtos 系统, 他们各有特色, 首先声明, 本文并不是基于某一款 rtos 系统, 关于 rtos 系统的介绍的解释网上一大把, 就不去炒剩饭了. 传统的 rtos 系统教程把系统的各个组件拆成几部分, 比如线程邮箱消息队列分开来讲, 个人觉得这种方式对新手不友好, 本文将自己的理解以问答形式梳理一下, 总体的思路如下:
- 多任务编程比单线程复杂, 多线程切换就产生了上下文的保存恢复和资源访问,
- 为了保存上下文就必须有线程栈,
- 在线程栈之外又有全局栈, 多线程程序访问全局栈就产生了数据一致性问题, 为了解决数据一致性问题就有了线程间同步
- 为了避免过度使用全局栈要让线程间共享资源就又有了线程间通信的方式
- 既然可以多线程了那中断就可以实现快如快出了
可以看出来这主要的知识点都是 一环套一环 , 新手如果不理解这其中的 因果关系, 而是一章一章的去看教程很多时候都会看蒙, 下面就几个主要知识点做简单总结, 鉴于篇幅和理解深度的原因, 有的地方只能是点到为止, 有说错地方, 还望各位踊跃指正
1. 线程是什么? 多线程又是什么? 为什么要多线程?
通俗来讲, 线程就是任务, 系统为了实现多任务就要引入多线程.
那么怎么形容这个任务呢: 领导叫你给客户倒杯水可以认为是一个任务, 给孩子在网上买一包纸尿裤可以是一个任务, 说人话就是事情, 那么对应到计算机甚至是我们的嵌入式系统里面, 任务就是: 点个灯? 发送一串串口数据或者是执行一次复位重启也能算一个任务.
那么多线程其实省略了 ” 同时执行 ” 这几个字, 为什么多线程, 显然是为了执行更多的任务. 那有同学要说: 以前我们不带系统的时候也可以做到一个板子做多个事情呀. 哈哈, 别忘了, 这里面还有一个实时呢, 关于这部分内容可以查看一下关于前后台系统和实时系统的解析, 这里就不造轮子了
2. 那多线程的程序是怎么运行的? 中断是什么线程?
其实在单核芯片内部多线程并不是同时运行的, 而是根据系统调度规则保持在某一个时刻只有一个线程在运行, 那有同学就问了, 什么是调度? 我们可以理解为调控和分配, 打个比方, 春晚有一个小品叫 << 装修 >>, 黄大锤先拿大锤破砖, 再拿小锤抠缝, 这个从大锤变到小锤就是一个调度, 发现墙破的差不多了, 停止大锤活动, 小锤上场继续. 对应到计算机上, 就是一个任务执行完了, 系统自动将当前任务停止, 转而去执行另一个线程.
中断不属于任何一个线程, 中断的优先级大于所有的线程, 拿手机去对比: 哪怕你马上要吃鸡成功或者王者荣耀正在进行激烈的团战, 只要有电话进来, 你的手机都会把游戏停止, 让你决定是否接听电话, 因为在手机的设定中, 接打电话是最高优先级事件, 所有的其他任务都得给这个事件让路.
3. 什么是抢占式优先级调度和时间片轮转调度?
很好理解, 抢占式就是某一任务很紧急, 必须要打断其他线程的运行, 举例: 你正在吃饭或者正在走路的时候, 突然肚子痛要拉肚子, 那这时候吃饭和走路的优先级就没有这个要去上厕所的优先级高, 那么我们说: 吃饭和走路被上厕所抢占运行.
而时间片轮转调度就更好理解: 给某一线程分配一个最大运行时间, 时间一到, 不管现在的任务进行的如何, 如果还有同等优先级的任务要执行, 就转去执行这个同等优先级的任务, 可以结合下面写作业的例子去理解
再举个不形象的例子: 语文课和数学课地位同等重要, 所以在学校不会有语文老师去抢数学老师的课时, 顶多拖下堂.—> 这就是时间片轮转调度, 而体育课相比之下数学语文这种 ” 正课 ” 显得重要, 所以很多时候数学老师会跟你们说, 这节体育课改上数学.–> 优先级抢占
4. 调度理解了, 那线程栈是什么, 上下文又是什么?
打个比方, 上学的时候我们学习语文数学英语, 老师规定作业要这样写: 写一分钟语文, 一分钟的数学, 然后再去写一分钟英语, 这样不段循环, 直到所有的作业都写完. 好等到你开始写了, 先打开语文作业本, 找到对应的页码和题目, 开始写作业, 嘀嘀嘀一分钟很快到了, 你不得不把语文作业本合起来, 标记一下写到了哪里, 然后打开数学作业本,, 找到对应的页码和题目, 开始写作业, 嘀嘀嘀一分钟又到了, 你又不得不合上数学作业本, 把页码和题目标记起来, 开始同样的动作去写英语, 等到英语的时间到了之后, 又得去把语文作业本打开, 找到刚刚记录的页码和题目继续写. 那么我们想象一下有三个竹筒, 里面分别装有语文, 数学, 英语的作业相关的信息, 这个筒子就是线程栈, 那这个筒子里记录的页码和题目就是上下文信息. 简单说来: 上下文用于保存和恢复线程运行的状态和结果等信息, 是多线程程序的基石, 要实现多任务, 就必要有上下文.
5. 线程同步是什么, 线程间通信又是什么, 他们是干嘛的?
上文说了, 每一个线程的栈区和上下文是分别独立存储的. 现在有个问题: 怎么确定线程执行的顺序, 和对资源访问的一致性? 就好比不对里有严格的纪律和军衔来限制士兵的活动一样,rtos 系统基于优先级来控制线程的运行顺序, 那总不能高优先级程序一直在运行吧, 低优先级任务得不到执行叫什么多线程.
放心, 这些问题早都有办法了, 高优先级的任务可以通过调用定时器来达到阻塞当前运行, 交出 cpu 的控制权, 当控制权交出后, 其他线程得以运行, 那么用定时器的阻塞有一个问题: 假如线程是要获取某个东西, 当时当定时器到期了还没获取到或者说不知道这个东西是不是有效的, 那怎么办?
此时, 就要用到信号量或者消息队列等线程同步或者线程通信等手段了.
区别在于: 线程同步不携带数据, 而线程通信携带数据.
举个线程间同步的例子, 当某个线程正打算要读取某个变量的值, 突然被一个高优先级的线程抢占, 这个高优先级的任务正好会改变这个变量的值, 然后高优先级线程执行完毕, 又轮到这个线程去执行, 然后这个变量因为被变掉了, 所以可能造成该线程的错误运行, 这个时候就需要用到线程同步了, 在当前程序在访问或者修改某个变量或者外设的时候, 用信号量等手段把操作保护起来, 这样当正好发生了高优先级线程抢占式运行的时候, 能够提示一下: 嘿哥们我还没完事呢, 你等会哈, 这时候等待还是不等, 等多久就由高优先级去选择了. 通俗的比方: 现在有一个公厕只有一个坑, 列兵小王正在蹲坑, 这时候团长来了, 因为不敢得罪团长, 小王不得不让出坑位让团长先上, 有了线程同步之后, 相当于公厕上加了一个锁, 小王上厕所的时候, 把门一锁, 管你是多大的领导也得给我等着, 哈哈
再举个线程间通信的例子: 食堂有王姨和张姨, 王姨管打饭, 张姨管打菜, 去食堂吃饭先打饭再打菜, 如果不建立起一个良好的线程通信机制, 可能就是这样的: 张姨不知道王姨现在是在打菜还是等自己的饭, 王姨打完一个菜就不知道干嘛了, 玩会手机或者跟别人唠唠闲嗑都有可能. 或者是张姨不知道王姨打饭速度不够快, 使劲装饭把王姨的工作台都占满了, 或者张姨打菜慢王姨打完饭老来问饭好了没有(不停去访问某一变量看是否有值), 这样的情况显然是不合理的. 那么用线程间通信, 王姨打完饭就在那边阻塞等饭送过来, 张姨装完饭就放在某个地方, 等王姨打完一个菜再装下一个, 这里面的盘子就相当与消息队列或者邮箱里的一份数据, 双方都需要这个数据, 然后通过线程间通信机制正确的执行自己的任务.
6. 中断的上半部和下半部又是什么? 不带 rtos 系统的程序怎么没有这说法?
通常在嵌入式编程中, 中断的处理需要遵循快进快出的原则, 而不带 rtos 系统因为没有多任务编程的概念, 几乎不会采用上半部和下半部编程(某些状态机框架可以实现), 通俗来说: 上半部就是得到数据, 下半部就是处理数据, 像这样把数据的产生和数据的处理分成上下两层的方式就叫上半部和下半部, 所以要是现在上半部和下半部编程, 最好就是采用多线程编程, 因为可以让一个线程用消息队列或者邮箱等方式阻塞在数据接收的地方去等待数据过来, 在阻塞期间不影响其他线程的运行.