主机边界:默认使用标准库模型、任务时间线、队列容量、内存预算和故障分类;没有目标板也能完成公式、状态、背压、超时和清单测试。
目标机边界:真实 FreeRTOS port、tick、时钟、上下文切换、IRQ、缓存/DMA、栈/堆、看门狗、输出安全态、负载、仪器和复位路径必须回到当前芯片、板卡和官方资料。
01 · REAL-TIME MODEL
实时模型:deadline、WCET 与抖动
实时系统不是把平均速度做快,而是在指定输入、资源和负载条件下,让每个重要动作在 deadline 之前完成。先用 release、period、deadline、WCET 和 jitter 描述任务,再讨论调度器、时钟和测量工具,才能把‘实时’从宣传词变成可复查的时间合同。
10.1.1 任务三元组:WCET、D 与 P
实时合同先写对象和时基。一个任务至少要有 name、release 条件、period 或事件来源、relative deadline、WCET 假设、优先级、资源依赖和失败动作。‘10 ms 任务’可能指每 10 ms 释放一次,也可能指必须在 10 ms 内响应;若不写清楚,调度、测试和现场日志无法对齐。
10.1.2 硬实时、软实时与价值曲线
FreeRTOS 的任务 API、Zephyr 的线程/调度服务以及 Linux 的调度策略都提供了不同抽象,但抽象名称不会自动生成 deadline。调度器只能依据可见的就绪、阻塞和优先级状态做选择;系统设计者仍要定义哪些工作必须完成、哪些可以丢弃、哪些应降级,以及错过之后如何保存证据。
C · DEADLINE BUDGETtypedef struct { const char *name; uint32_t wcet_us; uint32_t period_us; uint32_t deadline_us; } task_budget_t; static int due(uint32_t now, uint32_t deadline) { return (int32_t)(now - deadline) >= 0; }
模型展示预算和回绕安全比较;实际 tick、时钟、端口和 WCET 仍需目标机证据。
PYTHON · UTILIZATIONdef utilization(wcet, period): if wcet <= 0 or period <= 0: raise ValueError("positive budget required") return wcet / period def schedulable(tasks, reserve=0.2): return sum(utilization(t[0], t[1]) for t in tasks) <= 1 - reserve
主机计算是准入检查,不替代 ISR、阻塞、缓存和真实最坏执行路径。
10.1.3 release、finish 与 response time
release 是工作可被执行的时刻,start 是实际获得处理器的时刻,finish 是本次工作完成的时刻,response time 通常是 finish-release。等待队列、互斥锁、禁中断、缓存未命中、总线争用和高优先级抢占都可能拉长 start 或 finish;只测函数内部 elapsed 会漏掉排队和阻塞。
| 对象 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 时间 | release/deadline/finish | 平均值就是 WCET |
| 预算 | WCET/period/reserve | 利用率低就必然按时 |
| 证据 | trace/时基/窗口 | 一次样本证明上界 |
10.1.4 WCET 测量不是平均值
WCET 不是在某台机器上跑几次得到的最大值,而是针对给定代码、编译选项、输入集合、硬件、时钟和干扰模型所声称的上界。主机可以做路径枚举、压力和统计,目标机还要覆盖缓存、Flash wait state、DMA、IRQ、温度、电源和编译器生成代码;没有这些条件时只能称为测得样本。
10.1.5 利用率、裕量与准入
利用率 U=C/T 是准入预算的起点,不是所有调度算法都适用的充分条件。C 要包含真正占用 CPU 的执行,T 要与 release 语义一致,还要为 ISR、内核 tick、上下文切换、日志、故障恢复和突发任务保留 reserve。若总和接近 1,任何测量误差和优先级反转都会变成 deadline 失败。
从左到右是思考顺序,不是把时间、调度、资源和恢复证据压缩成一个“通过”。
10.1.6 抖动、延迟与观测窗口
抖动应说明测量哪两个事件以及窗口多长。输入边沿本身可能不均匀,任务 release 可能受 tick 量化,开始时间可能受抢占影响,完成时间还会叠加锁和总线等待。把 min、max、平均、p95 与样本数量一起保存,才能区分偶发异常、系统性相位漂移和观测工具引入的延迟。
10.1.7 tick、单调时钟与时间回绕
时间比较应使用单调时基,并明确 tick 宽度、单位、换算和回绕窗口。对有限宽度计数器,直接用 now > deadline 可能在回绕后错误;常见做法是用有符号差值并限制最大等待窗口。墙上时钟适合显示日期,不适合判断任务是否过期,尤其在校时、休眠唤醒或时间源切换时。
10.1.8 本节交付:实时预算卡
实时预算卡建议包含 task_id、release_source、period、deadline、wcet_sample、wcet_claim、priority、blocking_bound、isr_share、memory、clock、trace_fields、failure_action、host_result、target_result 和 unverified_scope。它把‘实时’变成读者可运行、工程师可测量、审查者可质疑的一组字段,而不是一句性能承诺。
请围绕“实时模型:deadline、WCET 与抖动”写出对象、输入/释放条件、调度或同步机制、输出与完成语义、一个时间或内存约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、端口、时钟、FreeRTOSConfig、工具链、仪器、负载和观察条件。不要用一次平均延迟、一个 heartbeat、一个队列发送成功或一张截图覆盖没有做过的验证。
02 · TASKS AND SCHEDULING
任务与调度:优先级不是时间保证
任务是可调度的工作单元,但任务状态、优先级、抢占、时间片、阻塞和唤醒之间有明确规则。把一个函数包进 task 入口并不会自动得到实时性;只有在释放、阻塞、预算、优先级和过期策略都被写出后,调度器的行为才可以被观察和解释。
10.2.1 任务状态与生命周期
任务状态是解释调度的第一张地图。READY 表示具备运行条件,RUNNING 表示当前占用某个 CPU,BLOCKED 表示等待时间、队列、通知或同步对象,SUSPENDED 表示被显式挂起,DELETED 表示生命周期结束。不同端口可能还有等待超时或临界区细节;记录状态时应同时保存原因和时间。
10.2.2 优先级、抢占与合作式执行
优先级决定多个就绪任务的选择顺序,不等于任务在 deadline 前一定完成。高优先级任务可能被中断、禁调度、互斥锁或更高优先级任务延迟;低优先级任务也可能持锁阻塞高优先级等待者。调度合同必须把 blocking bound、ISR 预算和最坏执行时间一起算,而不是只比较 priority 数字。
C · PERIODIC TASKvoid sample_task(void *arg) { TickType_t next = xTaskGetTickCount(); for (;;) { sample_once(); vTaskDelayUntil(&next, pdMS_TO_TICKS(10)); record_progress(); } }
绝对节奏示例;任务状态、tick 换算、过期策略和端口行为必须按配置核对。
PYTHON · RELEASE TRACEdef release_times(start, period, count): if period <= 0 or count < 0: raise ValueError("invalid schedule") return [start + i * period for i in range(count)] def response(release, finish): if finish < release: raise ValueError("finish precedes release") return finish - release
确定性时间线用于主机断言;它不包含上下文切换、ISR 或目标时钟误差。
10.2.3 时间片与同优先级公平
抢占式调度在更高优先级任务就绪时切换,合作式或调度锁则要求当前代码主动让出。临界区太长会把‘可抢占’变成假设;在任务里连续计算、轮询外设或关闭中断,都会把其他任务的 response time 拉长。trace 中要标出切换原因和临界区边界。
| 对象 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 状态 | ready/block/wake | 任务只有运行和停止 |
| 选择 | priority/preempt/time slice | 高优先级等于零延迟 |
| 节奏 | absolute release/overrun | 相对 delay 不漂移 |
10.2.4 阻塞、饥饿与活锁
同优先级时间片改善公平性,但 tick 粒度、端口配置和阻塞行为会影响实际份额。若两个任务都不阻塞且每片都做大量工作,系统可能看似公平却整体错过 deadline;若高优先级任务不断唤醒,低优先级任务会饥饿。要保存每个任务获得 CPU 的区间和被推迟的次数。
10.2.5 周期任务的绝对节奏
相对 delay 是‘从现在再等一段时间’,执行时间和抢占会逐次累积相位误差;绝对节奏则保存 next_release,每次按 period 推进。若任务已经过期,需要明确跳过、合并、限次追赶、降级或复位。无限追赶历史工作会形成过载正反馈,让系统越来越晚。
从左到右是思考顺序,不是把时间、调度、资源和恢复证据压缩成一个“通过”。
10.2.6 空闲任务、CPU 预算与低功耗
空闲任务的时间可以用于低功耗、后台清理或统计,但不能被误认为可随时借用的实时预算。进入睡眠会改变 tick、时钟和唤醒延迟,Flash/外设重新上电也有成本。CPU 使用率报告应分任务、ISR、内核和 idle,并说明采样窗口、计时来源和睡眠状态。
10.2.7 工作队列与延后处理
工作队列把短 ISR 或调用者动作转成后台 handler,减少中断上下文中的复杂操作;但 handler 若阻塞,会卡住同一队列后续项目。Zephyr 文档也强调工作队列 handler 的同步和阻塞边界。为每个项目写最大执行、是否可睡眠、失败重试和队列拥塞行为,才能判断这种延后是否安全。
10.2.8 通知任务与交接点
任务交接的证据不应只有‘通知已发送’。需要记录事件来源、通知/队列序号、产生时间、唤醒时间、开始时间、消费结果、剩余容量和 deadline。这样可以分开定位事件没产生、唤醒晚、被高优先级抢占、处理慢和结果丢弃,而不是把所有问题归因于调度器。
10.2.9 本节交付:任务调度卡
任务调度卡包括 task_id、state_machine、priority、preemption、period、release_source、deadline、wcet、blocking、critical_sections、cpu_share、idle_policy、workqueue_policy、overrun_action、trace_schema 和 target_limits。它是后续同步、内存、看门狗与交付单元共同使用的事实表。
请围绕“任务与调度:优先级不是时间保证”写出对象、输入/释放条件、调度或同步机制、输出与完成语义、一个时间或内存约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、端口、时钟、FreeRTOSConfig、工具链、仪器、负载和观察条件。不要用一次平均延迟、一个 heartbeat、一个队列发送成功或一张截图覆盖没有做过的验证。
03 · SYNCHRONIZATION AND IPC
同步与通信:队列、通知与互斥
同步原语不是可互换的积木。队列传数据,通知传事件或计数,信号量表达可用资源,互斥锁保护共享所有权,事件组表达多个条件;从 ISR 调用的 API、阻塞方式、唤醒对象、容量和溢出策略都必须写进合同。
10.3.1 队列是数据合同
队列同时定义元素格式、容量、顺序和所有权。发送成功只说明元素进入了队列,不说明消费者已经处理;接收成功才说明某个消费者取走了元素。固定长度、指针和零拷贝队列的内存归属不同,必须写清谁分配、谁释放、谁可以修改以及队列满、空和关闭时返回什么。
10.3.2 任务通知的定向语义
FreeRTOS 任务通知通常直接附着在一个任务上,因此比通用事件组或信号量更轻量、更快,但它不是广播总线。通知值可以表达计数、位或简单状态,却只有一个被通知对象;多个消费者、数据载荷、持久事件和取消语义仍可能需要队列、事件组或专用任务。选择原语时先写接收者和证据。
C · ISR TO TASKBaseType_t higher = pdFALSE; void ADC_IRQHandler(void) { sample_t sample = adc_read_sample(); xQueueSendFromISR(sample_queue, &sample, &higher); portYIELD_FROM_ISR(higher); }
ISR 只做最小采样和 ISR-safe 发送;队列满、缓存和中断优先级仍需目标配置。
PYTHON · BOUNDED QUEUEdef offer(queue, item, capacity, policy="reject"): if capacity <= 0: raise ValueError("capacity must be positive") q = list(queue) if len(q) >= capacity: if policy == "reject": return q, False, "full" if policy == "overwrite_oldest": q.pop(0) else: raise ValueError("unknown policy") q.append(item) return q, True, "accepted"
主机模型显式记录拒绝或覆盖;目标机还需证明所有权、DMA 和真实生产速率。
10.3.3 二值与计数信号量
二值信号量适合同步一次发生的事件,计数信号量适合表示多个同类令牌,互斥锁则表达‘谁拥有共享资源’。把信号量当互斥锁会丢掉所有权与优先级继承语义;把互斥锁当事件通知会让持锁时间、递归和 ISR 使用变得不可解释。名称相似不能替代语义。
| 对象 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 原语 | queue/notify/mutex | 所有同步对象等价 |
| 边界 | ISR-safe/ownership | 发送成功等于消费 |
| 压力 | capacity/backpressure | 队列满可静默覆盖 |
10.3.4 互斥锁与所有权
互斥锁的持有范围应尽量短,并且只保护真实共享不变量。若持锁期间做日志、等待 DMA、访问慢外设或调用未知回调,高优先级任务的阻塞上界会膨胀。FreeRTOS mutex 文档说明优先级继承可以降低部分反转风险,但不能治愈所有优先级反转;还要限制锁链、嵌套顺序和最长持有时间。
10.3.5 优先级反转与继承
优先级反转通常表现为高优先级任务等待低优先级持锁者,而中优先级任务不断运行。继承机制可能临时提升持锁者,但不会消除死锁、长临界区、多个锁链或中断禁用造成的延迟。证据应包含 holder、waiter、priority、lock_acquire/release、blocked_duration 和最终 deadline,而不只是‘开启了继承’。
从左到右是思考顺序,不是把时间、调度、资源和恢复证据压缩成一个“通过”。
10.3.6 事件组与多条件启动
事件组适合把多个独立条件组合成 wait-any 或 wait-all,例如网络、传感器和校准都 ready;它不携带数据,也不自动说明每一位由谁清除。若清位时机不明确,事件可能丢失或被重复消费。把事件位和数据队列配对,记录快照、等待条件和清除动作,才能复查启动路径。
10.3.7 ISR-safe API 与中断边界
ISR 不能调用会阻塞、调度不安全或依赖任务上下文的接口。正确模式通常是读取最小硬件状态、把样本写入已分配缓冲、调用 ISR-safe 发送/通知并请求一次上下文切换;复杂解析、内存释放和重试留给任务。目标机还要核对中断优先级是否满足端口限制、缓存/DMA 是否同步。
10.3.8 背压、容量与溢出
背压是系统在生产速度超过消费速度时的明确选择:拒绝新数据、覆盖最旧、降采样、暂停采集、提高批量或进入故障。静默覆盖会让数据质量无法解释;无限阻塞会把 ISR 或高优先级任务拖死。记录 capacity、high_watermark、accepted、dropped、overwritten 和 recovery_action,才能把过载变成可诊断事实。
10.3.9 本节交付:通信同步卡
通信同步卡包括 producer、consumer、primitive、payload、ownership、capacity、ordering、isr_api、wait_semantics、timeout、overflow、backpressure、priority_bound、trace_fields 和 target_assumptions。它将同步选择与后续内存、看门狗和发布证据连接起来,避免‘换一个 API 只是实现细节’的误判。
请围绕“同步与通信:队列、通知与互斥”写出对象、输入/释放条件、调度或同步机制、输出与完成语义、一个时间或内存约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、端口、时钟、FreeRTOSConfig、工具链、仪器、负载和观察条件。不要用一次平均延迟、一个 heartbeat、一个队列发送成功或一张截图覆盖没有做过的验证。
04 · MEMORY AND STACK
内存与栈:确定性从哪里来
内存策略决定了系统是否会在运行中遇到不可预测的等待、碎片、失败和栈溢出。FreeRTOS 同时支持静态与动态创建对象;工程上要把选择、生命周期、对齐、DMA、所有权、栈水位和失败动作写进预算,而不是只看链接器的剩余数字。
10.4.1 静态与动态对象
静态分配把对象存储和生命周期放进编译期或显式缓冲,通常更容易审查上限;动态分配则更灵活,但需要失败路径、碎片分析和运行期证据。现代 FreeRTOS 可以为任务、队列、定时器、事件组、信号量和互斥锁使用静态内存,但‘可静态创建’不代表对象自动初始化、并发和释放语义已经正确。
10.4.2 heap scheme 与碎片
堆的剩余字节不等于下一次分配一定成功。不同 heap 实现的合并、拆分、对齐和碎片行为不同;长期运行中交替申请不同尺寸对象可能留下无法利用的小块。记录 free、minimum-ever-free、largest-free-block、失败次数和分配调用点,比单一 RAM 百分比更能说明风险。
C · STATIC OBJECTSstatic StackType_t worker_stack[512]; static StaticTask_t worker_tcb; static uint8_t queue_storage[8 * sizeof(frame_t)]; static StaticQueue_t queue_tcb; /* create with xTaskCreateStatic/xQueueCreateStatic */
静态数组让上限可见;大小、对齐、section、栈水位和具体 API 仍需当前版本核对。
PYTHON · MEMORY BUDGETdef memory_budget(stack, queue, heap, limit): if min(stack, queue, heap, limit) < 0: raise ValueError("memory values must be non-negative") total = stack + queue + heap return {"total": total, "within_limit": total <= limit}
预算模型检查加总,不会测量栈峰值、碎片、cache、DMA 可见性或 linker 段属性。
10.4.3 任务栈预算
任务栈要覆盖最深调用链、局部数组、库函数、异常路径、格式化日志、FPU 上下文和中断嵌套带来的额外需求。high-water mark 只说明在已执行路径上留下的最小余量,不能证明未走到的路径安全。把栈预算、编译选项、优化级别、输入集合和测量窗口一起版本化。
| 对象 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 对象 | static/dynamic/lifetime | 剩余 RAM 足够 |
| 栈 | high-water/overflow | 一次运行无溢出就是安全 |
| 缓冲 | owner/cache/DMA | 指针入队无需归属 |
10.4.4 栈溢出检测与恢复
栈溢出检测可以用哨兵、边界检查、MPU、端口 hook 或 fault 现场,但每种方法覆盖的时机不同。检测到溢出后继续运行通常会扩大数据破坏,安全动作应是记录任务名、栈指针、调用现场、复位原因并进入受控停止或恢复。不要把‘没有触发 hook’写成‘绝不会溢出’。
10.4.5 缓冲区所有权与生命周期
零拷贝和 DMA 让缓冲所有权更复杂:生产者可能只拥有填充阶段,DMA 控制器拥有传输阶段,消费者拥有解析阶段,回收者拥有释放阶段。每次转移都要有状态、屏障/缓存动作和失败回收。如果队列只传指针而没有长度、容量和 owner,最容易出现覆盖、双重释放和旧数据重用。
从左到右是思考顺序,不是把时间、调度、资源和恢复证据压缩成一个“通过”。
10.4.6 ISR、DMA 与分配禁区
ISR 中不能依赖可能阻塞或不可重入的分配器;实时路径也不应把堆碎片和锁竞争引入每次事件。预分配固定大小块、无锁环形缓冲或生产者专属池可以减少不确定性,但会把容量和耗尽策略前置。目标机还要核对 DMA 能否访问该区域以及 cache clean/invalidate 的时机。
10.4.7 确定性、缓存与对齐
对齐、缓存属性、内存总线和 Flash wait state 都会影响执行时间和数据一致性。一个主机 Python 列表没有 cache miss、总线仲裁或非对齐陷阱;目标机应记录地址、段属性、DMA 可见性、cache 操作、测量工具和最坏路径。将内存放入某个 section 也不能自动证明 linker、MPU 和外设配置一致。
10.4.8 本节交付:内存预算卡
内存预算卡包括 object、count、size、alignment、section、owner、lifetime、peak、stack_high_water、heap_floor、largest_block、dma_cache_rule、failure_hook、reset_action、host_result、target_result 和 stop_conditions。它让调度和看门狗能引用明确的内存边界,而不是在故障后才猜‘是不是 RAM 不够’。
请围绕“内存与栈:确定性从哪里来”写出对象、输入/释放条件、调度或同步机制、输出与完成语义、一个时间或内存约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、端口、时钟、FreeRTOSConfig、工具链、仪器、负载和观察条件。不要用一次平均延迟、一个 heartbeat、一个队列发送成功或一张截图覆盖没有做过的验证。
05 · WATCHDOG AND OVERLOAD
看门狗与过载:失败时怎样活下来
看门狗是最后一道时间边界,不是把系统永远保持运行的按钮。健康监视器要检查关键任务是否真正完成、队列是否有进展、内存是否越界、通信是否恢复,并在超时前保存原因、进入安全态或执行有界复位。喂狗应证明进展,而不是证明某个线程还活着。
10.5.1 看门狗的职责边界
硬件看门狗通常由独立或不同于主 CPU 的时钟计时,软件看门狗则由任务或监视器收集健康证据。两者职责不同:软件层可以解释哪个任务、队列或锁没有进展,硬件层可以在 CPU 卡死时复位。若软件监视器无条件调用 refresh,硬件狗只证明监视器线程还在运行,不能证明控制回路完成。
10.5.2 健康监视器看什么
健康检查应选择可代表系统进展的信号,例如任务完成序号、消费计数、最近一次有效通信、传感器样本时间和输出更新版本。单纯 heartbeat 可能在死循环中继续翻转;把 progress、last_success、queue_age、lock_wait 和 error_state 组合起来,才能区分‘线程活着’与‘系统做了正确的事’。
C · PROGRESS WATCHDOGtypedef struct { uint32_t last_progress; TickType_t last_kick; } health_t; static bool healthy(const health_t *h, TickType_t now, TickType_t timeout) { return (TickType_t)(now - h->last_kick) <= timeout && h->last_progress != 0; }
监视进展而非线程存活;硬件窗口、复位原因和安全输出仍需目标机验证。
PYTHON · TIMEOUT CLASSIFIERdef classify_timeout(stage, elapsed, budget): if elapsed < 0 or budget <= 0: raise ValueError("invalid timing") if elapsed <= budget: return {"status": "within_budget", "stage": stage} return {"status": "timeout", "stage": stage, "overrun": elapsed - budget}
分类器保留阶段和超时量;它不判断 brownout、硬件复位或真实执行器安全。
10.5.3 超时的分类与证据
超时记录要说明哪个时钟、哪个预算、从哪个事件开始计时以及最后一个已确认的完成点。deadline miss、锁等待、队列拥塞、DMA 不完成、时钟停摆、brownout 和外部设备失联的恢复动作不同。只写‘watchdog reset’会丢失最有价值的阶段信息,也无法判断是否应自动重试。
| 对象 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 健康 | progress/last success | 线程活着就是健康 |
| 故障 | timeout/overload/reset | 所有超时都重试 |
| 恢复 | snapshot/safe/rollback | 喂狗能修复死锁 |
10.5.4 过载、背压与降级
过载策略要在资源耗尽前选择优先级:先限制生产速率,再丢弃可重建数据、合并重复工作、降低采样、暂停非关键日志,最后进入安全态或复位。每一步都要有上限和恢复条件。无限重试、无限扩容或继续接收超出协议窗口的数据,会把暂时拥塞放大成永久失控。
10.5.5 死锁、活锁与饥饿
死锁是相互等待且没有转移,活锁是不断动作但状态不前进,饥饿是某个工作长期得不到资源。CPU 使用率高不代表有进展,队列计数和 heartbeat 也可能在活锁中变化。把进度序号、状态转移、锁图、队列年龄和有效输出一起观察,比只看任务是否运行更可靠。
从左到右是思考顺序,不是把时间、调度、资源和恢复证据压缩成一个“通过”。
10.5.6 故障隔离与安全态
故障隔离的目标是让一个任务的坏指针、超时或协议异常不会直接驱动危险输出。可以使用分层服务、独立任务、边界检查、消息复制、超时断路器和硬件安全输入;但隔离机制本身也消耗时间和内存。安全态要写成可执行动作,例如关闭 PWM、释放继电器、限制速度或停止发送。
10.5.7 复位原因与恢复序列
复位恢复应先读取并保存复位原因、故障快照、配置版本和最后进度,再清理旧状态、初始化时钟/内存/外设、验证关键资源,最后逐步恢复服务。不要在恢复早期立即喂狗或启动全部任务;若同一条件再次失败,要限制重启次数并进入维护或安全模式,避免复位风暴。
10.5.8 本节交付:恢复卡
恢复卡包括 monitor_id、health_signals、period、timeout、window、last_progress、fault_class、snapshot_fields、safe_outputs、reset_cause、retry_limit、backoff、rollback_image、maintenance_mode、host_replay、target_injection 和 stop_conditions。它将看门狗从‘最后一行代码’提升为可审查的故障处理合同。
请围绕“看门狗与过载:失败时怎样活下来”写出对象、输入/释放条件、调度或同步机制、输出与完成语义、一个时间或内存约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、端口、时钟、FreeRTOSConfig、工具链、仪器、负载和观察条件。不要用一次平均延迟、一个 heartbeat、一个队列发送成功或一张截图覆盖没有做过的验证。
06 · REAL-TIME DELIVERY
实时交付:从时间线到可回滚证据
实时交付要把配置、任务预算、同步选择、内存峰值、看门狗策略、trace、测试命令和目标机边界绑在同一个版本上。主机包应让读者在干净 Python 3.11/3.12 环境运行模型和测试;目标机记录则要补齐芯片、端口、时钟、仪器、负载、故障注入、升级和回滚证据。
10.6.1 时间线与最小 trace
最小实时 trace 需要同时记录 release、ready、start、block、wake、finish、deadline、task_id、priority 和 source。若只在任务末尾打印 elapsed,无法知道延迟来自事件产生、排队、抢占、锁、执行还是日志本身。时间戳要使用已声明的单调时基,并记录 tick/频率、单位、溢出处理和采样开销。
10.6.2 运行时统计与取样偏差
运行时统计有观察成本和偏差。周期采样可能漏掉很短的高峰,事件追踪可能改变缓存和调度,printf 可能阻塞或抢占关键任务。CPU 使用率、栈 high-water、队列水位和 p95/p99 都应说明采样窗口、工具版本、是否包含 ISR/idle、是否启用优化以及统计开销,不能把一个百分比当成完整证明。
PYTHON · TRACE SUMMARYfrom collections import defaultdict def summarize(records): by_task = defaultdict(list) for row in records: by_task[row["task"]].append(row["finish"] - row["release"]) return {name: {"count": len(values), "max": max(values)} for name, values in by_task.items()}
主机脚本汇总时间线;目标机仍要绑定时钟、端口、测量开销和输入条件。
SHELL · PROVENANCEpython run_all_tests.py python -c "import hashlib, pathlib; p=pathlib.Path('src/realtime_model.py'); print(hashlib.sha256(p.read_bytes()).hexdigest())" # record FreeRTOSConfig, port, tick, compiler, map/ELF and target scope
命令、版本、哈希、测试输出和未验证范围共同组成实时交付证据。
10.6.3 主机测试与虚拟调度器
主机路径可以用标准库实现 utilization、deadline、队列背压、状态机、内存预算、看门狗和超时分类。读者应能在干净 Python 3.11/3.12 环境运行 `run_all_tests.py`,看到确定性断言和失败样例;这证明软件模型和交付步骤可复现,不证明具体 MCU 的 tick、IRQ、电气或温度行为。
| 对象 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 时间线 | trace/release/finish | 一条平均曲线足够 |
| 来源 | config/toolchain/hash | 二进制名字可追溯 |
| 边界 | host/target/stop | 主机通过就是实机保证 |
10.6.4 目标机测量与工具配置
目标机测量必须写明芯片、板卡、FreeRTOS port、tick rate、时钟源、编译器、优化、探针、GPIO 标记、触发、负载和环境。示波器上的一条边沿只覆盖标记点之间的路径;它不能自动证明整个任务链、所有输入集合或长期稳定性。遇到过热、输出异常、复位风暴或未知电气状态时应立即停止并保存现场。
10.6.5 FreeRTOSConfig、版本与哈希
FreeRTOSConfig、链接脚本、编译器版本、源码提交、依赖锁和生成配置共同决定调度、内存和时间行为。一次构建应保存 source_hash、config_hash、toolchain、port、tick、optimization、map/ELF、测试输出和目标板修订。二进制文件名或构建时间不能代替 provenance,否则无法解释同一源码为何产生不同 deadline 结果。
从左到右是思考顺序,不是把时间、调度、资源和恢复证据压缩成一个“通过”。
10.6.6 实时安全、权限与敏感日志
实时证据可能包含设备序列号、网络令牌、内存地址或内部故障现场。发布包要区分公开样例、调试日志和生产日志,必要时哈希/截断敏感字段,同时保留错误阶段、计数和时间关系。权限也属于合同:谁可以改变优先级、触发 reset、下载镜像、读取 raw trace,都要明确并可审计。
10.6.7 升级、回滚与不可逆动作
升级和回滚要优先保证可启动和安全态。写 Flash、切换镜像、改变时钟或启动执行器都是不可逆或高风险动作,不能靠无限重试。保存旧镜像、版本签名、更新阶段和复位原因;启动后先验证完整性、配置兼容和健康信号,失败时回到已知镜像或维护模式,而不是继续运行半更新系统。
10.6.8 评审、维护与已知限制
评审记录应列出已经运行的命令、输入、输出、最坏样本、目标机测量、已知限制、开放问题和下一步单变量实验。‘通过’要限定条件,例如‘在 host 模型、配置 v1、10 个任务和 reserve=20% 下可调度’,不能升级成‘所有板卡都满足实时性’。边界写得越清楚,维护者越能安全扩展。
10.6.9 本节交付:实时发布卡
实时发布卡包括 manifest、source_hash、config_hash、toolchain、port、tick、task_budgets、sync_policy、memory_budget、watchdog_policy、trace_schema、host_result、target_result、compatibility、rollback、security_scope、known_limits 和 stop_conditions。它把本章所有模型汇总为读者可运行、工程师可测量、产品可回滚的交付物。
请围绕“实时交付:从时间线到可回滚证据”写出对象、输入/释放条件、调度或同步机制、输出与完成语义、一个时间或内存约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,还要补齐芯片、端口、时钟、FreeRTOSConfig、工具链、仪器、负载和观察条件。不要用一次平均延迟、一个 heartbeat、一个队列发送成功或一张截图覆盖没有做过的验证。
资料卡(访问日期:2026-09-04):FreeRTOS 官方文档支撑任务、通知、互斥、事件组和静态/动态分配;Zephyr 官方文档提供调度、线程与工作队列的对照;Linux SCHED_DEADLINE 以 runtime/deadline/period 表达时间预算。通用模型不能替代当前 MCU、FreeRTOS port、时钟、FreeRTOSConfig、板卡、负载、测量工具和真实复位条件。