本章简介:
本章是概述性的内容。可以把多任务系统当做一个团队,里面的每一个任务就相当于团队里的一个人。团队成员之间要协调工作进度(同步)、争用会议室(互斥)、沟通(通信)。多任务系统中所涉及的概念,都可以在现实生活中找到例子。
各类RTOS都会涉及这些概念:任务通(tasknotification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。
我们先站在更高角度来讲解这些概念。
同步与互斥的概念
我这里用韦东山老师举的例子:
一句话理解同步与互斥:我等你用完厕所,我再用厕所。
什么叫同步?就是:哎哎哎,我正在用厕所,你等会。
什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
同步与互斥经常放在一起讲,是因为它们之的关系很大,
“
互斥
”
操作可以使用
“
同步
”
来实现。我“
等
”
你用完厕所,我再用厕所。这不就是用
“
同步
”
来实现
“
互斥
”
吗?再举一个例子。在团队活动里,同事A
先写完报表,经理
B
才能拿去向领导汇报。经理
B必须等同事A
完成报表,
AB
之间有依赖,
B
必须放慢脚步,被称为同步。在团队活动中,同事A
已经使用会议室了,经理
B
也想使用,即使经理
B
是领导,他也得等着,这就叫互斥。经理B
跟同事
A
说:你用完会议室就提醒我。这就是使用
"
同步
"
来实现
"
互斥
"
。
有时候看代码更容易理解,伪代码如下:
假设有
A
、
B
两人早起抢厕所,
A
先行一步占用了;
B
慢了一步,于是就眯一会;当
A
用完后叫醒B
,
B
也就愉快地上厕所了。在这个过程中,A
、
B
是互斥地访问
“
厕所
”
,
“
厕所
”
被称之为临界资源。
我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。这里埋下伏笔,之后看源码就会恍然大悟,
同一时间只能有一个人使用的资源,被称为临界资源。比如任务A
、
B
都要使用串口来打印,串口就是临界资源。如果A
、
B
同时使用串口,那么打印出来的信息就是
A
、
B
混杂,无法分辨。所以使用串口时,应该是这样:A
用完,
B
再用;
B
用完,
A
再用。
裸机的同步与互斥
在裸机程序里,可以使用一个全局变量或静态变量实现互斥操作,比如要互斥地使用
LCD,可以使用如下代码:
但是在 RTOS 里,使用上述代码实现互斥操作时,大概率是没问题的,但是无法确保万无一失。 假设如下场景:有两个任务 A、B 都想调用 LCD_PrintString,任务 A 执行到第 4 行代码时发现 bCanUse 为 1,可以进入 if 语句块,它还没执行第 6 句指令就被切换出去了;然后任务 B 也调用 LCD_PrintString,任务 B 执行到第 4 行代码时也发现 bCanUse 为 1,也可以进入 if 语句块使用 LCD。
在这种情况下,使用静态变量并不能实现互斥操作。
上述例子中,是因为第 4、第 6 两条指令被打断了,那么如下改进:在函数入口处先让bCanUse 减一。这能否实现万无一失的互斥操作呢?
把第 4 行的代码使用汇编指令表示如下:
假设如下场景:有两个任务 A、B 都想调用 LCD_PrintString,任务 A 执行到第 04.1 行代码时读到的 bCanUse 为 1,存入寄存器 R0 就被切换出去了;然后任务B也调用LCD_PrintString,任务 B 执行到第 4 行时发现 bCanUse 为 1 并把它减为 0,执行到第 5 行代码时发现条件成立可以进入 if 语句块使用 LCD,然后任务 B 也被切换出去了;现在任务A 继续运行第 04.2 行代码时 R0 为 1,运行到第 04.3 行代码时把 bCanUse 设置为 0,后续也能成功进入 if 的语句块。在这种情况下,任务 A、B 都能使用 LCD。
上述方法不能保证万无一失的原因在于:在判断过程中,被打断了。如果能保证这个过程不被打断,就可以了:
通过关闭中断来实现,RTOS的核心操作。
示例 1 的代码改进如下:在第 5~7 行前关闭中断。
示例 2 的代码改进如下:在第 5 行前关闭中断。
各类方法的对比
高能:
能实现同步、互斥的内核方法有:任务通知(task notification)、队列(queue)、事件组 (event group)、信号量(semaphoe)、互斥量(mutex)。理解这几个,你就能用FreeRTOS来做出你第一个项目了。
它们都有类似的操作方法:
获取/释放、阻塞/唤醒
、超时。比如:
-- 任务
A
获取资源,用完后任务
A
释放资源
--
任务
A
获取不到资源则阻塞,任务
B
释放资源并把任务
A
唤醒
--
任务
A
获取不到资源则阻塞,并定个闹钟;
A
要么超时返回,要么在这段时间内因为任务B释
放资源而被唤醒。
这些内核对象五花八门,记不住怎么办?我也记不住,通过对比的方法来区分它们。
--
能否传信息?还是只能传递状态?
事件组
--
为众生(所有任务都可以使用)?只为你(只能指定任务使用)?
队列,事件组,信息量/任务通知,互斥量。
--
我生产,你们消费?
队列
--
我上锁,只能由我开锁,
互斥量
使用图形对比如下:
队列:
-- 里面可以放任意数据,可以放多个数据
-- 任务、ISR
都可以放入数据;任务、
ISR
都可以从中读出数据
事件组:
-- 一个事件用一
bit
表示,
1
表示事件发生了,
0
表示事件没发生
-- 可以用来表示事件、事件的组合发生了,不能传递数据
-- 有广播效果:事件或事件的组合发生了,等待它的多个任务都会被唤醒
信号量:
-- 核心是
"
计数值
"
-- 任务、
ISR
释放信号量时让计数值加
1
-- 任务、
ISR
获得信号量时,让计数值减
1
任务通知:
-- 核心是任务的
TCB
里的数值
-- 会被覆盖
-- 发通知给谁?必须指定接收任务
-- 只能由接收任务本身获取该通知
互斥量:
-- 数值只有
0
或
1
-- 谁获得互斥量,就必须由谁释放同一个互斥量
总结:
经过对本篇的概述,如果你以前学过的话,可以加深你对FreeRTOS的了解,如果你是初学,可以帮你对RTOS有一个大概的掌握,后续我将会对这些一个个展开讲解,并且会从内部机制来对其进行分析,让你不止会用,而且还知道为什么可以这样用。