本章简介:
本章是概述性的内容。可以把多任务系统当做一个团队,里面的每一个任务就相当于团队里的一个人。团队成员之间要协调工作进度(同步)、争用会议室(互斥)、沟通(通信)。多任务系统中所涉及的概念,都可以在现实生活中找到例子。 各类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有一个大概的掌握,后续我将会对这些一个个展开讲解,并且会从内部机制来对其进行分析,让你不止会用,而且还知道为什么可以这样用。