Appearance
实时操作系统(RTOS)
概念
前几章解决的都是"一个程序怎么操作很多硬件"。但一个真实产品里往往有好几件互不相干的事要同时办:每 100 ms 采一次温度、每 20 ms 刷一次屏、随时可能来一帧串口命令、按键按下还要求立刻响应。裸机的 while (1) 只能一件一件干——前面那件没干完,后面那件就得等。
一句话说清它是什么:RTOS 是一个把 CPU 时间按优先级分给多个任务的调度器,它保证"最重要的事在最坏情况下也能在规定的时限内被处理"。
| 裸机(前后台) | RTOS | |
|---|---|---|
| 并发单位 | 一个 while (1) + 中断 | 多个任务,各有自己的栈 |
| 长任务的影响 | 阻塞所有其他事 | 被高优先级任务抢占 |
| 响应时间 | 取决于最长的一段代码 | 可以算出来、可以证明 |
| 代价 | 无 | RAM(每任务一个栈)+ 切换开销 + 复杂度 |
关键在最后一行上面的那句"可以算出来"。实时系统的"实时"不是"快",而是"可预测":只要给出每个任务的周期和最坏执行时间,就能算出最坏响应时间是否满足时限——结论是一个不等式,而不是一次实测。
这一层回答了上一层什么问题:上一章的 SPI/I2C 解决的是"怎么跟外面的芯片说话";这一章解决的是"一个 CPU 怎么同时照看很多件事"。它就是 操作系统 里"进程与调度"那一整套东西,被压缩到一块单片机的 RAM 里。
原理
一、裸机为什么撑不住:算一笔时间账
假设 while (1) 里依次做四件事,各占用的时间与它对时限的要求如下:
| 事务 | 单次耗时 | 时限要求 |
|---|---|---|
| 采集温度 | 1.2 ms | 每 100 ms 一次,晚一点没关系 |
| 刷新显示 | 0.8 ms | 每 20 ms 一次,晚一点没关系 |
| 串口收一帧 | 5.0 ms | 对方在等回包,必须快 |
| 按键响应 | 0.1 ms | 人的感受阈值约 100 ms |
裸机轮询的总循环时间 = 1.2 + 0.8 + 5.0 + 0.1 = 7.1 ms,最坏情况下按键要等到 7.1 ms 才被读到——这还能忍。真正的问题出在"某件事突然变慢":串口对端没发数据、HAL_UART_Receive 阻塞等待 1 秒,按键与显示全部停摆一秒。
裸机的响应时间由"最长的一段代码"决定,而这段代码的长度在运行时是不可控的。RTOS 的做法是把这四件事拆成四个任务,给按键任务最高优先级——谁更重要谁先跑,且这个优先关系在编译期就写死了。
二、任务 = 独立栈 + 一个上下文
一个任务(task)与一个函数的区别只有两点:它有自己独立的栈,以及它可以在任意时刻被挂起、稍后从挂起处继续。
把"稍后从挂起处继续"所需的那一小撮 CPU 状态叫做上下文(context)。在 ARM Cortex-M 上,一次切换要保存 8 个寄存器:
| 保存内容 | 说明 |
|---|---|
| R0–R3 | 参数 / 临时寄存器 |
| R12 | 临时寄存器 |
| LR(R14) | 返回地址 |
| PC(R15) | 下一条指令地址 |
| xPSR | 程序状态字(含标志位) |
这 8 个寄存器由硬件在进入异常时自动压栈,所以叫"硬件压栈",共 8 × 4 = 32 字节。如果任务里还用了 R4–R11,需要软件再压 8 个(32 字节)。一次最坏情况下的上下文切换要搬 64 字节。
每个任务在 RAM 里对应一个任务控制块(TCB,Task Control Block)加一块栈:
text
TCB 任务栈(向下生长)
+----------+ +------------------+ <- 栈顶(高地址)
| 栈顶指针 | -------> | 上次保存的上下文 |
| 优先级 | | 局部变量 |
| 状态 | | 函数调用帧 |
| 链表节点 | +------------------+ <- 栈底(低地址)
+----------+于是"切换任务"这个动作,本质就是换一个栈顶指针:
text
保存当前任务上下文(压栈)
-> 把当前 SP 写回它的 TCB
-> 从下一个任务的 TCB 取出 SP
-> 恢复上下文(出栈)注意这句"每个任务一块栈"的代价:栈是 RAM 里最贵的资源。4 个任务各 512 字节就是 2 KB,而一块入门级 MCU 总共才 20 KB RAM。
三、调度:固定优先级抢占
任务在任意时刻处于四种状态之一:
text
创建
|
v
+--------+ 被调度器选中 +--------+
| 就绪 | ----------------> | 运行 |
| Ready | <---------------- | Running|
+--------+ 被更高优先级 +--------+
^ 抢占 |
| | 等待事件(延时/信号量/队列)
| 事件到 v
| +--------+
+-------------------- | 阻塞 |
| Blocked|
+--------+抢占的意思是:只要一个比当前任务优先级更高的任务变成就绪,调度器立刻换人——不需要当前任务配合。这带来两个后果:
- 高优先级任务的响应时间只取决于"它自己 + 比它更高的任务",与低优先级任务写得多烂无关。这是实时性可分析的根基。
- 低优先级任务随时会被打断,所以它访问共享数据必须加保护。
调度发生的时机只有这么几个(不是"随时"):
- 系统节拍中断(SysTick)到来——纯延时到期、时间片轮转;
- 任务自己调用延时 / 等待信号量 / 等待队列,主动让出;
- 中断服务程序里调用
xxxFromISR的 API 唤醒了更高优先级的任务; - 任务自己主动调用
taskYIELD()。
同样的优先级才轮到时间片轮转(configUSE_TIME_SLICING),不同优先级之间永远是抢占关系。所以"给两个任务相同的优先级"等于宣布"它们同等重要,轮流跑"。
四、可调度性:两种判据
实时性最重要的一件事是在写代码之前就能判定这组任务来不来得及。判据有两个层次。
判据一:利用率上界(Liu & Layland,1973)
对速率单调调度(Rate Monotonic,周期越短优先级越高)这样的固定优先级调度,如果
text
U = Σ (C_i / T_i) <= n * (2^(1/n) - 1)则一定可调度。上界随任务数下降:
| 任务数 n | 1 | 2 | 3 | 4 | 5 | 8 | → ∞ |
|---|---|---|---|---|---|---|---|
| 上界 | 1.0000 | 0.8284 | 0.7798 | 0.7568 | 0.7435 | 0.7241 | 0.6931 |
这个式子有一个漂亮的极限:n 很大时上界收敛到 ln 2 ≈ 0.6931。也就是说,只要 CPU 利用率不超过 69.3%,不管任务怎么排都来得及。
但请注意它只是充分条件,不是必要条件:利用率超过上界不等于不可调度,只是这条判据给不出结论,要换更精确的方法。
判据二:响应时间分析(RTA)
对每个任务求最坏响应时间 R_i——从它被释放到它执行完毕的整个时间,中间还被更高优先级任务抢走的时间也算在内:
text
R_i = C_i + Σ_{j ∈ hp(i)} ceil(R_i / T_j) * C_jhp(i) 是比 i 优先级高的任务集合,ceil 是向上取整(在这个窗口里高优先级任务释放了几次,就抢走几次执行时间)。
这个式子的两边都有 R_i,所以用不动点迭代求解:先令 R_i = C_i,代进右边算出一个新值,再代回去,直到不再变化。如果迭代过程中 R_i > D_i(时限),立即判失败。
RTA 是充分必要条件(对固定优先级、D = T 的任务集),所以它比利用率上界强。
判据三:最早截止期优先(EDF)
如果允许动态优先级(谁的截止期最近谁先跑),可调度性判据简化为 U <= 1——CPU 只要不超载就一定来得及。代价是调度器每次都要按截止期排序,且实现比固定优先级复杂;多数小 RTOS 只提供固定优先级,EDF 多见于实时内核与理论研究。
五、同步与通信
任务之间要交换数据、要互斥访问外设,RTOS 提供的东西与 操作系统 里的同名概念一一对应,只是尺度更小:
| 机制 | 用途 | 关键区别 |
|---|---|---|
| 二值信号量 | 事件通知(中断给任务发信号) | 没有优先级继承 |
| 互斥量 Mutex | 互斥访问共享资源 | 有优先级继承 / 优先级天花板 |
| 计数信号量 | 管理 N 个同类资源 | 计数为 0 时等待 |
| 队列 | 任务间传数据(拷贝进出的值) | 天生线程安全,不用另加锁 |
| 事件标志组 | 一个任务等多个条件中的任意/全部 | 位或 / 位与 |
"二值信号量与互斥量看着一样,为什么不能混用" 是这一节的核心考点:
- 二值信号量可以从中断里给(give),它表达的是"某件事发生了";
- 互斥量只能由持有者释放,它表达的是"我在用这个资源",因此内核可以追踪"谁持有";
- 只有内核知道持有者是谁,才可能实现优先级继承。
六、优先级反转
考虑三个任务:高优先级 H、中优先级 M、低优先级 L,L 与 H 共用一把锁。
text
L 拿到锁
-> H 就绪,抢占 L,但 H 要那把锁,只能等
-> M 就绪,抢占 L(L 被 M 抢了,锁释放不出来)
-> 结果:H 在等 MH 被一个优先级远低于它的 M 挡住,等待时间不受控。这就是优先级反转(priority inversion)。
修法有两条,都靠内核在关键时刻临时抬高优先级:
| 方案 | 做法 | 代价 |
|---|---|---|
| 优先级继承 | 持锁者临时继承等待者的优先级 | 只解决"有等待者"的情形 |
| 优先级天花板 | 拿锁时直接抬到"所有可能拿这把锁的任务中的最高优先级" | 需要在设计期知道谁会用这把锁 |
这不是理论问题:1997 年火星探路者号的复位事故就是优先级反转(外加看门狗超时)造成的。
七、实时性怎么量:延迟与抖动
一个中断从"发生"到"对应的任务真正开始跑",中间隔着三段时间:
text
中断延迟 = 关中断最长时长 + 内核进入中断的固定开销
调度延迟 = 内核选下一个任务 + 上下文切换
执行时间 = 任务本身
最坏响应时间 = 中断延迟 + 调度延迟 + 执行时间 + 被更高优先级抢占的时间抖动(jitter)指的是"同一次响应,这一次和下一次差多少"。平均延迟小不等于实时性好——实时性看的是最坏值。所以在 RTOS 里,长临界区(长时间关中断)是最严重的罪状之一:它直接把所有任务的响应时间一起拖长。
示例
例 1:利用率上界到底有多紧
text
n = 1: 1.0000 n = 4: 0.7568
n = 2: 0.8284 n = 5: 0.7435
n = 3: 0.7798 n = 8: 0.7241
n -> inf: ln 2 = 0.6931只用 69.3% 的 CPU 就敢保证"一定能调度",这个数字看起来很浪费,但它是对任意任务集都成立的结论。真实任务集的利用率上界往往远高于 69.3%,所以实践中别把这 69.3% 当预算上限。
例 2:响应时间分析,一步步迭代
任务集如下(周期越短优先级越高):
| 任务 | C(执行) | T(周期) | D(时限) | 优先级 |
|---|---|---|---|---|
| T1 | 1 ms | 4 ms | 4 ms | 最高 |
| T2 | 2 ms | 5 ms | 5 ms | 中 |
| T3 | 2 ms | 10 ms | 10 ms | 最低 |
先算利用率:U = 1/4 + 2/5 + 2/10 = 0.25 + 0.40 + 0.20 = 0.8500。
上界是 n(2^(1/n) − 1) = 0.7798,0.8500 > 0.7798,判据给不出结论(注意:是"给不出结论",不是"不可调度")。
再用 RTA 逐项迭代:
text
T1: R = 1 -> 1 <= 4 OK
T2: R = 2 -> 2 + ceil(2/4)*1 = 3 -> 3 <= 5 OK
T3: R = 2 -> 2 + ceil(2/4)*1 + ceil(2/5)*2 = 5
5 -> 2 + ceil(5/4)*1 + ceil(5/5)*2 = 6
6 -> 2 + ceil(6/4)*1 + ceil(6/5)*2 = 8
8 -> 2 + ceil(8/4)*1 + ceil(8/5)*2 = 8 (不动点)-> 8 <= 10 OK所以这组任务实际可调度,尽管利用率判据说"不知道"。T3 的迭代序列 2 → 5 → 6 → 8 是整道题的关键:每一次迭代都在修正"被抢占了几次"这个估计。
例 3:一次真实调度长什么样
把上面那组任务按固定优先级抢占调度跑 20 ms,逐毫秒记录谁在跑:
text
t 0 1 2 3 4 5 6 7 8 9
T1 T2 T2 T3 T1 T2 T2 T3 T1 --
t 10 11 12 13 14 15 16 17 18 19
T2 T2 T1 T3 T3 T2 T1 T2 -- --三件事可以对着看:
- T1 每 4 ms 一次,且每次都在被释放的那一毫秒立刻跑——它是最高优先级,没有人能挡它,所以响应时间恒为 1 ms,与 RTA 算出的 R = 1 一致。
- T2 在
t = 0释放,跑在t = 1与t = 2,到t = 3才完成,响应时间 3 ms——被 T1 抢走了 1 ms,与 R = 3 一致。 - T3 在
t = 0释放,要跑 2 ms,但因为 T1 与 T2 排在前头,它到t = 3才第一次上 CPU,到t = 8才跑完,响应时间 8 ms,与 R = 8 一致。
"空闲"出现在 t = 9、t = 18、t = 19:这说明 85% 的利用率并不均匀地分布在每一毫秒上,而是"前面挤、后面空"。RTOS 里的低优先级任务(比如空闲任务)就是靠这些缝隙活的。
例 4:上下文切换的开销账
假设每秒切换 1000 次(1 kHz 节拍,每次节拍都发生一次切换),每次切换 200 个 CPU 周期,主频 72 MHz:
text
切换开销 = 1000 次/s * 200 周期 / 72 MHz = 0.2778%再算节拍中断本身(ISR 约 50 个周期,不做切换):
text
节拍开销 = 1000 次/s * 50 周期 / 72 MHz = 0.0694%两项加起来约 0.35%——比很多人想象的便宜。上下文切换贵在 RAM(每任务一个栈),不在 CPU。这也解释了为什么 RTOS 的"贵"通常体现在任务数量上:10 个任务 × 512 字节 = 5 KB,对 20 KB RAM 的芯片就是四分之一。
例 5:C 实现——可调度性判定加一次调度模拟
把上面三件事写成代码:算上界、迭代求 R、模拟一段调度。
/* rtos.c —— 固定优先级抢占调度的可调度性判定与一次调度模拟 */
#include <stdio.h>
#include <math.h>
#define NTASK 3
#define HORIZON 20
typedef struct {
const char *name;
int C; /* 最坏执行时间(ms) */
int T; /* 周期(ms) */
int D; /* 相对时限(ms),此处取 D = T */
int rem; /* 本周剩余执行时间 */
int next; /* 下次释放时刻 */
} Task;
static Task task[NTASK] = {
{ "T1", 1, 4, 4, 0, 0 },
{ "T2", 2, 5, 5, 0, 0 },
{ "T3", 2, 10, 10, 0, 0 }
};
static double util_bound(int n)
{
return n * (pow(2.0, 1.0 / n) - 1.0);
}
static int rta(int i) /* 响应时间分析:不动点迭代 */
{
int R = task[i].C;
printf(" %s: R = %d", task[i].name, R);
for (;;) {
int sum = task[i].C, j;
for (j = 0; j < i; j++)
sum += ((R + task[j].T - 1) / task[j].T) * task[j].C; /* 向上取整 */
if (sum == R || sum > task[i].D) break;
R = sum;
printf(" -> %d", R);
}
printf(" (D = %d) %s\n", task[i].D, R <= task[i].D ? "OK" : "MISS");
return R;
}
int main(void)
{
int i, t;
double U = 0.0;
printf("=== 1. Liu & Layland bound n(2^(1/n) - 1) ===\n");
for (i = 1; i <= 6; i++) printf(" n=%d: %.4f\n", i, util_bound(i));
printf(" n->inf: ln2 = %.4f\n", log(2.0));
printf("\n=== 2. task set ===\n");
for (i = 0; i < NTASK; i++) {
printf(" %s: C=%d T=%d D=%d\n", task[i].name, task[i].C, task[i].T, task[i].D);
U += (double)task[i].C / task[i].T;
}
printf(" U = %.4f, bound(3) = %.4f -> %s\n", U, util_bound(NTASK),
U <= util_bound(NTASK) ? "bound PASS" : "bound FAIL (not conclusive)");
printf("\n=== 3. response time analysis ===\n");
for (i = 0; i < NTASK; i++) rta(i);
printf("\n=== 4. fixed-priority preemptive schedule (%d ms) ===\n", HORIZON);
for (i = 0; i < NTASK; i++) { task[i].rem = 0; task[i].next = 0; }
for (t = 0; t < HORIZON; t++) {
for (i = 0; i < NTASK; i++)
if (task[i].next == t) { task[i].rem += task[i].C; task[i].next += task[i].T; }
for (i = 0; i < NTASK; i++)
if (task[i].rem > 0) { task[i].rem--; break; }
printf(" t=%2d %s\n", t, i < NTASK ? task[i].name : "idle");
}
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
=== 1. Liu & Layland bound n(2^(1/n) - 1) ===
n=1: 1.0000
n=2: 0.8284
n=3: 0.7798
n=4: 0.7568
n=5: 0.7435
n=6: 0.7348
n->inf: ln2 = 0.6931
=== 2. task set ===
T1: C=1 T=4 D=4
T2: C=2 T=5 D=5
T3: C=2 T=10 D=10
U = 0.8500, bound(3) = 0.7798 -> bound FAIL (not conclusive)
=== 3. response time analysis ===
T1: R = 1 (D = 4) OK
T2: R = 2 -> 3 (D = 5) OK
T3: R = 2 -> 5 -> 6 -> 8 (D = 10) OK
=== 4. fixed-priority preemptive schedule (20 ms) ===
t= 0 T1
t= 1 T2
t= 2 T2
t= 3 T3
t= 4 T1
t= 5 T2
t= 6 T2
t= 7 T3
t= 8 T1
t= 9 idle
t=10 T2
t=11 T2
t=12 T1
t=13 T3
t=14 T3
t=15 T2
t=16 T1
t=17 T2
t=18 idle
t=19 idle第 2 段的 bound FAIL 后面跟着 not conclusive 是刻意的:利用率判据失败只说明这条判据失效,不代表任务集不可调度——第 3 段的 RTA 才是判决。第 4 段的模拟与第 3 段算出的 R 逐一吻合(T1→1、T2→3、T3→8),这正说明 RTA 不是在纸上算着玩,它预测的就是真实调度。
例 6:Python——上界、RTA 与开销一起算
# RTOS:Liu & Layland 上界、响应时间分析、切换开销与 RAM 预算
import math
import unicodedata
def wpad(s, n):
"""按显示宽度右补空格:东亚宽字符算 2 列"""
w = sum(2 if unicodedata.east_asian_width(c) in "WF" else 1 for c in s)
return s + " " * max(0, n - w)
print("=== 一、Liu & Layland 利用率上界 n(2^(1/n) - 1) ===")
for n in range(1, 9):
print(f"n={n}: {n * (2 ** (1 / n) - 1):.4f}")
print(f"n->inf: ln2 = {math.log(2):.4f}")
print("\n=== 二、任务集与可调度性 ===")
tasks = [("T1", 1, 4), ("T2", 2, 5), ("T3", 2, 10)]
U = sum(c / t for _, c, t in tasks)
bound = len(tasks) * (2 ** (1 / len(tasks)) - 1)
print(f"U = 1/4 + 2/5 + 2/10 = {U:.4f}")
print(f"bound(3) = {bound:.4f} -> U {'<=' if U <= bound else '>'} bound, "
f"{'PASS' if U <= bound else 'FAIL (not conclusive)'}")
for i, (name, c, t) in enumerate(tasks):
R, seq = c, [c]
while True:
nxt = c + sum(math.ceil(R / tj) * cj for _, cj, tj in tasks[:i])
if nxt == R or nxt > t:
break
R = nxt
seq.append(R)
print(f"{name}: C={c} T={t} -> R = {R} 迭代序列 {seq} {'OK' if R <= t else 'MISS'}")
print("\n=== 三、上下文切换与节拍开销(72 MHz)===")
for label, cnt, cyc in [("上下文切换", 1000, 200), ("SysTick ISR", 1000, 50)]:
print(f"{label}: {cnt} 次/s x {cyc} 周期 / 72 MHz = {cnt * cyc / 72e6 * 100:.4f}%")
print("\n=== 四、任务栈与 RAM 预算(20 KB 的入门 MCU)===")
stacks = [("main", 1024), ("采集任务", 512), ("显示任务", 512), ("通信任务", 512), ("空闲任务", 256)]
total = sum(s for _, s in stacks)
for name, s in stacks:
print(f" {wpad(name, 10)}{s:5d} B")
print(f"栈合计 {total} B = {total / 1024:.2f} KB,占 20 KB 的 {total / 20480 * 100:.2f}%")
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
=== 一、Liu & Layland 利用率上界 n(2^(1/n) - 1) ===
n=1: 1.0000
n=2: 0.8284
n=3: 0.7798
n=4: 0.7568
n=5: 0.7435
n=6: 0.7348
n=7: 0.7286
n=8: 0.7241
n->inf: ln2 = 0.6931
=== 二、任务集与可调度性 ===
U = 1/4 + 2/5 + 2/10 = 0.8500
bound(3) = 0.7798 -> U > bound, FAIL (not conclusive)
T1: C=1 T=4 -> R = 1 迭代序列 [1] OK
T2: C=2 T=5 -> R = 3 迭代序列 [2, 3] OK
T3: C=2 T=10 -> R = 8 迭代序列 [2, 5, 6, 8] OK
=== 三、上下文切换与节拍开销(72 MHz)===
上下文切换: 1000 次/s x 200 周期 / 72 MHz = 0.2778%
SysTick ISR: 1000 次/s x 50 周期 / 72 MHz = 0.0694%
=== 四、任务栈与 RAM 预算(20 KB 的入门 MCU)===
main 1024 B
采集任务 512 B
显示任务 512 B
通信任务 512 B
空闲任务 256 B
栈合计 2816 B = 2.75 KB,占 20 KB 的 13.75%这一节的账要连起来看:CPU 开销只占 0.35%(切换 0.2778% + 节拍 0.0694%),而栈一项就吃掉 2816 B——20 KB RAM 的 13.75%。RTOS 的成本主要在 RAM,不在 CPU。所以在小芯片上,"能不能上 RTOS"的问题通常先撞到 RAM 而不是主频。
考点
- 实时性 = 可预测性,不是"快"。判断标准是最坏响应时间是否满足时限,而不是平均速度快不快。
- 上下文切换保存 8 个寄存器:R0–R3、R12、LR、PC、xPSR,共 32 字节硬件压栈;若用了 R4–R11 再加 32 字节。
- 速率单调调度 RMS:周期越短优先级越高。利用率上界
U <= n(2^(1/n) − 1),n → ∞ 时收敛到 ln 2 ≈ 0.6931。这是充分条件,超了不等于不可调度。 - 响应时间分析 RTA:
R_i = C_i + Σ ceil(R_i/T_j) C_j,两边都有 R,用不动点迭代;迭代中一旦R > D立刻判失败。RTA 是充分必要条件,比利用率上界强。 - EDF 的判据只有
U <= 1,但要求动态优先级。 - 二值信号量 vs 互斥量:前者可以从 ISR 里 give、无优先级继承;后者只能由持有者释放、有优先级继承。用信号量做互斥会导致优先级反转。
- 优先级反转:高优先级被中优先级挡住。修法是优先级继承或优先级天花板。火星探路者号事故的根因。
- 队列天生线程安全,数据是拷贝进出的,不需要额外加锁;但拷贝有开销,小数据用值队列、大数据用指针队列。
- 易错:把
U超上界当成"不可调度";把ceil写成整除(R/T向下取整会低估抢占次数);把"同理优先级时间片轮转"说成"同优先级也抢占";以为中断能直接调用普通 API(必须用xxxFromISR版本)。
小结
- 裸机的响应时间由最长的一段代码决定,长任务会拖死所有其他事;RTOS 用优先级抢占把"谁更重要"在编译期定下来。
- 一个任务 = 一个 TCB + 一块独立栈,切换就是把 SP 从一块栈换到另一块,代价是 64 字节的内存搬运。
- 可调度性有三个层次:利用率上界(够用就够用,不够用不下结论)、响应时间分析(充分必要)、EDF(
U <= 1)。 - 固定优先级下,高优先级任务的响应时间与低优先级任务无关,这正是实时系统可分析的根本原因。
- 同步用互斥量而不是二值信号量,否则会掉进优先级反转。
- 实时性看最坏值,不看平均值;代价主要在 RAM,不在 CPU。
回到主线:这一章的"任务、调度、同步"就是 操作系统 的进程管理在同一套概念上的微缩版,只是把页表换成了静态内存、把时间片换成了周期与时限;而它算的那些数(2^(1/n) − 1、不动点迭代)与 计组的流水线与吞吐率 一样,都是"先算清楚能不能满足指标,再去写代码"的思路。
这一章答的是"多件事怎么在同一颗芯片里轮着跑"。到这里,embed 的技术部件已经齐了——剩下的问题变成"怎么把它们拼成一个产品"。下一章把前面所有章节串成一条项目路径。
下一篇:综合实践:从点灯到小型系统
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。