UCB-CS149-嵌入式系统笔记-全-

UCB CS149 嵌入式系统笔记(全)

01:信息物理系统概述

在本节课中,我们将学习信息物理系统的基本概念,了解其重要性、设计挑战以及未来的发展趋势。我们将从课程介绍开始,逐步深入到信息物理系统的核心原理。

课程介绍与安排

我是Alberto Sangiovanni-Vincentelli,来自瑞典。我将与Edward Lee共同教授这门课程,他也是我们课程用书的合著者。

在开始之前,有几个行政事项需要说明:

  • 所有在候补名单中的本校正式学生都将被录取,但无法接收校外进修学生。
  • 研究生和本科生需分别注册对应编号的课程。
  • 每位学生都必须注册一个实验课。由于人数超额,我们已新增一个实验时段。选课系统将在明天前调整完毕,请务必在周一前完成注册。

现在,让我们进入课程的核心内容。

什么是信息物理系统?

上一节我们介绍了课程的基本情况,本节中我们来看看课程的核心主题——信息物理系统。

信息物理系统是逻辑(例如运行在微处理器上的计算机程序)与物理世界的结合。这与标准计算系统有本质区别。标准计算系统的基本抽象是时间、功耗和操作顺序无关紧要,但这种抽象对于处理与物理世界交互的系统是不适用的。

信息物理系统无处不在,汽车、飞机、生产线、国防系统、航空电子设备等都依赖于内置的电子智能。然而,系统越复杂,出现错误或部件间发生意外交互的可能性就越大,这可能导致严重的后果,例如汽车行业因软件缺陷导致的大规模召回事件。

一个复杂案例:四旋翼飞行器

为了理解信息物理系统的复杂性,让我们看一个例子:自主四旋翼飞行器。

要让这个飞行器自主飞行并完成任务(如跟随地面目标或运送物品),需要解决一系列挑战:

  • 建模与控制:需要对其飞行动力学进行建模和控制。
  • 模式管理:系统有不同的操作模式(如起飞、巡航、着陆),并需管理模式间的转换。
  • 子系统协同:需要协调多个交互的子系统的行为。
  • 多机通信:当存在一个飞行器编队时,需要处理机间通信与协同。
  • 硬件设计:涉及传感器(感知物理世界)和执行器(作用于物理世界)的接口设计。
  • 软件设计:需要编写能够处理并发任务和实时调度的复杂软件。
  • 验证与调试:必须在实际飞行前,通过模型分析和仿真来验证系统的安全性和实时性。

其计算平台可能包含多个并行的微处理器,分别处理低级飞行控制和高级决策任务。它还可能集成了多种传感器(如IMU惯性测量单元、GPS、视觉系统)并采用传感器融合技术,以精确获取自身位置和感知环境。

课程核心:基础、抽象与模型

面对如此复杂的系统,有两种设计方法:一种是“ hacking”(即试错法),这对于简单系统可行,但对复杂系统则难以调试和维护。因此,本课程的重点是第二种方法:基于模型的设计

本课程的核心是教授基础抽象组合规则。技术不断变化,但基本原理是永恒的。我们的教材围绕三个轴线组织:

  1. 建模:用数学方程描述系统行为(描述性模型)或规定系统应有的行为(规定性模型)。工程设计是自上而下(规定)与自下而上(描述)的结合。
  2. 设计:创造实现想法的具体方案。
  3. 分析:理解系统行为及其原因,以便在仿真中发现问题并修正。

行业重要性与经济潜力

信息物理系统为何如此重要?它们不仅已渗透到各个工业领域,更代表着未来的巨大经济潜力。

麦肯锡等咨询公司列出的颠覆性技术中,至少有四项与本课程紧密相关:

  • 物联网
  • 先进机器人技术
  • 自动驾驶与半自动驾驶车辆
  • 云计算(作为支撑技术)

其中,物联网的经济潜力被预估为数万亿美元,远超云计算。谷歌、苹果等科技巨头也正在大力投资这些领域:

  • 谷歌:通过自动驾驶汽车(Waymo)、智能家居(Nest)、先进机器人(Boston Dynamics)和无人机项目,旨在掌控交通、家庭、制造和通信基础设施。
  • 苹果:据信对特斯拉感兴趣,意在结合其在电池技术和高端产品生态方面的优势。

这些投资表明,物理世界与信息世界的融合是未来的核心趋势。

未来趋势:从个人设备到传感云

计算技术的中心正在转移。过去是大型机时代,现在是个人电脑和智能手机的移动接入时代,而未来将是传感云时代。

未来,无数传感器将嵌入我们周围的一切(墙壁、家具、甚至人体),形成一个巨大的传感网络。所有的数据将在云端被收集、合成和分析。这将彻底改变我们的生活和工作方式,例如实现智慧城市管理。同时,生物信息物理系统也在发展,例如通过脑机接口,让猴子仅凭思维就能控制远端的机械臂。

核心挑战:统一异构模型

设计信息物理系统的根本挑战在于如何统一两种截然不同的模型:

  • 物理系统模型:通常用微分方程描述,是连续、并发的。
  • 计算系统模型:通常用C语言等命令式语言描述,其抽象本身没有时间概念,是顺序、离散的。

传统的数字电路设计通过同步抽象(如时钟)成功地将异步的晶体管物理行为与确定性的逻辑功能分离开。我们需要为信息物理系统找到类似的、能够统一连续物理动力学和离散计算逻辑的抽象方法论工具

过去在集成电路设计中,我们通过组合与分解(水平方向)、抽象与细化(垂直方向)以及同步设计等方法论,成功地将芯片复杂度从12个晶体管提升到数十亿个。我们需要将这些原则应用到更复杂的信息物理系统设计中。

课程要求与总结

本节课我们一起学习了信息物理系统的基本概念、重要性、设计挑战及未来展望。本课程的目标是培养批判性思维,提供应对技术变化的基础理论和方法,而非教授特定工具或一时的技术。

以下是课程的一些具体要求:

  • 教材:使用Lee & Seshia所著的《Introduction to Embedded Systems》最新版。
  • 考核:包括作业、实验和课程项目。
  • 项目:通常以4人小组形式进行,从给定项目中选择,鼓励团队间竞争。历史上曾有自主飞行、乐高平衡车等项目。

请记住,成功设计信息物理系统的关键在于避免“即插即祈祷”的方式,而是采用严谨的建模、明确的设计方法和深入的分析。


总结:在本节课中,我们一起学习了信息物理系统的定义、其广泛的应用和巨大的经济潜力。我们通过四旋翼飞行器的例子剖析了其设计复杂性,并指出了课程的核心——学习如何通过建模、设计和分析来应对这些挑战。我们探讨了统一连续物理模型与离散计算模型这一根本难题,并展望了从个人设备到泛在传感云的未来趋势。本课程旨在为你奠定应对这一快速发展的领域所需的基础理论和思维框架。

02:传感器与执行器

在本节课中,我们将学习嵌入式系统如何通过传感器和执行器连接计算世界与物理世界。我们将探讨不同类型的传感器工作原理、如何为它们建模,以及如何驱动执行器。课程将包含核心概念的公式化描述,并确保内容简单易懂。


传感器:连接物理世界

上一节我们介绍了嵌入式系统的基本概念,本节中我们来看看系统如何感知物理世界。传感器是将物理量(如温度、压力、运动)转换为电信号的设备。

以下是几种常见的传感器类型及其工作原理:

  • 磁力计:利用霍尔效应。当电流流过处于磁场中的导体板时,电子会因磁场作用发生偏转,从而在板的上下两侧产生可测量的电压差。公式表示为:V_H = (I * B) / (n * e * d),其中 V_H 是霍尔电压,I 是电流,B 是磁感应强度,n 是电荷载流子密度,e 是电子电荷,d 是导体厚度。
  • 加速度计:核心是一个弹簧-质量块系统。固定框架与被测物体相连,质量块通过弹簧与框架连接。加速度会导致质量块与框架间的距离变化,通过测量该距离(通常通过测量电容变化实现)即可获得加速度值。其动态遵循牛顿第二定律:F = m * a
  • 陀螺仪:用于测量角速度或方向变化。机械陀螺仪利用高速旋转转子的定轴性。现代微机电系统陀螺仪则可能使用光学(如萨格纳克效应)或其他原理来检测旋转。


传感器建模与挑战

理解了传感器的基础后,我们需要为其建立数学模型,以预测其行为并设计可靠系统。建模有助于理解传感器的局限性和误差来源。

以下是使用传感器时需要考虑的几个关键问题:

  • 方向与重力:加速度计无法区分线性加速度和重力加速度分量。设备倾斜时,重力会在测量轴上产生分量,导致读数偏差。
  • 积分误差(航位推算):通过积分加速度来估算位置和速度时,任何微小的测量偏差都会随时间累积,导致误差呈二次增长。位置 x(t) 可通过双重积分得到:x(t) = x0 + ∫(v0 + ∫ a(t) dt) dt
  • 动态范围与灵敏度:传感器需要能够测量预期范围内的信号,同时对小信号足够敏感。例如,安全气囊中的加速度计需要检测剧烈的碰撞加速度。
  • 采样与混叠:以过低频率对连续信号采样会导致高频信号被错误地呈现为低频信号。根据奈奎斯特采样定理,采样频率必须至少是信号最高频率的两倍。
  • 噪声:所有传感器输出都包含噪声,需要进行信号调理(如滤波)以获得可用数据。

执行器:施加影响于物理世界

传感器让我们感知世界,执行器则让我们改变世界。执行器将电信号(通常来自处理器)转换为物理动作,如运动、发光或发声。

驱动执行器,尤其是电机等大功率设备时,需要特别注意功率接口,因为微控制器引脚只能提供很小的电流。

以下是驱动执行器(以直流电机为例)的要点:

  • 功率需求:直接连接大功率负载(如电机、LED)到微控制器引脚会损坏芯片,必须使用外部驱动电路(如晶体管、电机驱动模块)。
  • 脉宽调制控制:对于电机等惯性负载,无需复杂的线性功率放大器。使用脉宽调制(PWM)是高效且简单的控制方法。通过快速开关电压,并改变占空比(高电平时间比例),可以控制电机的平均电压和速度。占空比公式为:D = T_on / (T_on + T_off)
  • 电机模型:直流电机的行为可以用一组方程描述:
    1. 电气方程(基于基尔霍夫电压定律):V = R*i + L*(di/dt) + K_e*ω
    2. 机械方程(基于牛顿第二定律旋转形式):J*(dω/dt) = K_t*i - B*ω - τ_load
      其中,V是电压,i是电流,R是电阻,L是电感,K_e是反电动势常数,ω是角速度,J是转动惯量,K_t是转矩常数,B是阻尼系数,τ_load是负载转矩。

视觉与其他传感方式

除了上述传感器,摄像头提供了丰富的环境信息。机器视觉不一定要模仿人类视觉的矩形视场,可以针对特定任务进行优化。

例如,运动捕捉系统使用布置在空间中的多个特殊摄像头,追踪物体上的红外反光标记点。这些摄像头的像素排列并非用于生成给人看的图像,而是为了精确计算标记点在三维空间中的位置,效率极高。


总结

本节课中我们一起学习了嵌入式系统中传感器与执行器的核心知识。我们了解了传感器(如磁力计、加速度计)如何将物理量转换为电信号,以及为其建立模型的重要性。我们探讨了使用传感器时的挑战,如重力干扰、积分误差和混叠。我们还学习了如何驱动执行器,特别是使用PWM高效控制电机的方法,并介绍了其数学模型。最后,我们看到了像运动捕捉系统这样非传统、任务优化的传感方式。掌握这些原理是设计可靠、高效的嵌入式与信息物理系统的基础。

03:内存架构 🧠

在本节课中,我们将学习嵌入式系统中至关重要的内存架构。我们将探讨不同类型的内存、内存的组织方式,以及如何在C语言程序中管理内存。理解这些概念对于设计高效、可靠的嵌入式系统至关重要。


内存类型:易失性与非易失性

上一节我们介绍了课程的整体安排,本节中我们来看看内存的基本类型。在嵌入式系统中,内存的选择和特性直接影响系统的行为和可靠性。

内存主要分为两大类:易失性内存和非易失性内存。

  • 易失性内存:断电后数据会丢失。最常见的例子是RAM(随机存取存储器)。动态RAM(DRAM)尤其“易失”,它需要定期刷新(例如每64毫秒一次)来保持数据,否则即使不断电也会丢失数据。静态RAM(SRAM)不需要刷新,但断电后数据同样会丢失。
  • 非易失性内存:断电后数据依然保留。历史上出现过多种技术:
    • EPROM:可擦除可编程只读存储器,需要用紫外线照射来擦除。
    • EEPROM:电可擦除可编程只读存储器,可以通过电信号擦除。
    • 闪存:当今最主流的非易失性存储器之一。它有一个重要特性:不能直接覆盖单个位置,而必须先擦除整个块,然后再写入。此外,每个存储单元的写入次数有限(现代闪存约为10万次)。闪存因其与处理器工艺兼容、功耗要求一致而变得非常流行。
    • 磁盘驱动器:也是非易失性的,但由于其机械部件,在尺寸、功耗和可靠性方面对嵌入式系统可能带来挑战。

内存映射与地址空间

理解了内存的物理特性后,我们需要看看软件是如何访问这些硬件的。这涉及到逻辑地址空间如何映射到物理地址空间。

处理器通过地址来访问内存。例如,一个32位处理器可以寻址 2^32(约40亿)个内存位置,即4GB的地址空间。然而,这个地址空间是一个逻辑概念,并不直接对应芯片上实际的物理内存容量。

内存映射图定义了逻辑地址空间的布局。例如,在ARM Cortex-M3处理器的架构中:

  • 地址 0x0000 0000 开始映射到闪存。
  • 地址 0x2000 0000 开始映射到静态RAM。
  • 其他地址区域可能映射到动态RAM或外部设备。

关键点在于:

  1. 逻辑与物理的分离:内存映射图是架构规范,具体的芯片实现可能只有较少的内存,并映射到这个逻辑空间的某个部分。
  2. 内存映射I/O:某些地址可能并不对应内存,而是映射到外部设备(如模数转换器)的寄存器。读写这些地址实际上是在与硬件外设通信。
  3. 直接硬件控制:在无操作系统的“裸机”嵌入式编程中,程序员可以直接读写任何地址。如果写入不存在的内存地址或错误地写入设备寄存器,硬件会照做,这可能导致系统崩溃或不可预测的行为。编写设备驱动时需要格外小心。

内存组织方式:静态、栈与堆

现在我们已经知道如何通过地址访问内存,接下来看看程序运行时,变量和数据在内存中是如何组织和生存的。在C语言中,主要有三种内存分配方式。

以下是三种主要的内存分配区域:

  • 静态分配内存:在程序整个运行期间都存在的变量。编译器在编译时就为它们确定了固定的内存地址。
    • 示例:在函数外部定义的全局变量,或在函数内部用 static 关键字声明的变量。
    • 注意:静态变量的初始值在程序加载时写入内存。如果按下复位键重启程序,这些变量不会重新初始化,而是保留之前运行的值,这可能是一个隐蔽的错误来源。
  • :用于存储函数调用时的局部变量、参数和返回地址。内存随着函数调用自动分配(入栈),随着函数返回自动释放(出栈)。
    • 工作原理:有一个栈指针寄存器指向栈顶。函数调用时,栈指针移动以分配空间;函数返回时,栈指针移回以释放空间。
    • 风险:如果递归过深或局部变量过大,可能导致栈溢出,覆盖其他内存区域(如程序代码、静态变量甚至I/O寄存器),造成灾难性后果。高级处理器有内存保护机制(如触发“段错误”),但低端微控制器可能没有。
  • :用于动态内存分配。程序可以在运行时请求一块特定大小的内存(使用 malloc),并在不再需要时释放它(使用 free)。
    • 管理:需要运行时环境(可能是操作系统或一个小型内核)来维护堆的空闲和已用块信息。
    • 嵌入式系统考量:在长时间运行的嵌入式系统中,必须小心管理堆内存,避免内存泄漏(分配后未释放),否则最终会导致内存耗尽和系统崩溃。

C语言中的内存操作实例与分析

理论需要结合实践。让我们通过一些具体的C代码示例,来加深对内存分配和操作的理解。

以下是几个关键的内存操作示例及其解释:

  • 示例1:静态变量赋值

    char x;
    void foo(void) {
        x = 0x20;
    }
    

    含义:编译器为8位变量 x 分配一个静态内存地址。执行 foo() 时,将值 0x20 写入该固定地址。

  • 示例2:指针与地址操作

    void foo(void) {
        char y;
        char *x;
        x = &y;
        *x = 0x20;
    }
    

    含义y 是栈上的局部变量。x 是指针变量(在栈上分配,大小取决于地址宽度,如16位)。x = &y;y 的地址存入 x*x = 0x20;y 的地址写入值 0x20。这等价于 y = 0x20;

  • 示例3:有缺陷的程序——返回栈地址

    int* foo(int a) {
        int result;
        result = a * 2;
        return &result; // 严重错误!
    }
    void bar(void) {
        int *z;
        z = foo(10);
        // 此时使用 *z 的值是不可靠的
    }
    

    缺陷分析:函数 foo 返回了局部变量 result 的地址。但 result 分配在栈上,当 foo 返回时,其栈帧被释放,result 所占用的内存可能被后续的函数调用或中断所覆盖。因此,bar 中通过指针 z 访问到的值将是不可预测的垃圾数据。这是一个非常隐蔽的错误,因为程序在简单测试时可能偶然正常工作。

  • 示例4:递归导致的栈溢出

    int z = 0x10;
    void f(int y) {
        if (y > 0) {
            f(y-1);
        }
    }
    int main(void) {
        f(0x4FF);
        // z 的值可能已被改变,程序甚至可能崩溃
    }
    

    分析:函数 f 递归调用自身很多次。每次调用都会在栈上压入返回地址和局部变量(尽管这里没有显式局部变量,但编译器可能仍会分配)。递归深度 (0x4FF 次) 足够大,会导致栈不断增长,最终溢出并覆盖其他内存区域,包括静态变量 z,甚至可能覆盖程序计数器,导致跳转到随机地址执行,使程序完全崩溃。


总结与回顾

本节课中我们一起学习了嵌入式系统内存架构的核心知识。

我们首先区分了易失性内存非易失性内存的特性与应用场景。然后,我们探讨了逻辑地址空间如何通过内存映射与物理硬件关联,并理解了内存映射I/O的概念。接着,我们深入分析了程序运行时内存的三种组织方式:静态分配,以及它们在C语言中的体现和潜在风险。最后,通过代码实例,我们具体分析了如何操作内存,并识别了如“返回栈地址”和“栈溢出”等常见且危险的编程错误。

掌握这些内存管理的基本原理,是编写高效、稳定嵌入式系统软件的基础。在接下来的课程中,我们将继续探讨与硬件紧密交互的其他主题。

04:传感器与执行器接口

在本节课中,我们将学习如何将嵌入式系统的数字世界与物理世界连接起来。我们将探讨内存架构的收尾内容,并深入了解如何通过GPIO、串行接口等方式与传感器和执行器进行通信。

内存层次结构

上一节我们讨论了嵌入式系统中使用的内存技术。本节中,我们来看看如何组织这些技术以形成高效的内存层次结构。

典型的处理器内存层次结构从寄存器组开始,这是最小但最快的内存。接下来是缓存或暂存器内存。两者的核心区别在于:缓存的内容由硬件管理,而暂存器的内容由软件管理。

在缓存中,数据以“行”为单位组织。一个地址被分为标签(Tag)组索引(Set Index)块偏移(Block Offset)。硬件使用组索引找到对应的缓存行,然后比较标签以判断是否命中。如果是直接映射缓存,每组只有一行;如果是组相联缓存,则每组有多行,需要更复杂的硬件(如内容可寻址存储器)来并行查找。

当发生缓存未命中时,硬件需要从主存获取数据,并决定替换缓存中的哪一行。常见的替换策略包括最近最少使用(LRU)先进先出(FIFO)

然而,缓存虽然能提升平均性能,却会导致访问时间变得不可预测,这在需要严格控制时序的嵌入式系统中可能是个问题。因此,嵌入式系统中有时会直接关闭缓存以获得可重复的行为。

连接物理世界

现在,让我们转向本节课的核心:如何连接数字世界与物理世界。物理世界是连续、并发的,而数字计算是离散、顺序的。桥接这两个世界是信息物理系统的核心挑战。

一个典型的嵌入式开发板(如BeagleBone或Arduino)提供了许多数字和模拟输入输出引脚。通过配置内存映射寄存器,我们可以控制这些引脚的功能。

以下是配置引脚功能的几种主要方式:

  • 通用输入输出(GPIO):软件可配置为输入或输出。作为输出时,可以驱动引脚为高电平或低电平;作为输入时,可以读取外部电路施加到引脚上的电平。
  • 串行外设接口(SPI):一种高速的全双工串行通信总线。
  • 通用异步收发器(UART):实现异步串行通信(如RS-232)的硬件。
  • USB:通用的高速串行总线。

通用输入输出详解

GPIO是最基础的接口方式。当配置为输出时,常采用开集电极电路。软件写“1”到控制寄存器会使晶体管导通,将引脚拉至低电平(接地);写“0”则使晶体管关闭,此时通过外部上拉电阻可将引脚拉至高电平。

开集电极设计允许实现“线或”逻辑,即多个输出引脚可以直接连接在一起,任何一方拉低都会使整条线变低。

设计注意事项:连接外部元件(如LED)时,必须根据欧姆定律计算合适的限流电阻值。公式为 R = (Vsupply - VLED) / ILED。电阻值过小会超过GPIO引脚的最大电流定额,损坏处理器;电阻值过大则可能导致LED亮度不足。

串行通信:以RS-232为例

串行通信一次传送一位数据。RS-232是一个经典的异步串行协议,其数据帧以起始位开始,然后是数据位(通常8位),最后是停止位。通信双方需要预先约定相同的波特率(如9600 bps)。

接收端在检测到起始位后,会按照约定的时间间隔对信号进行采样,以获取数据位。波特率的上限主要受限于两端时钟晶振的精度误差。误差会随着每个位的采样而累积,因此限制了单次传输的位数和最大速率。

在现代嵌入式系统中,UART硬件负责处理串行化的细节。软件通过读写内存映射寄存器来发送和接收数据。

以下是一个通过UART发送数据的C代码示例,它演示了“忙等待”的方式:

while (!(UCSR0A & 0x20)); // 等待“发送缓冲区空”标志位就绪
UDR0 = byte_to_send;       // 将数据写入UART数据寄存器

这种方式的效率很低,因为在等待硬件就绪的数百微秒内,处理器无法执行其他任务,浪费了计算资源和电能。

实验平台架构简介

在本课程的实验中将使用的平台采用了一种混合架构。它包含一个运行Linux的双核ARM处理器,以及一个在FPGA上实现的32位MicroBlaze“软核”处理器。

  • ARM处理器(运行Linux):提供高级、稳定的控制环境,便于远程访问和调试。
  • MicroBlaze处理器(无操作系统):用于进行底层的“裸机”编程,让你直接与内存映射寄存器和硬件外设交互。

你需要通过查阅头文件和数据手册来理解类似 DIO_15_8 这样的宏定义,它们对应着特定的硬件控制寄存器地址。

网络与时钟同步

最后,我们提升一个层次,看看网络通信。在嵌入式领域,存在许多特定领域的网络技术,如用于汽车的控制器局域网(CAN)。这些技术通常为满足实时性和确定性时序而设计。

时钟同步是实现高效、可靠网络通信的关键。如果网络中的各个节点能够保持时间同步,它们就可以协调动作,例如在无线传感器网络中,节点可以同时唤醒、通信,然后同时进入休眠,从而极大节省能耗。

IEEE 1588(精确时间协议)等技术可以实现纳秒甚至皮秒级的时钟同步。高精度的时钟同步不仅优化了网络性能,甚至在纠正重大科学实验误差(如中微子超光速测量事件)中发挥了关键作用。我们将在下一节课中详细讲解时钟同步的工作原理。

总结

本节课我们一起学习了嵌入式系统与物理世界接口的核心知识。我们回顾了内存层次结构,深入探讨了GPIO和串行通信(如RS-232)的工作原理与编程方式,介绍了实验平台的混合架构,并引出了网络通信中至关重要的时钟同步概念。理解这些底层接口机制,是构建可靠、高效嵌入式系统的基础。

05:中断机制

在本节课中,我们将要学习嵌入式系统中的中断机制。中断是处理器处理并发事件的核心技术,它允许处理器在执行主程序的同时,响应外部硬件事件。我们将探讨中断的工作原理、如何设置中断服务程序,以及在使用中断时需要注意的并发问题。

上一节我们介绍了基本的I/O轮询方法,本节中我们来看看如何利用中断机制更高效地处理I/O。

中断的基本概念

中断本质上是一种低级的并发机制,存在于所有现代处理器中。它允许处理器在执行主程序时,被外部事件(如引脚电压变化)打断,转而去执行一个特定的短程序(中断服务程序),执行完毕后再返回原程序继续执行。

核心流程可以用以下伪代码描述:

主程序执行中...
外部事件发生 -> 触发中断
处理器保存当前程序计数器(PC)和寄存器状态
处理器跳转到中断向量表指定的地址(即中断服务程序入口)
执行中断服务程序(ISR)
执行“从中断返回”指令
处理器恢复之前保存的PC和寄存器状态
主程序从被打断处继续执行

轮询与中断的对比

在深入中断细节之前,我们先回顾一下轮询策略。在安全性要求极高的系统(如飞机飞控系统)中,为了获得确定性的行为,通常会禁用中断和线程,采用单线程轮询的方式。

以下是轮询方式的典型代码结构:

int main() {
    // 初始化设置
    setup();
    while(1) {
        // 主循环,不断查询各个设备状态
        if (device1_ready()) handle_device1();
        if (device2_ready()) handle_device2();
        // ... 查询更多设备
    }
}

轮询的优点是行为确定、可控,但缺点是效率低下,因为CPU大部分时间都在检查那些“未就绪”的设备。

中断则解决了效率问题。当硬件设备就绪时,它会主动通知CPU,CPU可以在此期间处理其他任务。

中断的硬件机制

要理解中断,需要了解其在硬件层面的工作原理。不同的处理器架构其中断机制略有不同,但核心思想相似。

以8位AVR微控制器(如Arduino所用)为例,其内存起始部分有一个“中断向量表”。

内存地址     用途
0x0000      复位向量 (Reset)
0x0002      外部中断0向量 (IRQ0)
0x0004      外部中断1向量 (IRQ1)
...

当发生对应中断(如IRQ0引脚电压变化)时,硬件会自动将程序计数器(PC)设置为对应向量地址(如0x0002)。因此,程序员需要在该地址处放置一条跳转指令,指向实际的中断服务程序。

在32位处理器(如MicroBlaze或ARM)上,原理类似,但地址空间更大,通常可以直接在向量表中存放中断服务程序的入口地址。

关键点:中断发生时,硬件通常只做最小化工作(如保存PC),而保存和恢复通用寄存器的工作,通常由编译器在生成中断服务程序代码时自动添加。

使用定时器控制时间

在C语言中,我们无法直接控制指令执行的时间。为了进行时间相关的操作(如定时执行任务),我们需要借助一个称为“定时器”的外部硬件外设。

其工作流程如下:

  1. 通过内存映射寄存器配置定时器,设定一个时间间隔(如16毫秒)。
  2. 启动定时器,硬件开始独立于CPU进行倒计时。
  3. CPU继续执行主程序。
  4. 当设定的时间到达时,定时器硬件会触发一个中断。
  5. CPU暂停当前任务,执行与该中断关联的服务程序。
  6. 在中断服务程序中执行预定的定时任务。

实战:设置中断服务程序

在实践中,我们通常使用厂商提供的库函数或宏来设置中断,但这些代码背后仍然是操作内存映射寄存器。理解其本质至关重要。

以下是一个在AVR上设置1毫秒定时器中断的“魔法代码”示例:

#include <avr/io.h>
// 配置定时器1,产生1ms周期中断
TCCR1A = 0x00;
OCR1A = 71;
TIMSK1 = (1 << OCIE1A);

这些看似神秘的标识符(如TCCR1A)实际上是通过头文件宏定义,最终被展开为对特定内存地址的写入操作。例如,TCCR1A = 0x00;可能被展开为:

*((volatile uint8_t *)0x80) = 0x00;

这行代码的含义是:向内存地址0x80(这是一个控制定时器的寄存器)写入值0x00

并发编程的陷阱与volatile关键字

当主程序和中断服务程序共享变量时,就构成了一个简单的并发程序。这会引入复杂性和潜在的错误。

考虑以下有缺陷的代码,它试图利用中断实现一个2秒的延时:

volatile uint32_t timer_count = 0; // 声明为volatile

void ISR() { // 中断服务程序,每毫秒触发一次
    if (timer_count > 0) {
        timer_count--;
    }
}

int main() {
    // ... 初始化中断,设置为1ms触发一次ISR
    timer_count = 2000; // 等待2000毫秒,即2秒
    while(timer_count != 0) {
        // 执行一些操作
    }
    // 2秒后继续...
}

这段代码存在一个严重的竞态条件漏洞。如果timer_count的当前值为1,而中断恰好在主程序检查while(timer_count != 0)之后、但在它读取timer_count值之前发生,中断服务程序会将timer_count减为0。但主程序已经判断条件为真(非零),并进入循环体。更糟糕的是,如果timer_count是无符号整数,从中断返回后,主程序可能执行timer_count--,导致其下溢变成一个非常大的数,从而使循环远远超过2秒。

volatile关键字的作用:它告诉编译器,这个变量的值可能会被程序本身之外的代理(如中断服务程序)改变。因此,编译器不能对这个变量进行优化(例如,将其值缓存到寄存器中),每次访问都必须从内存中重新读取。在上面的例子中,如果没有volatile,编译器可能会认为timer_count在循环中不会改变,从而将循环优化成死循环。

使用中断时还需注意:

  • 中断服务程序应尽量短小,以减少对主程序时序的干扰。
  • 注意中断嵌套:高优先级中断能否打断低优先级中断服务程序,取决于处理器配置。
  • 共享数据访问:对共享变量的读写可能不是原子的(例如,32位赋值在8位处理器上需要多个周期),中断可能发生在中间状态,导致数据不一致。

总结

本节课中我们一起学习了嵌入式系统的中断机制。我们了解到中断是一种强大的并发处理工具,能有效提高CPU利用率,但它也引入了非确定性和复杂的并发问题,使得程序调试和验证变得困难。我们探讨了中断的硬件原理、如何通过定时器进行时间控制、如何设置中断服务程序,并重点分析了并发编程中常见的陷阱,如竞态条件和volatile关键字的重要性。记住,对于安全关键系统,往往需要避免使用中断以获得完全的确定性。理解这些底层机制,将为我们后续学习更高级的并发模型和系统分析打下坚实基础。

06:基于模型的设计

在本节课中,我们将学习嵌入式视觉(Embedded Vision)的概念、其重要性、工作原理以及实现挑战。嵌入式视觉是计算机视觉在嵌入式系统中的实际应用,它正变得日益重要,因为它为机器提供了感知和理解物理世界的能力。


概述:为什么嵌入式视觉很重要?

从神经科学的角度来看,视觉占据了人类大脑约三分之一到一半的容量。这暗示了两点:第一,视觉在计算上非常复杂;第二,视觉极其重要。对于机器而言,嵌入式视觉同样如此——它计算密集,但能为系统带来巨大的价值。

过去,计算机视觉技术复杂且昂贵,主要用于高端制造和军事等小众领域。然而,随着处理器和图像传感器技术的进步,现在我们已经能够在低成本、低功耗的嵌入式设备中实现强大的视觉功能。这类似于无线网络(Wi-Fi)在过去20年的发展历程:从昂贵复杂到无处不在。

嵌入式视觉能够极大地提升机器对环境的感知能力,使其更安全、更易用、功能更强大。接下来,我们将通过几个实例来了解嵌入式视觉的实际应用。

以下是几个创新的嵌入式视觉产品示例:

  • OrCam设备:一款为视障人士设计的辅助设备。它通过安装在眼镜上的摄像头捕捉图像,用户可以用手指指向感兴趣的物体(如钞票、路牌、菜单),设备会通过语音告知用户该物体是什么。
  • 戴森360 Eye扫地机器人:与早期依靠碰撞感知的扫地机器人(如Roomba)不同,这款机器人使用3D视觉技术实时构建房间的3D地图。这使得它能够规划出高效、无碰撞的清洁路径,显著提升了清洁效率。
  • Ractiv设备:一个售价约75美元的小型设备,可以将任何平面变成触摸屏。它通过3D视觉技术追踪手指在平面上的动作,实现了低成本、低功耗的交互解决方案。

这些例子展示了嵌入式视觉如何为产品带来全新的功能。接下来,我们将探讨嵌入式视觉系统是如何工作的。


嵌入式视觉系统的工作原理

上一节我们看到了嵌入式视觉的应用实例,本节中我们来看看其背后的基本工作原理。一个典型的嵌入式视觉处理流程可以看作一个前馈流水线。

处理流程从图像采集开始。随后,系统通常需要对图像进行优化或增强,因为在现实世界中,成像条件往往不理想(如光线不足、镜头畸变、运动模糊等)。这与工厂自动化中可控的成像环境截然不同。

图像优化步骤可能包括对比度增强、降噪、色彩校正和镜头畸变校正等。完成图像优化后,系统进入信息提取阶段,其目标是将像素数据转换为有意义的对象。

以下是典型的处理流水线阶段:

  1. 图像获取与增强:获取原始图像并进行预处理,以改善图像质量并提取最大信息量。
  2. 特征提取:从增强后的图像中检测低级特征,例如角点、边缘。这些特征是识别更大对象的基础。
  3. 对象识别:将低级特征聚合起来,识别出完整的对象(如人脸、车辆)。
  4. 场景理解与推理:对识别出的对象进行推理,例如跟踪对象的运动轨迹、判断其属性或意图。

在这个流水线中,有两个关键趋势:数据速率急剧下降算法复杂度急剧上升。前端处理涉及每个像素,数据量巨大但算法相对简单(如滤波、色彩变换)。后端处理的对象数量很少,但算法非常复杂,可能涉及大量启发式方法和大量代码。同时,数据类型也从前端简单的整型(如8位像素值)向后端更复杂的类型(如用于3D映射的浮点数)转变。这种计算上的异构性对处理器提出了挑战。

然而,上述流程是一个高度简化的视图。现实中,算法要复杂得多,并且经常包含反馈回路。这是因为在视觉领域,我们缺乏像无线通信那样精确的物理数学模型。视觉本质上是一个实验性领域:算法在实验室中开发,在真实世界中测试、失败、分析原因、改进,并不断循环。

这种缺乏模型的情况、恶劣的成像条件、巨大的算法参数设计空间以及所需的多学科知识(光学、传感器、算法、处理器等),共同构成了嵌入式视觉在算法和功能层面上的主要挑战。


实现挑战与处理器选择

上一节我们讨论了算法层面的挑战,本节中我们来看看将算法部署到实际产品中时面临的实现挑战,特别是处理器的选择。

核心挑战在于:我们需要极高的计算性能(通常达到每秒数百亿次操作),但同时要求低成本、低功耗,并且处理器必须是可编程的。可编程性至关重要,因为视觉算法缺乏标准且迭代迅速,固定功能的硬件(ASIC)一旦制造完成就无法更新算法,难以适应快速变化。

通用CPU(如x86、ARM Cortex-A系列)虽然可编程且性能强大,但在能效(性能/瓦特)和成本效率(性能/美元)方面往往无法满足要求。全速运行时会功耗过高、发热严重。

因此,常见的解决方案是采用异构处理架构。在这种架构中,一个通用CPU负责处理控制流和复杂度高但计算量相对较小的代码(约占代码量的80%),而将计算密集的核心算法(约占代码量的20%)卸载到更专用、更高效的并行处理引擎上。

以下是几种常见的用于嵌入式视觉的异构处理单元:

  • 移动应用处理器中的加速器:现代手机SoC中除了CPU,通常还集成GPU、DSP和专用的图像信号处理器(ISP)。这些单元能效更高,适合运行视觉算法中的并行计算任务。
  • 通用GPU(GPGPU):利用图形处理单元进行通用计算。随着OpenCL等开放标准编程框架的普及,在嵌入式GPU上部署视觉算法变得更加可行。
  • 面向视觉的DSP:一些公司提供专门为视觉计算优化的可授权DSP IP核(如CEVA、Cadence Tensilica),或专门的视觉处理器芯片(如Movidius)。
  • FPGA:现场可编程门阵列能提供极高的能效和灵活性。现在通过高级综合(HLS)工具或OpenCL,可以用高级语言为FPGA编程,降低了开发门槛。

选择正确的处理器组合并进行高效的软件映射,是成功实现嵌入式视觉产品的关键。


实例分析:车道偏离预警系统

为了更具体地理解,让我们分析一个常见的嵌入式视觉应用:汽车高级驾驶辅助系统(ADAS)中的车道偏离预警(LDW)

该系统通过安装在挡风玻璃后的摄像头监测道路车道标记。其算法流程大致如下:

  1. 图像预处理:校正镜头畸变,确保直线在图像中看起来是直的。
  2. 边缘检测:使用如Canny边缘检测等算法,从图像中提取出所有边缘。这本质上是一种2D FIR滤波,计算量大。
  3. 边缘细化:通过形态学操作(如边缘细化)清理边缘图像,去除噪声。
  4. 直线检测:应用霍夫变换(Hough Transform)从边缘图像中检测出可能的直线。这是一种投票机制,计算量也很大。
  5. 车道标记识别:应用启发式规则筛选直线,例如根据位置(应在路面上)、颜色(白或黄)进行过滤。
  6. 跟踪与预测:跨视频帧跟踪候选车道线,使用卡尔曼滤波等算法预测车辆相对于车道的运动趋势,从而在车辆即将偏离车道时提前发出警告。

这个流程从每秒处理数百万像素开始,到最后仅处理几十条候选线,数据量大幅下降,但算法逻辑变得更加复杂。这只是一个起点,真正的系统需要在各种路况、天气和光照条件下进行海量测试,并持续迭代优化算法。像Mobileye这样的行业领导者,已在算法开发上投入了数千人年,并且这个过程仍在继续。


资源与总结

本节课中我们一起学习了嵌入式视觉。我们了解到,由于处理器和传感器技术的进步,为机器添加“视觉”能力正变得日益可行和经济。嵌入式视觉通过提升机器对环境的感知,能够实现前所未有的新功能和应用,从辅助设备到智能家电再到汽车安全。

然而,实现嵌入式视觉也面临诸多挑战:计算需求高、成像条件复杂、算法快速迭代以及需要跨学科优化。在实现时,通常需要采用异构处理架构,在通用CPU和专用加速器(如GPU、DSP、FPGA)之间合理分配任务,以平衡性能、功耗、成本和编程复杂性。

如果您希望深入了解嵌入式视觉的更多技术细节、实践案例、可用处理器和工具,可以访问嵌入式视觉联盟(Embedded Vision Alliance) 的网站。该联盟提供了大量免费的教育资源,包括技术讲座、教程文章和代码示例。

此外,对于希望深入钻研的同学,推荐《Computer Vision: Algorithms and Applications》等书籍,以及关注像“计算机视觉度量”这类更侧重实际实现细节的参考资料。


总结:嵌入式视觉是一个快速发展的领域,它结合了复杂的算法和先进的硬件,旨在为广泛的嵌入式设备赋予感知和理解周围世界的能力。对于未来的嵌入式系统工程师来说,掌握这方面的知识将极具价值。

09:扩展与定时自动机

在本节课中,我们将开始讨论离散行为模型,特别是将深入探讨一种在各类应用中广泛使用的行为数学模型——有限状态机。

概述:什么是有限状态机?

有限状态机是一种描述系统行为的数学模型。例如,您手机屏幕的控制、自动售货机在您投入硬币并弹出商品时的逻辑,本质上都是由有限状态机控制的。接下来,我们将了解其工作原理及其构成。

系统与执行器模型

在深入有限状态机之前,我们先回顾一下执行器的通用定义。一个执行器是一个数学对象。首先,系统是一个函数,它接受一个输入信号并产生一个输出信号。因此,系统的定义域和值域都是信号集合,而信号本身也是函数。这是一种从函数到函数的系统行为功能视图。

为什么采用这种模型?因为它处理起来更方便,尤其是在涉及系统组合时。在这些功能模型中,可以有描述输入信号与输出信号之间可能关系的参数。例如,一个放大器根据其放大倍数会产生不同的输出,这个倍数就是一个参数。

数学上,系统 S 的模型可以表示为:S: X -> Y,其中 XY 都是函数空间。

离散系统与信号

离散系统处理的是离散信号。离散信号的直观概念是,其值不是连续的,而是从一个值跳变到下一个值。通常,我们习惯于认为离散信号具有有限个取值级别,但这并非必需。离散的关键在于,在任意两点之间没有无限多个值。

因此,一个离散模型就是将离散信号映射为离散信号。

示例:车库计数器

我们将使用一个贯穿课程的示例:车库计数器。它的作用是跟踪车库中剩余的车位数量。例如,在机场停车场,你会看到数字“22”,表示该区域有22个空位;如果显示红色标志,则表示没有空位。

这个模型包含两个检测器:一个检测车辆到达,输出“到达”信号;另一个检测车辆离开,输出“离开”信号。那么,“到达”信号需要携带什么信息呢?我们不需要一个实数,只需要知道是否有车到达。同样,“离开”信号也只需要知道是否有车离开。这种只有“存在”或“不存在”两种状态的信号被称为纯信号。纯信号可以用布尔代数编码(例如,0表示不存在,1表示存在),但其语义核心是事件的发生与否。

因此,“到达”和“离开”都是纯信号,其值域为集合 {缺席, 存在}。计数器则对这两个信号进行代数求和:当“到达”信号出现时,计数值加一;当“离开”信号出现时,计数值减一。我们关心的是剩余车位数量,其输出是自然数集合 {0, 1, 2, ...} 中的一个值,表示已被占用的车位数量。

反应的概念

反应是一个直观概念:系统接收到输入后,会做出反应。大多数嵌入式系统都是反应式系统。它们存在于环境中,当环境产生某些事件时,系统会做出反应。例如,汽车的防抱死制动系统在您踩下刹车踏板时就会做出反应。

当一个系统反应时,它会做两件事:

  1. 改变其内部状态。
  2. 产生一些输出。

在车库计数器的例子中,可能的输出是显示剩余或已占用的车位数量。状态的改变描述了系统行为模式的变化,而输出是独立的信息。在数学模型中,我们需要同时提供状态信息和输出信息。

有限状态机的形式化定义

现在,我们可以将上述数学模型编码为有限状态机的形式化定义。

有限状态机由以下部分组成:

  • 状态集合 (Q): 一个有限的、非空的集合。在车库例子中,状态可以是 {0, 1, 2, ..., n},表示已占用的车位数量。
  • 输入字母表 (Σ): 所有可能输入事件的集合。在我们的例子中,是“到达”和“离开”信号的所有可能组合。
  • 输出字母表 (Ω): 所有可能输出值的集合。在我们的例子中,是自然数集合。
  • 转移函数 (δ): 定义了在给定当前状态和输入条件下,系统将转移到哪个下一个状态。其形式为 δ: Q × Σ -> Q
  • 输出函数 (λ): 定义了在给定当前状态和输入条件下,系统将产生什么输出。其形式为 λ: Q × Σ -> Ω
  • 初始状态 (q₀): 系统开始运行时的状态。在我们的例子中,初始状态是 0(车库为空)。

状态本质上是系统过去所有历史信息的摘要。要预测系统未来的行为,只需要知道当前状态和未来的输入,而不需要知道过去的所有细节。

图形化表示:状态图

有限状态机通常用状态图来表示。状态图中的节点代表状态,弧线代表状态之间的转移。

每个转移弧线上通常有两个标签,格式为 守卫 / 动作

  • 守卫: 一个逻辑条件,指定在什么输入条件下可以发生此转移。
  • 动作: 转移发生时产生的输出。

在车库计数器的例子中,从状态 0(无车)转移到状态 1(有一辆车)的条件是:有“到达”信号且没有“离开”信号。我们可以将其守卫写为 up ∧ ¬down。同时,输出动作就是新的状态值 1

自循环与完整性

如果“到达”和“离开”信号同时发生,则计数值不变,系统应保持在当前状态。这可以通过一个指向自身的转移弧线(自循环)来表示,其守卫为 up ∧ down,动作为保持当前计数值。

如果一个状态对于某些输入组合没有明确的转移定义,那么这个状态机就是不完整的。有时,设计者会故意忽略某些“永远不会发生”的输入组合,这为系统优化提供了自由度。但在安全关键系统中,确保状态机的完整性非常重要。

初始状态

初始状态必须明确指定,否则无法确定系统的起始行为。这类似于求解微分方程需要初始条件。在现实中,系统重启(如电脑蓝屏后重启)就是将系统重置到一个已知的初始状态。

输出语义:摩尔机与米利机

关于输出,有两种主要的有限状态机模型:

  • 摩尔机: 输出仅与当前状态有关。在状态图中,输出可以标在状态节点内部。
  • 米利机: 输出与当前状态和当前输入都有关系。在状态图中,输出标在转移弧线上。

在车库计数器的例子中,如果我们将输出(显示的数字)视为与状态(当前车辆数)一致,那么它就更像一个摩尔机。但在其他系统中,输出可能独立于状态编码,此时米利机模型更合适。

守卫与动作的扩展

守卫可以是基于纯信号(布尔值)的逻辑表达式,例如:

  • true: 转移总是被允许(无条件转移)。
  • p1: 当信号 p1 存在时允许转移。
  • p1 ∧ p2: 当信号 p1p2 同时存在时允许转移。
  • p1 ∨ p2: 当信号 p1p2 存在时允许转移。

守卫也可以基于具有数值的信号:

  • p3 = 1: 当信号 p3 存在且其值等于1时允许转移。
  • p3 > 5: 当信号 p3 存在且其值大于5时允许转移。

示例:恒温器

一个经典的例子是恒温器。它有两个状态:“制冷”和“制热”。

  • 在“制冷”状态,只要温度 T > 18°C,就保持制冷(自循环)。当 T ≤ 18°C 时,转移到“制热”状态。
  • 在“制热”状态,只要温度 T < 22°C,就保持制热(自循环)。当 T ≥ 22°C 时,转移到“制冷”状态。

这种设置(18°C 和 22°C 之间的死区)是为了防止在阈值附近频繁切换,导致系统“振荡”。

事件触发与时间触发

有限状态机的反应可以由两种机制触发:

  • 事件触发: 当输入事件发生时,系统立即反应。这是最直观、反应最快的方式。但是,如果多个事件几乎同时发生,可能导致竞争条件或未定义行为(例如,汽车空调的升温和降温按钮被同时按下)。
  • 时间触发: 系统只在特定的时间点(例如,时钟滴答声)检查输入并做出反应。这更安全,因为可以处理在同一个时间周期内发生的多个事件,但可能不够及时,且会消耗更多功耗(即使没有事件发生,系统也会被周期性唤醒)。

现实世界本质上是异步和事件驱动的。为了实现同步(时间触发)行为,我们通常引入一个时钟作为参考,强制系统在规整的时间点上同步反应。这是一种实现技巧,而非模型本身固有的概念。

确定性、非确定性与行为

  • 确定性有限状态机: 对于给定的当前状态和输入,下一个状态和输出是唯一确定的。
  • 非确定性有限状态机: 对于给定的当前状态和输入,可能存在多个可能的下一个状态和/或输出。

非确定性可以用于:

  1. 建模未知环境: 表示我们对环境行为的不完全了解(例如,其他司机的行为)。
  2. 表示设计自由度: 表示我们尚未在多个可行的实现方案中做出决定。

非确定性状态机通常更紧凑,但分析起来更复杂(需要检查所有可能的行为路径)。著名的子集构造法可以将非确定性有限状态机转换为确定性有限状态机,但可能导致状态数量指数级增长。

行为、轨迹与计算树

  • 行为: 系统可能执行的一系列非终止步骤。
  • 轨迹: 对一个特定行为的记录,包含了每一步的状态、输入和输出值(类似于飞机的黑匣子数据)。
  • 计算树: 一个图形,展示了从初始状态开始,所有可能的输入序列所导致的所有可能行为路径。它本质上是将有限状态机“展开”成一个(可能是无限的)树状结构。

有限状态机的强大之处在于,它能用一个有限的模型(状态和转移规则)来编码潜在的无限多种行为。

有限状态机的应用:验证

有限状态机是形式化验证的强大工具,特别是用于检查安全属性

  • 安全属性: 断言“某些坏事永远不会发生”。例如,系统永远不会进入“发动机爆炸”或“飞机坠毁”这样的坏状态。通过可达性分析,我们可以检查从初始状态出发,是否可能到达这些坏状态。
  • 活性属性: 断言“某些好事最终会发生”。例如,系统总是能够回到空闲状态。验证活性属性通常比验证安全性更复杂。

在设计安全关键系统时,控制器可以被设计为主动防止系统进入可能导致灾难性后果的状态。

概率模型与混合系统

  • 概率有限状态机: 与非确定性不同,概率状态机为每个转移分配了一个概率。它描述的是我们对其统计特性有了解的随机过程(例如,投掷硬币),而不是完全未知的不确定性。
  • 混合系统: 许多实际系统(如汽车发动机控制器)同时包含离散逻辑(何时点火、喷油)和连续动态(活塞运动、温度变化)。有限状态机可以用来对这类系统中的离散控制部分进行建模,与连续动力学模型相结合。

总结

本节课我们一起学习了有限状态机这一核心的离散行为建模工具。我们了解了其基本组成部分(状态、输入、输出、转移函数、输出函数),掌握了其图形化表示方法(状态图),并区分了摩尔机和米利机。我们还探讨了事件触发与时间触发的区别,以及确定性、非确定性和概率模型的含义。最后,我们看到了有限状态机在系统规范、环境建模以及形式化验证(特别是安全属性验证)中的强大应用。有限状态机虽然不能建模所有系统,但在其适用范围内,它是一个极其强大和实用的工具。

10:状态机的组合与扩展

在本节课中,我们将继续探讨有限状态机,并扩展其基本模型,使其能在更广泛的场景下应用。我们将介绍扩展有限状态机,并深入探讨一个在过去十年中备受关注的主题——混合系统。

从基本状态机到扩展状态机

上一节我们介绍了基本的有限状态机模型。本节中,我们来看看如何扩展这个模型,使其能表示更复杂的系统。

基本有限状态机由状态、转换、守卫条件和动作组成。其输出通常是纯信号(如0或1)。然而,许多实际系统需要输出更复杂的数据,例如整数或实数。

以下是扩展有限状态机的一个核心概念:输出可以是变量。这意味着输出不再局限于有限的信号集,而可以取自然数、实数等值。

例如,一个简单的车库计数器模型。基本模型需要为每个可能的车辆数(0到M)设置一个状态,这非常繁琐。扩展模型可以简化为一个单一状态,并引入一个计数变量 count

代码示例:车库计数器扩展模型

状态: 空闲
转换:
  - 守卫: car_enters
    动作: count = count + 1; 输出(count)
  - 守卫: car_leaves AND count > 0
    动作: count = count - 1; 输出(count)

这个模型的状态空间看似只有一个,但实际上包含了变量 count 的所有可能取值,其本质与多状态模型等价,但表示上更简洁。

混合系统简介

扩展状态机引入了连续变量,这自然引出了混合系统的概念。混合系统结合了离散的动态(有限状态机)和连续的动力(微分方程)。

混合系统在以下场景中非常有用:

  • 数字控制器与物理对象:例如机器人撞墙后改变运动模式。
  • 恒温器:当连续变化的温度达到阈值时,控制器触发离散动作。
  • 汽车引擎控制:引擎的运转周期(吸气、压缩、爆炸、排气)是离散状态,而曲轴转速、油门位置是连续变量。

混合系统的核心在于:连续变量的演化达到某个阈值时,会触发离散状态的跳转。每个离散状态(或称“模式”)下,系统都遵循一组特定的微分方程进行连续演化,这个状态保持不变的区域称为不变集

实际应用案例:汽车引擎建模

让我们通过一个具体例子来理解混合系统的建模过程。考虑汽车引擎的控制。

引擎的每个气缸工作周期包含四个离散阶段:

  1. 吸气
  2. 压缩
  3. 膨胀(做功)
  4. 排气

决定阶段转换的触发器是活塞到达上止点或下止点,而这由曲轴(驱动轴)的连续旋转位置决定。因此,这是一个典型的混合系统:连续运动(曲轴角度)守卫着离散状态(气缸阶段)的转换

此外,控制变量如点火正时(火花提前或延迟)也可以建模为离散决策,这会在状态机中增加额外的路径。

建模过程总结

  1. 识别关键物理现象:如气缸四冲程、曲轴运动。
  2. 抽象不必要的细节:忽略燃烧室内复杂的流体动力学,关注宏观阶段和扭矩输出。
  3. 定义离散状态:如四个冲程阶段,以及点火提前/延迟决策。
  4. 建立连续动力学:如驱动轴旋转方程、进气歧管压力变化。
  5. 定义守卫条件:如“当曲轴角度 == 上止点时,从压缩阶段转换到膨胀阶段”。

这种建模方法比传统的平均值模型精确得多,又比求解全部物理方程(如偏微分方程)简单可行,是实现高性能引擎控制的基础。

时间自动机:一种特殊的混合系统

在所有混合系统中,有一类特别简单且理论性质良好,称为时间自动机

在时间自动机中,唯一的连续动力学是时间的流逝,通常表示为 ẋ = 1(即时间导数恒为1)。一个典型的例子是交通灯控制器中的计时器,或者鼠标的双击检测(判断两次点击的时间间隔)。

时间自动机的重要性在于,尽管它包含连续变量(时间),但可以被自动转换为一个等价的有限状态机。这意味着关于它的许多性质(如“系统是否总能达到绿灯状态?”)是可判定的,这为形式化验证提供了便利。

相比之下,包含一般微分方程的混合系统,其验证问题通常是非常困难甚至不可判定的。

反应触发与时间触发

理解系统何时对输入做出反应至关重要,这引出了两种语义:

  • 事件触发:在纯(扩展)有限状态机中,当任何输入守卫条件变为真时,立即发生转换。这是反应式语义。
  • 时间触发:当模型中引入了计时器或时钟变量(如 countẋ=1)时,转换通常由时间条件(如 count >= 60)守卫。系统在特定的时间点检查条件并做出反应。

在交通灯例子中,即使有行人按钮按下,系统也会等到当前计时周期结束(如60秒)时才检查条件并决定是否转换,这就是时间触发。

总结与课程项目启示

本节课中我们一起学习了:

  1. 扩展有限状态机:通过引入输出变量,使模型能更简洁地表示计数器等系统。
  2. 混合系统:结合离散状态机和连续微分方程,用于建模机器人、汽车引擎等物理信息融合系统。
  3. 时间自动机:以时间流逝为连续动力的特殊混合系统,具有较好的理论性质。
  4. 建模思想:从复杂物理过程中提取关键离散模式和连续变量,抽象掉无关细节,建立可用于控制和验证的数学模型。

这引出了我们课程项目的核心方法论:给定一套传感器、执行器和计算平台(如Arduino),你需要为一个自选的应用完成从物理问题到数学模型,再到实现和验证的完整设计流程。这正是嵌入式系统设计的精髓所在。

09:模态行为建模

在本节课中,我们将学习嵌入式系统设计中的核心方法学,特别是平台化设计的概念,以及如何通过建模来预测和验证系统行为。我们将以汽车电子系统为例,探讨如何应对复杂性、缩短上市时间并确保系统安全。


设计方法学的重要性

上一节我们介绍了嵌入式系统的基础概念。本节中,我们来看看设计方法学为何至关重要。

方法学是组织思维和解决问题的方式。如果方法严谨且组织良好,设计结果将远优于随意尝试和反复检查。方法学本质上是一种基于特定原则的智力过程。大多数方法学都基于简单的原则,因为组织思维的方式本身就不多,总体框架应保持简单。

我们将讨论一些过去使用的设计模型,并分享工业界的视角,包括不同公司采用的方法以及我们认为优秀的设计方法。


汽车电子:一个复杂的案例

为了具体说明,我们使用一个大家熟悉的例子:汽车。如今,汽车开发的主要挑战在于电子部分。今天,汽车超过50%的价值来自电子系统。人们通常认为汽车是机械部件,但实际上,它更像是披着机械外衣的电子部件。电子部分才是最重要的。

安全性、燃油效率和减排目标,没有电子控制都无法实现。过去20年的重大发展始终集中在电子控制方面。当然,政府也施加了巨大压力,要求将事故或故障降至最低,这意味着汽车部件必须极其可靠。

然而,坦率地说,汽车行业的许多进步并非由消费者需求驱动。消费者只关心汽车的炫酷外观和娱乐系统,很少有人愿意为安全或环保额外付费。因此,创新的引入往往依赖于政府法规。例如,加州设定了到2018-2020年实现“净零能耗建筑”的目标,这同样是由法规驱动的。


上市时间的压力与虚拟工程

过去,推出一款新车需要大约五年时间,需要经过概念、模型、初步设计、骡车测试等多个阶段,反复修复问题。如今,这种模式已不再可行。例如,大众汽车采用我们即将介绍的方法,将新车的上市时间缩短到了6个月到一年。

这种巨大改进是通过方法学和消除物理原型实现的。这被称为虚拟工程,即大部分工作都在计算机上完成。我们不再制造“骡车”,因为成本太高,且产品(如手机)需要快速上市。


汽车功能的复杂性与架构演变

现代汽车的功能极其复杂,包括防抱死制动系统、电动转向、主动悬架、电子稳定控制等。新功能大多与安全相关,分为被动安全(如事故发生后起作用的安全气囊)和主动安全(如防止事故发生的自动刹车车道保持)。趋势是从被动安全转向主动安全,并最终实现完全自动驾驶

为了实现这些功能,汽车需要大量传感器、强大的计算单元(用于图像识别等)和执行器。这构成了一个典型的、高度复杂的分布式嵌入式系统。

传统的设计方法是“一个功能,一个盒子”,即每个功能对应一个独立的电子控制单元。这导致现代高端汽车中可能有超过100个ECU,意味着上百个微处理器。同时,车内的线缆长度可达数公里,增加了重量和油耗。

为什么不使用无线连接?主要原因是可靠性电磁兼容性,而非仅仅是安全黑客问题。无线连接可能受到干扰,在安全关键系统中这是不可接受的。

因此,汽车内部采用了多种网络,如LIN、CAN和FlexRay。嵌入式系统工程师需要了解传感器技术、网络和计算,领域非常广泛。


行业变革:从供应商主导到OEM主导

过去,汽车制造商依赖一级供应商提供“黑盒”解决方案。但随着电子系统成为竞争力的关键,制造商希望掌控核心技术并获取更高利润。因此,他们转向了集成模块化架构

这意味着什么?OEM(原始设备制造商)负责整体架构和集成,而供应商可能只提供软件算法或特定组件。这打破了“一个功能,一个盒子”的对应关系。

  • 一个ECU可以承载多个功能
  • 一个功能可以分布在多个组件上

OEM拥有所有应用的完整视野,可以要求供应商提供符合其架构标准的软件组件,然后由OEM进行集成。这带来了两个好处:削弱了供应商的议价能力,并提高了灵活性,例如可以通过软件升级为汽车增加新功能。


新架构的核心挑战:时序保证

然而,这种新方法带来了严峻挑战。当我在现有平台上添加新软件功能时,如何保证时序?现有的微处理器和操作系统大多基于“尽力而为”的原则,无法保证最坏情况执行时间。

在安全关键系统中,性能(尤其是时序)是正确性的一部分。例如,从碰撞发生到安全气囊弹出的信号如果延迟,后果将是致命的。因此,能够表征在已有任务运行的平台上,新增软件执行的时序特性,变得至关重要。

一种潜在的解决方案是在通信中使用时分多址等同步协议,为关键消息分配固定的时间槽。即使底层硬件是异步的,也可以通过抽象层让应用看到同步的行为。这体现了选择性约束的设计哲学——通过限制选择来获得可预测性。


设计流程模型:V模型及其局限

业界常提到V模型。这是一个线性的设计流程:从系统概念开始,经过功能设计、架构选择、实现,最后进行测试和验证,形成一个“V”字形。

为什么它不好?问题在于验证进行得太晚。如果在最后阶段才发现错误,返工成本极高。数据显示,在需求阶段发现并修复一个错误的成本是1,在维护阶段发现则可能高达500。

因此,我们需要将验证尽可能提前到设计过程中。在虚拟工程时代,我们可以利用数学模型在早期进行验证,无需等待物理原型。这催生了“小V循环”的概念,即每个设计步骤都包含自己的设计-检查-修复循环。

为了做到这一点,我们需要平台组件模型(包括计算机、线缆等),以便在设计初期就能评估实现方案。如果模型准确,早期设计决策就能更好地反映最终实现。


平台化设计

这就是平台化设计的核心思想。它大约在25年前被提出,如今已成为主流。大众汽车CEO最近也宣布,未来车辆开发将基于平台和平台化设计。

平台化设计不仅适用于电子部分,也适用于机械部分。其思想是:拥有一个基本完成的平台,通过微调来生产满足客户需求的新产品。例如,大众固定了汽车前轴、后轴等核心机械部件,只允许调整轴距等少数参数。这是“选择性约束”的典型应用。

平台化设计始终基于两部分:

  1. 我想做什么?(功能)
  2. 我有什么可用的?(平台)

平台是所有可用组件库的组合。设计过程就是将功能映射到平台组件上。这类似于从门级库中进行逻辑综合,也类似于为不同的葡萄酒选择合适的酒杯——你需要根据功能(葡萄酒特性)选择能提供所需性能(醒酒效果)的平台组件(酒杯)。


设计是一个“中间相遇”的过程

因此,平台化设计不是一个纯粹的自顶向下过程,而是一个“中间相遇”的过程。需求从顶部而来(目标),可用的库从底部而来(现有组件)。设计过程就是在这两者之间寻找匹配。

为了使这个过程可行,库中的组件必须能够“即插即用”。我们需要定义清晰的接口组合规则,就像乐高积木一样。这就是契约的概念:每个组件都声明其假设(对运行环境的要求)和保证(在此假设下提供的性能)。通过检查契约的兼容性,可以判断组件能否组合。

当需要引入一个全新的创新组件时,你可以先将其视为一个“虚拟组件”,为其定义功能和性能要求(即契约),然后交给专门团队去实现。如果实现成功并满足了契约,集成就是正确的;如果无法满足,则需要重新协调顶层设计。

平台本身不是一个单一实体,而是一个解决方案族,由参数化的组件库定义。平台实例则是其中一个具体的连接配置。


性能分析与正确性

在嵌入式或信息物理系统中,仅验证功能正确性是不够的,性能分析同样关键。系统必须在规定的时间内做出反应。因此,在设计的每一步映射后,都需要进行性能分析。如果结果不满足要求,就需要调整功能、更换更快的处理器或增加资源,然后重新评估。


建模的本质

所有虚拟工程方法都依赖于模型。那么,什么是建模?建模就是用一种能够解释预测系统行为的方式来描述系统。一个只能解释过去、不能预测未来的模型是无效的。

建模的艺术在于抽象——在特定抽象层次上,忽略不重要的细节。没有“唯一真实”的模型,模型的有效性取决于你提出的问题。例如:

  • 设计晶体管电路时,可能需要SPICE模型(微分方程级)。
  • 设计数字电路时,布尔逻辑模型(0和1)就足够了。
  • 设计软件时,本身没有物理时间概念,只有并发性偏序关系
  • 将软件映射到微处理器时,才需要考虑执行时间

最关键且最困难的部分是对环境建模,即预测世界将如何与你的设计交互。例如,设计汽车方向盘时,必须假设司机有两只手。


契约与组件化设计

基于契约的设计是当前的热点。契约是一个(假设, 保证)对。它为每个组件明确了使用条件。这极大地促进了组件的可组合性和设计的模块化。微波设计中要求阻抗匹配到50欧姆,就是一个典型的契约。


各种建模技术

在本课程中,我们将遇到多种建模技术:

  • 物理现象建模:使用常微分方程
  • 模态行为建模:使用有限状态机。例如,汽车有不同的驾驶模式(加速、巡航、刹车),模式间的转换由输入(如踏板位置)触发。
  • 软件建模:关注并发与逻辑。
  • 网络建模:关注延迟、错误率、丢包等。

所有这些技术的核心区别在于它们如何处理时间——这个在工程、物理乃至哲学上都至关重要的问题。我们将在下一讲中详细讨论这些不同的时间观念和建模技术。


总结

本节课我们一起学习了:

  1. 虚拟工程正在取代物理原型,成为主流设计方法。
  2. 传统的线性V模型存在验证滞后的问题,现代方法强调早期验证和“小V循环”。
  3. 平台化设计是一种强大的“中间相遇”方法论,它通过将功能映射到由可重用组件库定义的平台上来进行设计。
  4. 时序保证是信息物理系统设计,尤其是汽车等安全关键系统设计的核心挑战。
  5. 建模是虚拟工程的基础,其关键在于针对所提问题选择合适的抽象层次
  6. 基于契约的设计通过明确组件的假设和保证,提高了设计的可组合性和可靠性。
  7. 存在多种建模技术(如ODE、FSM),其核心区别在于对时间的处理方式。

在接下来的课程中,我们将深入探讨这些具体的建模技术。

10:期中复习与调度问题深入探讨

在本节课中,我们将回顾期中考试的相关安排,并深入探讨嵌入式系统中的实时调度问题,特别是考虑任务间依赖关系和互斥锁时,经典调度理论面临的挑战与局限性。

课程公告与项目安排

今天是10月28日。作业提交截止时间为今晚午夜。我们将在午夜发布作业答案,因此不接受任何迟交的作业。请按时提交。发布答案的目的是为了帮助大家复习即将在周四举行的期中考试。

关于办公时间,由于明天下午有年度评审会议,我明天的办公时间将从上午10点调整到11点,而非原来的11:30到12:30。今天的办公时间照常,为下午2点到3点。欢迎大家前来咨询期中考试或项目相关问题。

关于项目,我们已经审阅了所有团队的项目章程并提供了反馈。接下来,请各团队与一位GSI会面。建议你们选择一个本周(或下周一)的实验课时间段,并尝试固定下来,以便团队能每周与GSI定期会面。GSI将在常规安排的实验课时间段提供办公时间支持。每个团队将被分配一位GSI作为主要联系人,协助处理订购零件、解决问题等事宜。

由于本周的年度评审和其他活动,实验课时间有所调整:今天下午的实验课将从3点持续到5点;周三的实验课将提前一小时,从9点开始到12点结束。

项目章程整体质量很高,许多项目颇具雄心。我们鼓励大家采取科学的工程方法。例如,如果你的项目计划使用传感器识别人脸并与同学进行自然语言对话,请制定备用方案。一个好的通用备用方案是:针对一个问题陈述,评估一系列候选解决方案的有效性。这意味着你需要对传感器进行测量、表征和建模,并确定它们解决该问题的适用性。以这种方式构建问题,即使所选传感器无法解决问题也是完全可以接受的,因为你构建了一个科学实验来评估该问题。请以此为目标,进行高质量的工程分析,这比仅仅做出一个炫酷的演示更重要。

关于订购零件,如果项目章程中提出使用特定处理器板但没有给出充分理由(例如“我上学期用过”或“我抽屉里有一个”),这不是一个充分的理由。如果学校已有的平台具备同等能力,我们将不会批准使用教学资金购买功能重复的平台。如果你有充分的理由,请务必说明。

调度理论回顾与深入

上一节我们介绍了实时调度的基本概念,本节中我们来看看在更现实的场景下调度理论面临的挑战。

实时调度是一个庞大的研究领域。我们课程的目标是让大家了解基本术语,并对这些理论结果保持一定的审慎态度。理解这些调度结果的关键在于,要明白它们所解决的问题往往并非现实场景。

单调速率调度

单调速率调度定理假设有n个周期性调用的独立任务,周期明确,且所有任务都有最坏情况执行时间。任务之间没有交互、互斥锁或优先级关系。这并非一个非常现实的场景。

尽管如此,该定理提供了一个有用的指导原则。定理指出:如果存在任何固定优先级分配能产生可行调度,那么按周期排序优先级(周期越短,优先级越高)也能产生可行调度。重要的是要理解,这并未说明这是“最佳”调度,它只说明在满足每个周期执行一次任务的所有调度中,如果存在可行调度,那么单调速率调度也是可行的。这就是其在“可行性”意义上的最优性。

最早截止时间优先调度

最早截止时间优先调度与单调速率调度有几个关键不同:首先,它允许优先级动态变化;其次,它不需要预先知道所有任务;第三,任务不需要是周期性的,它们可以在任意时间点到达并准备执行,每个任务都有一个关联的截止时间。

霍恩定理指出:对于一组具有任意到达时间的独立任务,在任何时刻执行所有可用任务中绝对截止时间最早的任务,这种算法在最小化最大延迟方面是最优的。这是一个更强的结果,因为它对不可行调度也说了些内容:如果最大延迟被最小化,且该值为负,则调度是可行的。因此,如果存在任何可行调度,最早截止时间优先调度也能找到可行调度。

引入现实约束:优先级关系

现在,我们通过增加更多现实因素来扩展这些结果。我们将考虑两个问题:优先级关系和互斥锁。

首先考虑优先级关系。假设我们有一组六个任务,需要执行一次(非周期性)。任务间存在优先级约束,例如任务1必须在任务2或3之前执行,任务2必须在任务4或5之前执行。这些关系可以用有向无环图描述。

在这个例子中,任务2的最坏情况执行时间为1,截止时间为5;任务4的执行时间为1,截止时间为3。这里存在一个奇怪的情况:任务4的截止时间(3)早于其前置任务2的截止时间(5)。如果我们盲目应用最早截止时间优先调度,由于任务3的截止时间(4)早于任务2(5),调度器会先执行任务3,导致任务4错过截止时间。然而,存在一个可行的调度:先执行任务2,再执行任务3。

这并不违反霍恩定理,因为霍恩定理针对的是独立任务。这里的任务存在依赖关系。

解决此问题的一种方法是“最晚截止时间优先”反向调度,由Gene Lawler提出。其思想是反向时间线做决策:首先决定哪个任务最后执行(选择截止时间最晚的),然后反向工作。另一种方法是修改最早截止时间优先调度:根据优先级关系修改任务的“释放时间”(即任务可开始执行的最早时间),并基于后续任务的截止时间和执行时间修改任务的“截止时间”。经过这样的修改后,最早截止时间优先调度在最小化最大延迟方面仍然是最优的。

在实践中,截止时间往往是通过合同谈判确定的,可能并不完全合理。例如,在波音787梦幻客机的电气系统中,关键负载不允许断电超过50毫秒。这个数字源于子系统供应商之间的合同约定,而非纯粹的工程考量。

互斥锁与优先级反转

接下来我们看看互斥锁可能对调度产生的影响。互斥锁确保一段代码(临界区)不会被多个线程同时执行。当一个线程持有锁时,任何其他尝试获取同一锁的线程都会被阻塞。

优先级反转是一个经典问题,曾导致火星探路者号频繁重启。场景涉及高、中、低三个优先级的任务:

  1. 低优先级任务3持有锁。
  2. 高优先级任务1就绪,抢占任务3并开始执行。
  3. 任务1尝试获取任务3持有的锁,因此被阻塞。
  4. 此时,中优先级任务2就绪。由于任务2优先级高于任务3,它抢占任务3并开始执行。
  5. 任务2可以执行任意长的时间,实际上阻塞了更高优先级的任务1。

这就是优先级反转:低优先级任务间接阻塞了高优先级任务。

解决方案是优先级继承协议:当一个低优先级任务因持有锁而阻塞高优先级任务时,低优先级任务临时继承高优先级任务的优先级。这样,当中优先级任务就绪时,无法抢占已继承高优先级的低优先级任务,从而避免了无限制的阻塞。

互斥锁与死锁

另一个问题是互斥锁与优先级交互可能导致的死锁。考虑两个任务(任务1优先级高于任务2)和两把锁(A和B):

  1. 任务2先获取锁A。
  2. 任务1抢占任务2,获取锁B,然后尝试获取锁A(被阻塞)。
  3. 任务2恢复执行,尝试获取锁B(被阻塞)。
  4. 双方互相等待,形成死锁。

优先级天花板协议可以预防此类死锁。该协议为每把锁分配一个“优先级天花板”,其值等于可能获取该锁的所有任务中的最高优先级。一个任务仅当其优先级严格高于当前被其他任务持有的所有锁的优先级天花板时,才被允许获取一把锁。在上例中,锁B的优先级天花板是任务1的优先级。因此,当任务2持有锁A时,任务1(其优先级不高于锁B的天花板,即它自己的优先级)在尝试获取锁B时会被阻塞,从而让任务2有机会完成并释放锁,避免了死锁。

然而,确定“哪些任务可能获取哪把锁”需要进行静态代码分析,对于像C这样允许任意指针操作的语言,这可能是不可判定的。因此,在安全关键系统中(如航空电子设备),工程师常常选择完全避免使用线程,转而采用更可分析的并发编程模型。

调度算法的脆弱性

总的来说,基于优先级的线程调度算法是脆弱的。“脆弱”意味着微小的变化可能导致巨大的、非预期的后果。理查德·格雷厄姆提出的“格雷厄姆异常”清晰地说明了这一点。

他展示了在多处理器调度中,存在以下反直觉现象:

  1. 增加处理器数量可能导致完成时间变晚
  2. 减少所有任务的执行时间可能导致完成时间变晚
  3. 放宽优先级约束(给调度器更多自由)可能导致完成时间变晚

这些是非单调性的例子:提前完成反而可能导致最终截止时间被错过。这种脆弱性使得系统在安全验证方面异常困难。在实践中,大型复杂系统(如联合攻击战斗机)的软件也遇到过类似问题:看似无关的代码微小改动,会导致其他部分错过截止时间。

总结与审慎态度

本节课中我们一起学习了实时调度在引入任务依赖和互斥锁后遇到的复杂问题。我们回顾了优先级继承和优先级天花板协议等解决方案,也通过格雷厄姆异常了解了调度算法的内在脆弱性。

这些调度理论结果提供了有价值的工程指导原则,但必须谨慎应用。理解每个定理的前提假设(什么没说)与实际系统的差距至关重要。对于安全关键系统,仅依赖这些调度理论是远远不够的,需要更严格的设计、分析和验证方法。希望本课程能帮助大家在未来的工程实践中,既懂得利用这些理论工具,又对其局限性保持清醒的认识。

祝大家期中考试顺利!

11:执行时间分析与自动评分系统 🚀

在本节课中,我们将学习如何对嵌入式系统设计进行执行时间分析,并深入了解一个基于运行时监控的自动评分系统。我们将从线性时序逻辑(LTL)的回顾开始,逐步过渡到信号时序逻辑(STL),并最终探讨如何利用这些工具来构建一个用于评估机器人控制器设计的自动评分框架。

从模型检查到运行时监控 🔍

上一节我们讨论了模型检查技术,它用于自动验证设计是否满足特定需求。然而,对于像Cyber-Physical系统(CPS)这样的复杂混合系统,模型检查STL公式通常是难以处理的。因此,我们需要一种更轻量级、更易处理的方法。

本节中,我们将转向一种称为“轻量级验证”或“运行时监控”的方法。这种方法不是检查模型的所有可能轨迹,而是通过模拟生成有限数量的轨迹,并监控这些轨迹是否满足给定的规范。虽然这不能像模型检查那样提供完备的证明,但它能为我们提供关于特定模拟运行的有用信息,并且当规范不满足时,可以轻松地生成反例。

监控LTL公式 📊

为了理解运行时监控,我们首先回顾如何监控一个LTL公式。LTL公式在离散的事件序列上进行评估。

以下是监控基本LTL运算符的方法:

  • 原子谓词:如 AB,监控它们就是检查该谓词在序列的每个时间点上的真值。
  • Next运算符:公式 X B 为真,当且仅当 B 在序列的下一个时间点为真。
  • Always运算符:公式 G A 为真,当且仅当 A 在序列的每一个时间点都为真。如果在某个点 A 为假,则整个公式为假,并提供了一个反例。
  • Eventually运算符:公式 F B 为真,当且仅当在序列的某个未来时间点 B 为真。

需要注意的是,在运行时监控中,我们只有有限的观察数据。对于某些公式(如 G A),如果 A 在观察到的所有点都为真,我们无法断定它在无限未来也为真。因此,监控结果有时是“未知”的。

信号时序逻辑(STL)介绍 ⏱️

STL是LTL的扩展,它引入了实时约束和实值信号。这使得它非常适合描述CPS的属性。

一个经典的LTL属性是:“总是(一旦有请求,最终会得到授权)”,即 G (request -> F grant)

  • 带实时约束的MTL:我们可以将其扩展为:“总是(一旦有请求,在0.5秒内最终会得到授权)”,即 G (request -> F_[0,0.5] grant)。这里,grant 仍然是布尔谓词。
  • STL:更进一步,我们可以直接对信号值本身进行推理。例如,xy 是实值信号(如机器人的位置),我们可以写出公式:G_[0,20] ( (x(t) > 0.5) -> F_[0,6] G_[0,5] (abs(y(t)) < 0.05) )。这个公式描述了一个稳定属性:如果 x 超过0.5(超调),那么它最终应在6秒内稳定在 y 的绝对值小于0.05的区域内,并保持5秒钟。

STL的语法允许我们定义原子谓词(如 x(t) > 0.5),以及带有时间区间的时序运算符(如 F_[a,b] φ, G_[a,b] φ)。公式在时间零点进行评估,并根据运算符的含义向前查看信号值。

自动评分系统架构 🛠️

现在,我们来看看如何将STL监控应用于自动评分系统。该系统的核心是测试计划

测试计划由以下部分组成:

  • 测试用例/轨迹:这是在特定环境设置下通过模拟生成的一系列轨迹。理想情况下,这些测试应覆盖可能触发设计错误或性能问题的各种情况。
  • 故障监视器:对于每个测试轨迹,我们定义一组STL属性(即故障监视器)。每个属性都编码了一种特定的设计缺陷或故障模式。如果某个属性在轨迹上被评估为,则意味着检测到了相应的故障。

系统的通用流程如下:

  1. 声明信号、参数和STL公式。
  2. 定义一系列测试,每个测试指定环境、模拟时长等。
  3. 对于每个测试,列出要检查的故障监视器(STL公式)。
  4. 自动评分器运行每个测试模拟,获取轨迹数据。
  5. 对于每个故障监视器,在生成的轨迹上评估其STL公式。
  6. 根据评估结果(真/假)提供反馈。如果某个故障被标记为“关键”,系统可能会提前终止测试。

一个完整的STL评分示例 🤖

让我们通过一个具体例子来理解。假设作业要求机器人在2秒内离开某个区域。

我们可以在测试计划中定义如下:

-- 定义信号和参数
signal x, y
param y_min = -1.0, x_max = 1.0

-- 定义一个子公式:机器人是否在目标区域内?
subformula in_region := (y(t) < y_min) or (x(t) > x_max)

-- 定义顶层故障属性:机器人未能离开区域
-- 如果在时间0到20秒内,机器人始终在区域内,则此故障为真
formula goal_missed := G_[0,20] in_region

然后,我们定义一个测试,使用特定的环境(如“左侧有障碍物”),并设置模拟时长为20.1秒(略长于公式的时间范围以确保能判定)。故障监视器 goal_missed 会检查轨迹。如果公式为真,则评分系统会报告故障并提供预设的反馈信息,例如“机器人在指定时间内未能离开目标区域”。

在实际的课程导航实验室中,我们使用了更复杂的测试计划,包含多个测试用例和故障监视器,用于检测诸如“机器人原地打转”、“碰撞后未充分后退”、“左右两侧连续碰撞”等常见设计错误。这些STL属性被精心设计来捕捉学生代码中可能出现的特定错误模式。

创建自定义测试计划 ✨

该系统的一个强大之处在于其可扩展性。你可以为自己的控制器设计创建自定义测试计划。

以下是操作步骤:

  1. 找到Cyber-Physical系统模拟器的数据目录(例如 cyberseed/data/)。
  2. 该目录下存有现有的测试计划文件(如 feedback_nav.stl)。
  3. 你可以参考现有文件的格式,编写自己的 .stl 文件,定义新的信号、参数、公式和测试用例。
  4. 替换或添加你的测试计划文件,然后通过评分器运行你的控制器设计,以验证其是否满足你的自定义规范。

这为进行个人项目或深入研究特定控制算法提供了极大的灵活性。

总结 📝

本节课我们一起学习了嵌入式系统设计验证的进阶内容。我们从模型检查的局限性出发,引入了适用于复杂系统的运行时监控方法。我们详细探讨了如何监控LTL公式,并进一步学习了功能更强大的信号时序逻辑(STL),它能够描述带有实时约束的实值信号属性。

最后,我们深入剖析了一个基于STL运行时监控的自动评分系统架构。该系统通过定义测试用例和编码故障模式的STL属性,能够自动评估机器人控制器等CP设计,并提供有针对性的反馈。我们还了解到,这个框架是开放和可扩展的,鼓励我们创建自定义的测试计划来验证自己的设计想法。

通过结合形式化方法和实际工具,我们能够更系统、更高效地设计、分析和调试复杂的嵌入式与信息物理系统。

12:设计规范与形式化方法 🧠

在本节课中,我们将要学习如何精确地定义和规范一个设计。许多人认为设计的难点在于实现,但实际上,最困难的部分是清晰地说明你想要什么,以及设计应该做什么。这不仅是技术问题,也涉及到巨大的商业利益。

设计规范的重要性 💡

一个设计是正确的,当且仅当它满足其规范。一个没有规范的设计无法被判断对错,因为你没有比较的依据。仅仅运行几个测试,然后说“看起来不错”是不够的。

许多嵌入式系统被部署在安全关键系统中,例如汽车、飞机和医疗设备。这些系统的软件和硬件必须可靠,不能有缺陷。一个著名的例子是达芬奇手术机器人,其设计的精确性至关重要。另一个例子是心脏起搏器,如果其软件存在缺陷,可能会对患者造成严重伤害。

什么是规范?📝

规范是对设计目标的数学化陈述。它需要非常精确,以便进行验证。验证问题就是检查你的设计是否满足用于建立属性的数学公式。

控制器综合是另一个相关的主题。理想情况下,如果我们能根据规范自动构建出满足所有要求的系统,那将是最佳情况。这就是控制器综合的目标。

模型与验证 🔍

我们之前讨论过基于模型的设计,即从一个模型开始,然后推导出设计的其余部分。在设计阶段,我们希望验证这个闭环系统是否满足需求。这可以通过自动化的方式实现,即控制器综合,或者通过运行计算机程序来检查设计是否满足要求。

验证策略有多种,其中最常用的是仿真。然而,仿真只能覆盖有限的测试用例,无法保证在所有可能情况下的正确性。因此,人们非常感兴趣进行形式化验证,这是一种自动检查所有可能情况的方法。

为了实现形式化验证,我们需要一种形式化的方式来编写模型和需求,以便算法能够处理。这就是形式化验证代码。

安全属性与时间逻辑 ⏳

在设计时,我们可能希望断言某些“坏状态”永远不会被达到。例如,心脏永远不应进入纤颤状态。这种属性被称为安全属性。

为了表达这种涉及系统演化的属性,我们需要时间逻辑。命题逻辑是静态的,不涉及时间演化。时间逻辑,由以色列计算机科学家Amir Pnueli在1977年提出,正是为了断言设计的时序属性而发明的。

时间逻辑有两个基本概念:“始终”和“最终”。例如,“始终不进入坏状态”和“最终进入一个好状态”。

线性时间逻辑(LTL)基础 🧮

LTL建立在原子命题之上,例如:

  • x:信号x存在。
  • x = 1:信号x存在且其值为1。
  • S:机器处于状态S。

在这些原子命题上,我们可以使用逻辑运算符(与、或、非、蕴含)和时序运算符来构建更复杂的公式。

LTL的核心时序运算符包括:

  • G (Globally): 公式G φ表示属性φ在轨迹的每一个状态(及其所有后缀)中都为真。用于表达安全属性,例如G (¬bad)表示永远不进入坏状态。
  • F (Finally/Future): 公式F φ表示属性φ在轨迹的某个未来状态中最终会为真。用于表达活性属性,例如F good表示最终会进入好状态。
  • X (Next): 公式X φ表示在下一个状态中,属性φ为真。
  • U (Until): 公式φ1 U φ2表示属性φ1一直为真,直到属性φ2变为真。之后φ1可以为真或假。

一个状态机满足一个LTL公式,当且仅当该状态机的所有可能执行轨迹都满足该公式。

规范的挑战与未来方向 🚀

即使我们有了强大的形式化工具,仍然面临挑战:

  1. 规范的正确性: 我们如何确保我们写下的规范本身是正确的、反映了真实需求?规范本身可能存在不一致性。
  2. 规范的完备性: 我们如何确保没有遗漏任何重要的需求?这被称为需求工程中最困难的问题之一。

未来的研究方向包括:

  • 从系统轨迹推断时间逻辑: 给定一个被认为是正确的系统,能否自动推断出它所满足的所有时间逻辑属性?
  • 从自然语言提取规范: 大多数工程师使用自然语言(如Word文档)写规范。能否开发工具,从受限的自然语言模式中自动生成形式化规范?这被称为基于模式的规范捕获或规范挖掘。

总结 📚

本节课我们一起学习了设计规范的核心重要性。我们了解到,精确的、数学化的规范是验证设计正确性的基础。我们介绍了线性时间逻辑(LTL),这是一种用于描述系统时序行为的强大形式化语言,其核心运算符包括G(始终)、F(最终)、X(下一个)和U(直到)。最后,我们探讨了在创建和验证规范时面临的挑战,以及该领域未来的研究方向。记住,清晰、可测量的规范是任何成功设计的第一步。

13:规范与时序逻辑

在本节课中,我们将学习状态机之间的等价与精化关系。我们将探讨语言等价、模拟关系以及精化的不同概念,并理解它们在系统设计和验证中的重要性。

等价与精化

上一节我们介绍了状态机的基本概念,本节中我们来看看如何比较两个状态机。等价意味着两个机器可以相互替换而不改变系统的可观察行为。然而,存在比简单的语言等价更强的概念,即模拟。

如果两个状态机是确定性的,那么语言等价和双向模拟是相同的。如果机器是非确定性的,情况则不同,我们稍后会看到这是如何可能的。

精化的概念

那么,等价和精化之间有什么区别?等价,如前所述,是指两个机器可以做相同的事情。而精化则不同。

当你有一个规范时,它通常告诉你需要做什么,但不会说明你不关心的事情。因此,当我进行实现时,我可能需要指定所有细节。那么,我是在复制规范告诉我要做的事情吗?实际上,我做得更少。这就是精化的概念。

为什么做得更少?因为如果规范只关心“向右直走”,而不指定步幅,那就意味着无论我迈大步还是小步都无关紧要。在实现时,我需要决定我的步幅。这就是我的实现。因此,实现是规范的一个子集,它包含的行为更少,因为规范包含了所有正确的行为,而实现只是从中选取一个。

精化可以有多层。通常,你有一个规范,然后进行第一次精化、第二次、第三次,直到得到实现。最终的实现必须是确定性的。无论你做什么,它都必须在物理空间中移动,因此必须是确定性的。模型可能是非确定性的,但实现本身必须是确定性的。

精化的类型

两个可能不等价的状态机 M1 和 M2 可以通过包含关系联系起来。M2 的行为全部包含在规范 M1 中。以下是几种精化关系:

  • 类型精化:M2 可能与 M1 类型兼容,意味着它可以在不引起类型冲突的情况下替换 M1。例如,如果 M1 的输入/输出空间是实数,而 M2 使用自然数,那么 M2 是 M1 的类型精化,因为自然数是实数的子集。
  • 语言精化(行为精化):M2 可能是 M1 的特化,M2 只能产生 M1 能产生的输出序列,反之则不然。正如之前所说,规范要求“从这里到那里”,而我选择了一个特定的步幅,因此我的所有行为都包含在规范中,但规范能做的更多。
  • 模拟精化:M2 可能是 M1 的模拟精化,即在每个反应中,M2 可以产生 M1 能产生的所有输出值。这意味着 M1 可以模拟 M2,M1 可以做 M2 能做的一切。

在所有情况下,如果 M1 在系统中是有效的,那么 M2 也是有效的,因此可以替换它。但这里的关键在于“有效”的定义取决于我们使用的度量标准。例如,比较手机时,“更好”本身没有意义,必须定义衡量标准(如速度、开放性、应用生态)。

因此,精化的概念与用于判断两个事物是否相同的特定度量标准相关。通常,我们说 M2 实现了 M1,M1 是行为更丰富的规范,M2 是一个受限制的版本,不会超出 M1 要求的空间。

类型精化示例

现在的问题是,如何判断一个特定机器相对于类型是否是另一个机器的精化?这很简单。

M2 是 M1 的类型精化,如果 M2 的输入类型是 M1 输入类型的子集,并且 M2 的输出类型包含 M1 的输出类型。输入类型需要是子集,以确保 M2 接受的任何输入也能被 M1 接受。输出类型可以更丰富,只要 M1 能产生的任何输出值,M2 也能产生即可。

让我们看一个车库计数器的例子。我们知道原来的状态机是冗余的,可以简化为单一状态。状态 0, 1, 2... 对应于输出,输出和状态(计数器值)之间存在一一对应关系。

输入端口是 P_upP_down,类型为 {present, absent}。输出端口是 count,类型 V_count 是从 0 到 M 的自然数。

现在,P_up 的序列可以是 present, absent, present, absent...P_down 是另一个序列。根据这些输入序列,计数器输出 count 序列,例如 0, 1, 0, 1...

一个类型精化的例子是:假设我有一个车库计数器,显示数字最多到 99(即计数 1, 2, 3... 直到 99)。我有另一个车库计数器,只有 90 个空间。由于自然数的输出空间包含在前一个计数器的自然数空间内,我们可以说存在类型精化。

因此,每次我进行类型精化时,我知道可以用 M2 替换 M1。然而,这并不总是可行的。什么时候不行?例如,如果有 92 辆车(或车位),而我的机器只能数到 90,那么当第 91 辆车进来时,我的计数器就无法给出正确答案,因此我不能用那个计数器来代表系统的行为。

语言等价与模拟

回顾简单的机器人模型,我们问:何时可以安全地用一台机器替换另一台?我们说,对于确定性机器,语言等价就足够了。对于非确定性机器,则需要模拟的概念。

但请记住,模拟是单向的。语言精化也是单向的。语言等价则是双向的:如果 A 与 B 语言等价,那么我可以互换它们,选 A 或选 B 都一样。如果是精化关系,则不能这样做:我可以用更受限的机器替换更丰富的机器,但不能反过来。

在非确定性机器的情况下,问题在于一台非确定性机器根据你做出的选择,可能比另一台机器有更多的行为。因此,我可以匹配它所做的任何事情,但即使看起来我们能产生相同的序列,如果我先行动,它可能无法跟上;如果它先行动,我总是可以跟上,但反之则不然。这就是为什么我们说模拟与语言等价的概念绝对不同。

执行轨迹与行为

我们如何区分这些情况?我们将讨论执行轨迹。之前我们说行为代表了机器对环境的影响。执行轨迹意味着我有一个输入序列和相应的输出序列,我们称之为轨迹。

轨迹不仅包含输入和输出的信息,还包含状态信息。它不仅说明我给出了这个输出,还说明我从某个状态转移到另一个状态。因此,它比单纯的输入输出对包含更多信息。例如,我可以区分两个不同机器的响应,因为它们的状态序列不同。这就是轨迹包含更多概念的原因。

状态机 M 的行为实际上是所有可能轨迹的集合,即机器可以做的所有事情。这就是机器的语言。如果你了解一些语言理论,你会知道有些语言可以被有限状态机接受。这里,语言是机器可以产生的所有可能行为(输入输出序列)的集合。

对于类型兼容的状态机 M1 和 M2,如果 M2 的语言包含在 M1 的语言中,则 M2 是 M1 的语言精化。这意味着我的行为比另一个机器少,但我的所有行为都包含在更丰富的机器中。因此,如果 M2 的可观察 IO 行为是 M1 的子集,M2 可以替换 M1。

我们谈论可观察行为是因为,如果我关心有限状态机对环境的作用,我必须观察从该机器输出什么。任何不可观察的、不输出的东西我都不知道,因此相对于系统做什么而言,我不关心。

语言等价不足的例子

我们之前说过,语言等价通常不足。只有在确定性机器的情况下是完美的。对于非确定性机器则不然。

例如,考虑左边的确定性机器 M1 和右边的非确定性机器 M2。对于特定的输入,M2 可以进入两个状态(F 或 H),F 只能产生输出 1,H 只能产生输出 0。如果你输入任何序列,得到的输出是相同的。

但问题在哪里?让我们看看机器的行为。假设我从 M1 的 A 到 B,从 M2 的 E 到 F(这是一种可能的行为)。接下来,M1 的行为是走分支到 D,输出 0。在 M2 中,如果我在 F,我能产生 0 吗?不能。

所以,由于非确定性,它们语言等价,但并非模拟等价。如果我让一个机器先选择方向,另一个机器后选择,我可以匹配行为;如果我调换选择顺序,则无法匹配。这意味着一个机器可以模拟另一个,但反之则不然。这只在非确定性机器的情况下成立,因为存在选择以及由此产生的大量行为。

因此,即使这些机器具有完全相同的输入输出行为,在上下文中,M1 并不总是可用于替换 M2。

模拟关系

假设 M1 是规范。它做的一切都是可以接受的。那么,如果我用 M2 替换 M1,是可以的,因为 M2 能做的任何动作都是可以接受的。但反过来则不行:如果 M2 是规范,用它替换 M1 可能不行,因为 M1 在状态 B 总是能做出 M2 无法匹配的动作。

所以,一个方向是可以的,另一个方向不行。这是一种匹配游戏:你走一步,我匹配;你走一步,我匹配。无论你给我什么,我都有答案。

模拟关系是一种状态对应关系,确保无论我输入什么,这两台机器都能产生相同的输出。如何建立这种对应关系?我们通过构建状态对集合来定义模拟关系。

从初始状态对开始(例如 M2 的 E 和 M1 的 A)。然后,考虑 M2 先行动(在游戏中),它可能走到 F。M1 必须能够响应,走到 B。所以 (F, B) 成为一个状态对。或者,M2 走到 H,M1 走到 B,则 (H, B) 是另一个状态对。继续这个过程,我们可以得到所有可能的状态对集合,如 (G, C), (I, D) 等。

这个状态对集合被称为模拟关系。为什么关心模拟关系?因为这样一个集合的存在,可以通过算法分析并保证一台机器可以模拟另一台。因此,如果 M1 模拟 M2,则 M2 精化 M1,M2 可以替换 M1,M1 可以接受的一切,M2 也可以。

双向模拟

比模拟更强的概念是双向模拟。双向模拟意味着机器 M2 模拟 M1,同时 M1 也模拟 M2。在游戏中,这意味着我可以先走,它可以跟随;它也可以先走,我可以跟随。

但是,双向模拟是否等同于语言等价?不是。我们稍后会看到,你可以有两台机器相互模拟,但却不是双向模拟。例如,如果游戏是交错进行的(一步我主导,下一步它主导),可能会得到不同的行为。

形式上,给定 M1 和 M2,如果存在一个模拟关系 S,起始于初始状态对,并且对于 S 中的所有状态对 (s1, s2) 和所有输入 i,满足以下两个条件,则 M1 与 M2 是双向相似的:

  1. 对于 M2 的每个可能转移 (s2, i) -> (s2‘, o2),存在 M1 的转移 (s1, i) -> (s1‘, o1),使得 (s1‘, s2‘) 属于 S,且 o2 等于 o1。
  2. 反之亦然,对于 M1 的每个可能转移,存在 M2 的相应转移,且输出相等。

这与之前模拟的定义不同,之前只要求 M2 的输出包含在 M1 的输出中,且是单向的。双向模拟要求输出完全相同,并且关系是对称的。

模拟与包含

如果 M1 模拟 M2,那么我可以确定输出序列是相同的。但是,如果两台机器的输出序列相同,我不能断言它们之间存在模拟关系。这是因为在有限状态机中移动时,输出序列并不能讲述完整的故事,状态的变化有时也很重要,而这正是模拟所捕获的,但输出等价没有捕获。

模拟关心的是状态的对齐。这就像著名的“帷幕”比喻:语言等价就像帷幕后的机器,我不关心这家伙在里面做什么,只要最终答案一样。模拟概念则意味着它们遵循相同的步骤序列,就像人类思维遵循的步骤一样。它更强,因为它关心如何得到解决方案,而不仅仅是拥有相同的输入输出。

总结

本节课中我们一起学习了状态机精化与等价的核心概念。

  • 类型精化:确保 M2 可以替换 M1 而不会引起输入输出空间的冲突。这是最基本的要求,但在实际设计中,因疏忽造成的类型错误可能导致严重后果。
  • 语言精化:M2 的行为(语言)是 M1 行为的子集。这意味着 M2 能做的任何事情,M1 也能做(在行为层面)。
  • 模拟精化:在每个反应步骤,M2 只能产生 M1 也能产生的输出,并且存在状态对应关系(模拟关系)来保证这一点。它比语言精化更强,考虑了内部状态路径。
  • 双向模拟:M1 和 M2 可以相互模拟,输出完全相同,且状态转移路径相对应。这是最强的等价形式之一。

在所有情况下,如果 M1 在某个系统中是有效的(根据所选度量标准),那么 M2 也是有效的。这就是精化的概念,在自上而下的设计过程中,会经历一系列精化步骤,从规范最终得到实现。

理解这些不同的等价和精化关系,对于正确进行系统设计、验证和组件替换至关重要。

14:可达性分析 🧭

在本节课中,我们将学习可达性分析,这是一种用于验证系统设计是否满足特定安全性和活性属性的关键技术。我们将探讨其基本概念、算法实现以及在实际应用中的挑战。

课程概述与项目安排

在开始正式内容之前,我们先回顾一下课程相关的实验和项目安排。

以下是关于项目的重要时间节点和指导原则:

  • 项目组队:团队由3-4人组成,可以自由选择队友。
  • 项目范围:可以是研究型或应用型,但必须基于课程提供的硬件平台列表,并遵循嵌入式系统的设计方法学。
  • 设计方法:鼓励使用模型进行设计和仿真,避免临时拼凑。需要构建良好的软件架构,包括模块化分解、测试策略和代码注释。
  • 关键时间点
    • 10月21日:提交项目章程(组队和项目大纲)。
    • 11月4日:与助教和导师进行项目评审。
    • 11月17日:项目进度迷你更新。
    • 11月25日:项目里程碑检查。
    • 12月17日:项目成果展示(每人约15-20分钟)。
    • 12月19日:提交最终报告、演示视频和同行评价。
  • 项目激励:优秀项目有机会获得奖励,并可能被推荐发表会议或期刊论文。

可达性分析简介

上一节我们介绍了如何使用时序逻辑来形式化地描述系统属性。本节中,我们来看看如何验证一个设计是否满足这些属性,这就是可达性分析的核心目标。

在机器人学或其他嵌入式系统应用中,一个典型问题是:我们希望系统能够到达某个目标状态,同时避免进入一系列“坏”状态。可达性分析正是解决这类问题的工具。通过分析系统所有可能到达的状态集合,我们可以判断:

  • 如果任何可达状态是“坏”状态,则系统存在隐患。
  • 如果目标状态包含在可达状态集合中,则系统能够达成目标。

模型检验与形式化验证

可达性分析是模型检验算法的基础。模型检验是一种用于验证形式化属性的算法,它通过系统地探索系统的状态空间来工作。该技术的发明者曾因此获得图灵奖,这凸显了其重要性。

形式化验证的流程如下:

  1. 建立模型:需要同时为系统和其运行环境建立模型。
  2. 组合成闭系统:将系统模型与环境模型组合,形成一个没有外部输入的“闭系统” M
  3. 定义属性:用时序逻辑公式 P 描述需要验证的属性。
  4. 验证引擎:运行验证引擎,检查属性 P 是否对模型 M 成立。如果成立,则系统满足属性;如果不成立,验证器会提供一个反例,即一条导致属性失效的状态路径,这对于调试和后续的控制器综合至关重要。

显式状态模型检验

为了验证一个形如 G P(全局属性 P)的公式,我们需要枚举所有可达状态,并检查属性 P 在每个状态上是否成立。

系统的行为通常用状态图(或可达图)来表示。从初始状态开始,根据状态转移关系一步步探索,就能构建出这个图。探索策略主要有两种:

以下是两种主要的图搜索策略:

  • 广度优先搜索:从初始状态开始,先探索所有一步可达的状态,然后是两步可达的,依此类推。它像扇面一样展开。
  • 深度优先搜索:从一条路径深入探索到底,再回溯探索其他分支。

在显式状态模型检验中,深度优先搜索常使用栈来记录当前路径。当发现一个状态违反属性时,栈中记录的就是导致该问题的反例路径。

符号化模型检验

显式遍历状态图在状态数量爆炸时会失效。符号化模型检验采用了一种不同的思路:它使用高效的数据结构(如二叉决策图)来隐式地表示和操作布尔函数,从而表征状态集合和转移关系。

其核心思想是将状态编码为布尔变量,将状态集合表示为其特征函数(一个布尔函数),将状态转移关系也表示为一个布尔函数。这样,集合运算(如并集、交集)和状态转移就可以通过布尔运算来完成。

符号化广度优先搜索的算法伪代码如下:

R = S0 // 初始可达集合
R_new = R
while (R_new != ∅) {
    // 计算从R_new出发一步可到达的新状态,并去掉已访问过的
    R_new = next_states(R_new) \ R
    R = R ∪ R_new
}

R_new 为空集时,说明所有可达状态均已找到。

状态空间爆炸与抽象技术

可达性分析面临的主要挑战是状态空间爆炸问题。即使对于中等复杂度的系统,其可能的状态数量也会随着变量增加呈指数级增长,使得完全分析变得不可能。

解决这一问题的关键策略是使用抽象。通过创建系统的简化模型来减少状态空间。一种常见的方法是局部化抽象,即移除与待验证属性无关的变量。然而,这种抽象可能会引入原本不存在的非确定性,导致验证时出现“假反例”。因此,当在抽象模型中发现反例时,必须回到原始模型中去验证该反例是否真实存在。

安全性 vs. 活性属性

在时序逻辑中,属性主要分为两类:

  • 安全性属性:表示“坏事永远不会发生”,例如“交通灯永远不会在行人通行时变绿”。这类属性通常更容易验证,并且如果违反,总会有一个有限长度的反例路径。
  • 活性属性:表示“好事最终会发生”,例如“机器人最终会拾起物体A”。验证活性属性通常比安全性属性更复杂。

有趣的是,某些活性属性可以转化为安全性属性来验证。例如,属性“最终 P 成立”(F P)等价于“并非永远非 P 成立”(¬(G ¬P))。因此,我们可以通过验证安全性属性 G ¬P 是否被违反来间接验证 F P

验证与综合

可达性分析不仅用于验证,也与控制器综合紧密相关。验证告诉我们系统是否可能进入坏状态或无法到达好状态。而综合则是主动设计一个控制器,通过生成适当的输入来引导系统,确保它避免坏状态并到达好状态。

从某种意义上说,控制器的作用是“覆盖”或“重塑”原始系统的动态行为,使其按照我们的期望运行。先进的控制器(如战斗机飞控系统)正是通过这种方式来管理高度不稳定的被控对象。

课程总结

本节课我们一起学习了可达性分析的核心内容。我们了解到,可达性分析是模型检验的基础,用于验证系统是否满足形式化规约的属性。我们探讨了显式状态和符号化状态两种分析方法,并认识了状态空间爆炸这一主要挑战及其应对策略——抽象。最后,我们看到了验证与控制器综合之间的深刻联系,两者都是通过分析系统的可达状态来确保其正确行为。掌握这些概念,对于设计和验证可靠的嵌入式系统至关重要。

16:多任务处理

概述

在本节课中,我们将学习如何使用状态机形式化地分析嵌入式系统中的决策逻辑,特别是通过组合和层次化状态机来建模复杂的并发行为,例如中断处理。我们将通过一个具体的C代码示例,探讨如何构建模型并利用形式化方法验证程序属性。

状态机与形式化分析

状态机为我们提供了一种形式化决策逻辑的方法,特别是离散的、顺序的决策过程。我们可以用它来描述相当复杂的行为。

然而,当我们手动使用状态机时,它们有时看起来并不易于处理。我们之前看到的例子都过于简化,未能完全反映现实情况的复杂性,这在使用气泡和弧线图时可能导致难以处理的复杂度。

为了手动处理更复杂的逻辑,我们为状态机添加了一些符号。例如,扩展状态机使我们能够在状态机中操作变量值。此外,状态机的并发组合允许我们将两个相对简单的状态机同步或异步地组合起来。组合本身定义的状态机可能比两个独立组件复杂得多。这是一个通用的工程原则:将复杂系统分解为简单组件。这并没有消除复杂系统中的复杂性,只是使其更易于理解。

但状态机的真正价值在于,如果你有一个良好的形式化状态机模型,那么它就适用于机械化分析。这才是其真正价值所在。当然,它对我们有限的大脑尝试理解系统也很有价值。仅仅尝试为某个系统构建状态机描述就能给你带来很多洞见。因此,它对人类认知部分是有价值的,但因为它可以接受自动化工具的分析而更具价值,这些工具可以揭示你未预料到的行为,例如用于检查安全条件。

今天我将尝试说明这一点,并且为了手动演示,我不得不再次过度简化状态机,但希望它能让你了解如何利用状态机进行自动化分析。

状态机组合回顾

首先,我们回顾一下同步组合。这是一个非常简单的例子:两个没有输入的状态机。在我们的状态机形式主义中,一个关键原则是状态机本身不决定何时反应。状态机对何时反应没有说明,环境选择状态机何时反应。当你组合状态机时,环境选择组合何时反应。然后你可以选择组合反应意味着什么,但环境将选择组合何时反应。

如果你选择同步组合,那么两个机器会同时且瞬时地反应。这样做时,它们实际上表现得像另一个状态机,一个单一的状态机。反应是同时且瞬时的。它会将你从初始状态 S1 S3(即这个初始状态)带到下一个状态 S2 S4,一步完成,瞬时发生,并产生输出 B。然后在下一个反应中,当你在状态 S2 S4 时,你将瞬时回到初始状态。

当你进行状态机组合时,首先要做的是尝试理解组合的状态空间。获得组合状态空间的一种暴力方法是直接形成乘积空间。每个状态机都有一定数量的状态。在这个例子中,这些不是扩展状态机,所以状态数量与气泡数量匹配。但如果是扩展状态机,则不一定。无论如何,每个状态机都有一定数量的状态。如果你有两个状态机,一个有一定数量的状态,另一个也有一定数量的状态,那么组合机器的总可能状态空间就是这两个状态空间的乘积。在这个例子中,这里的两个状态和那里的两个状态给了我们四个状态。

顺便说一下,仅仅形成乘积空间就说明了为什么这是一个强大的方法,因为你可以用状态不多的状态机来描述具有许多可能状态的复杂系统。这就是工程价值的所在,能够模块化系统。

在这个例子中,乘积空间有四个状态,但其中两个状态是不可达的。从初始状态没有路径到达它们。那么它们是否在状态空间中?某种程度上是,但也不完全是。事实上,我们稍后会讨论状态机等价的概念。我这里描述的是一个四状态的状态机,只是碰巧有两个状态不可达。但这个状态机在很强的意义上等价于一个不包含这些状态的两状态状态机。我们可以形式化这个等价概念,事实上我们也会这样做。形式化这个等价概念非常有用,因为你可以确定在简化系统时是否改变了其行为。

异步组合

如果我们取相同的两个状态机,并使用异步组合来组合它们,那么我们有一些选择,因为“异步组合”这个短语本身不足以告诉我们两个机器反应意味着什么。

如果我为异步组合选择交错语义,那么组合的一次反应包括:首先,非确定性地选择两个机器中的一个,让该机器反应。然后在下一个反应中,我非确定性地选择两个机器中的一个并让它反应。这就是异步组合的交错语义的含义。

由此产生的组合本身就是一个状态机,同样有四个状态。但在这种情况下,它们都是可达的,并且产生的组合是一个非确定性机器,在每个状态中有不止一个可能的反应。

应用于嵌入式软件问题

现在,让我们看看是否可以将这种组合机制应用于嵌入式软件中的一个实际问题。

还记得我们在讨论中断服务例程以及如何控制程序中的时序时,看过这段代码。想法是,我想写一个程序,先做某事两秒钟,然后再做别的事。这可能是一个初始化阶段,你的程序应该发出令人愉快的声音两秒钟,让每个人都感到高兴,然后继续执行之后可能令人讨厌的事情。无论是什么,你打算做某事两秒钟,然后继续。

那么问题是,这段代码正确吗?提出正确性问题的一种方式是:你是否确实能到达这两秒循环之后的代码点?我甚至不担心是否恰好花费两秒钟,只问是否能到达那里。如果我能证明存在某种方式使我无法到达那里,那么无论时序如何,我都清楚地发现了程序中的一个错误。

那么,我们能否使用状态机来分析这个问题?这将是关键问题。当然,这是一个C程序。根据冯·诺依曼计算模型,C程序是作为状态机执行的。在冯·诺依曼模型下,你的计算机从一个初始状态开始,即内存的内容。然后你的指令集架构有一组操作来转换内存(这里的内存包括寄存器和所有东西,所有机器的可能状态,机器中的所有内存)。每条指令改变状态。

现在,状态的数量是巨大的。如果你有两GB内存,两GB是160亿比特。所以这台机器主内存的状态有2的很多次方个,状态非常多。因此,你不会想用气泡和弧线图来表示这台机器的状态,这行不通。

所以我们必须以某种方式抽象这个程序的功能。一种有用的抽象程序的方法是只关注流程控制。执行如何通过程序中的指令序列进行?我们可以识别程序中的关键点。例如,我标识了一个位置 D,就在分支之前,和一个位置 E,就在分支之后。类似地,while语句也是一个分支,所以我有一个位置 A,就在进入while循环之前;位置 B,当我开始while循环体时;以及位置 C,这是我最终感兴趣的:我是否能到达C。这将是我要问的问题。

如果我说这些是程序中所有有趣的点,那么我能否使用状态机明确地回答这个问题?我想再次强调“形式化”这个词,因为非正式的做法是你挠挠头,看看这个程序,想出所有可能的执行线程,形成对程序如何工作的理解,然后得出结论。问题在于,对于非平凡的系统(这是一个相当平凡的系统,所以你也许可以遍历所有可能性,但即使这样,你也很可能错过一些),但对于更复杂的系统,我保证你会错过一些。你肯定会错过一些。因此,形式化机制意味着我们实际上可以进行穷举搜索。你可以一次性研究所有可能的执行,这就是该方法的威力。

构建状态机模型

让我们尝试将状态机应用于这个模型。这是两个部分的状态机:一个主程序和一个中断服务例程。在良好的工程实践中,我想模块化这个程序,我将用状态机描述这两个组件中的每一个。

我可以这样做。顺便说一下,有很多选择。构建这些模型是一门艺术,尽管你也可以机械化地构建这些模型。但是对这个程序进行机械化分析的结果将是一个大得多的状态机,我们不会想手动查看它,因为它会让你不知所措,你会想自动查看它。但是当你手动构建这些东西时,你选择一些抽象。这里是一个可能的选择。

对于主程序,我们假设在到达A之前没有有趣的事情发生,所以我们称其为初始状态。它是一个扩展状态机,我们有一个计时器计数变量,在进入初始状态时初始化为值2000。然后我们是否去B?如果计时器计数非零,我们就去B。在B之后我们做什么?我们总是返回A。这是B唯一能做的事情,因为这是一个while循环。所以B之后,我只是回到A。当我在A时,如果计时器计数为0,我将转到C。这是一个对相当简单的代码片段很好的状态机抽象。

现在看看中断服务例程。一个好问题是:B和A之间的移动何时发生?请记住,在我们的状态机形式主义中,你的状态机不能选择何时反应。所以这是一个我们必须解决的关键问题:这个东西何时会反应?以及我们将如何协调这两个状态机的反应?我们将进行同步组合、异步组合还是其他什么?对于单个状态机,实际上C程序也没有说明。C程序没有说明你何时从while循环的末尾再次进行测试。在这个模型中没有任何说明。进入这个代码块,然后让我的机器暂停,出去吃午饭,回来后再启动时钟并回到while循环,这是这个C程序完全有效的执行。C程序只说明了要执行的操作序列,没有说明何时应该执行它们。所以我们的形式主义具有与C代码相同的属性,即形式主义没有说明何时进行转换,它只是说如果你在状态B并且机器反应,那么它将转到A。

另一个问题是:如果我们在B并且计时器计数达到零,它会转到C吗?答案是否定的,在形式主义中肯定不会,因为那里没有转换,在C代码中也不会。这个模型是C代码的一个好模型,因为B是程序执行的状态,实际上是在我甚至进入这个代码块之前。所以一旦我决定进入这个代码块,我就在B。在那一刻,如果计时器计数变为零,什么也不会发生,我将执行这个代码体,直到我回去再次进行测试。C不是这样工作的,有些编程语言是这样工作的。特别是,如果我用SL写这个程序,我可以说,在监视这个计时器计数的同时执行这个。如果在这个执行期间计时器计数变为零,我可以中止这个执行并进入下一步。但这不是C的语义。C只在这个while循环的开始测试这个计时器计数,并在执行这个代码体期间忽略其值。在执行这个代码体结束时,它回到开始并再次执行测试。这正是我们的模型所说的。在执行那个代码体结束时,我们回到开始并再次执行测试。

所以问题之一是,当我把这些箭头放在这里说这是我们在代码中的位置时,我到底是什么意思?这可能有点棘手,但A的意思是我即将进行测试,以决定是执行代码体还是跳到下一个执行。然后B是我已经做出了决定,现在我将执行这个代码体。是的,如果你无限期地暂停这个代码体的执行,你的计时器中断仍然可能触发。这个模型本身,这个较低的状态机,没有说明会发生什么。因为到目前为止它不包括中断服务例程。但实际上,C程序也没有真正说明会发生什么。无限期地暂停这个并让中断服务例程继续执行,是C程序完全有效的执行。

在我们的形式主义中,转换只描述了我们在这个状态时在一次反应中做什么。实际上,这种表示法没有直接提供任何方式来说“如果我在这个状态,那么我不在当前反应中,但在未来的某个反应中,我想去C”。我们唯一能说的方式是通过一个中间状态。从B到A的转换没有守卫条件,在我们的表示法中,这只是守卫条件始终为真的布尔值的简写。所以如果你在状态B并且机器反应,这个转换将触发,我们将从B移动到A。没有问题,没有需要测试的东西。这些都是好问题,因为在这种表示法中有很多任意性。但任何形式化表示法中都有很多任意性。因此,为了使用一种表示法,你必须习惯它。你不能说它是错的,除非它自身不一致。所有一致的形式化表示法都是同样正确的。形式化意味着没有意义。最有用的形式化表示法不仅是正确的,而且是有用的。希望这个表示法既正确又有用。它确实有一些任意的决定,你必须习惯它才能使用它。

中断服务例程的状态机

好的,我希望现在每个人都理解了第一个状态机。现在,我们来看看中断服务例程的状态机。中断服务例程比主程序稍微奇怪一些,因为在主程序中,我可以明确定义一个初始状态在位置A。但对于中断服务例程,我不能真的这样做,因为当这个东西启动时,中断服务例程可能甚至不在任何地方,我不在D,也不在E。因为中断还没有被触发。所以我需要添加一个额外的状态,一个空闲状态,它只是表示我根本不在中断服务例程中。现在我需要定义一个外部输入assert,这是一个纯输入。每当提供这个外部输入时,这个中断服务例程就会转到D。然后当它在D时,它将测试计时器计数,如果计时器计数非零,它将转到E;否则,它将转换回空闲状态并发出输出return。如果它转到E,那么它将在下一次反应中无条件地递减计时器计数,发出输出return,并返回到空闲状态。

每个人都理解描述ISR的这个状态机吗?那么我应该如何组合这些?我应该使用同步组合来组合它们吗?我看到几个人摇头,为什么不行?它们不是同步的,所以那会非常奇怪,实际上会是一个不正确的行为模型。因为那会说,假设我的初始状态是空闲逗号A。然后当组合反应时,两个机器必须同时且瞬时地反应。所以如果我在输入处得到一个assert,我将转换到D,并根据计时器计数的值转换到B或C。但这不是C程序所做的。

那么异步组合是正确的做法吗?如果是,应该使用哪种变体?我应该使用交错语义吗?可能基于同样的理由,我不使用同步组合的同样原因表明,如果我要进行异步组合,最好使用交错语义。我不希望这两个机器同时反应。那么问题变得更简单:异步交错语义是组合这两个状态机的正确方式吗?是的,但有一个问题。如果我选择交错异步语义,那么我可以在B和D处断言中断服务例程,然后在下一个反应中,选择继续执行我的主程序,即使我仍然在中断服务例程中。这不是中断的工作方式。主程序在中段服务例程返回之前不会恢复执行。实际上,如果我强行构造描述这两个状态机的非确定性交错异步组合的状态机,它就在这里。这个状态机具有在实践中不会发生的状态转换。例如,它显示我可以从A D转到B D。这是在这个组合中允许的转换,但C程序不会这样做。

所以我们有点卡住了。我们有两个漂亮简单的状态机,而我们到目前为止提出的所有组合机制都失败了。我不知道如何组合这些。

层次化状态机

所以让我们选择一种新的组合语义:层次化状态机。为了做到这一点,我将不得不提出一些描述中断控制器如何工作的描述。必须有一些逻辑来管理这两个状态机如何相互交互。我必须捕捉到,如果中断服务例程开始执行,它将执行到完成。我可以捕捉关于中断服务例程的更多细节,从而开始回答Josh提出的问题:禁用中断到底意味着什么?例如,我可以有硬件,如果在我正在处理中断服务例程时发生断言,那么我会记录该断言已经发生,然后在那个服务例程返回后稍后处理它。我可以在状态机中捕捉这种行为。但这是硬件的描述,这不是C程序的描述,甚至不是指令集架构的描述。它是微处理器和计时器的特定实现的描述。

我可以这样做,但我会使用一个更简单的中断服务例程,因为使用那个四状态中断处理程序会导致PowerPoint图上有太多状态,而且你最好进行机械化分析而不是人工分析,所以我将进一步简化。

在这样做之前,让我解释一下层次化状态机。同样,在这种表示法中有很多任意性。这种风格的层次化状态机最初由David Harel在1987年引入,他称之为状态图。事实证明,他最初的状态图论文并不是真正的形式主义。有很多歧义,实际上,人们误解了他的论文,并提出了许多不同实现的状态图,所有这些都有不同的语义。有一次,我读了一篇论文,它分类了大约28种不同的层次化状态机语义变体,这些变体实际上被人们在各种上下文中使用,而且它们都不同。所以如果你想做任何事情,你必须选择一个。我将给你一个。它是形式化的,因为如果我给你一个状态机,它的行为和可能的行为集是完全定义的。

层次化意味着什么?首先,我们可能将一个细化与一个状态关联起来。在这个例子中,我有一个两状态的状态机,状态A和B。B有一个细化,它本身是一个两状态的状态机。解释这一点的方式是:如果我在状态B,我实际上在C或D中。但只能是其中一个,不能同时是两者。在Harel的表示法中,他称之为或状态,称B为或状态,因为它不是一个真正的状态,它是一个占位符,表示处于几个其他状态之一。

那么,这里的状态空间有多大?这个状态机有多少个状态?实际上是三个或四个,取决于语义中的另一个微妙之处。让我给你一个提示,然后我们再回来讨论。当我从B转换到A时,我可能记得我在C D中的位置。当我回到B时,我可能从我离开的地方继续。如果我离开B时在D,那么如果我下次返回B,如果我仍然在D,那么这个状态机实际上有四个状态。另一方面,如果我每次进入B都去C,那么这个状态机只有三个状态。David Harel也引入了这个概念,他谈到转换可以是历史转换或非历史转换。历史转换实际上让你能够非常紧凑地描述极其复杂的行为,但状态数量可能变得非常大。

现在,我们必须定义这个层次化机器反应意味着什么。根据层次化状态机的定义,一次反应包括:首先,细化反应,然后顶层机器反应。尽管它们是顺序的,但它们发生在同一次反应中,所以你将它们视为同时发生,同时且瞬时。但你仍然必须一个接一个地做。这几乎像是一个矛盾,但从外部视图来看,这意味着什么?例如,假设我在状态B,机器反应,并且守卫G1和G4都启用。那么会发生什么?首先细化反应,我从C到D,并产生输出A4。然后高层状态机反应,从B到A,并产生输出A1。这里重要的是,A1和A4是由这个状态机同时产生的。它们都在这一次反应中出现在输出上。

这是一个可能的反应序列。这是一个跟踪,一种记录可能反应的方式。我们从状态A开始,如果G2启用,我们将产生输出A2并转换到状态C。如果在下一个反应中G4启用,我们将产生A4并转换到D。如果在下一个反应中G1启用,那么我们将产生A1并转换到A。等等,我让细化反应了吗?在这个转换中,我实际上做了,我在状态D,细化反应了,但没有从D出发的转换被启用,所以它的反应是停滞,它只是停留在原地。然后高层状态机反应。然后我可能回到D。这是一个历史转换还是非历史转换?这是一个历史转换,我去了D。所以我记得我在哪里。所以现在一旦我在D,如果G3和G1都启用,那么我的反应将是同时产生A1和A3并转换到A。我下次进入B时会进入哪里?进入C,因为我采取了从D到C的转换。想想这个转换,当我从D到A时,我现在在状态A。如果我的下一个转换把我带回B,我应该去哪里?我需要记住去哪里,我必须要么去C要么去D。如果是历史转换,我将去……实际上,无论如何,它都会去C,但在这两种情况下,任何时候我从A到B,如果我有一个历史转换,我必须记住我离开的地方。所以仅仅知道你在A是不够的。如果你只知道你在状态A,你缺少一块状态信息,所以我会通过实际回答这个问题的方式来更明确地说明这一点。顺便说一下,每当你有一个状态机的组合,并且你想确切地知道它的含义时,总是将其转换为一个单一的扁平有限状态机。那个单一的扁平有限状态机就是该组合含义的定义。

扁平化状态机

这是这个状态机的一个扁平化版本。这应该也能回答Josh的问题。我最初从A C开始。希望这很清楚。然后我可以转换到B C。从B C,我可以转换到B D,或者我可以转换回A C。然而,如果我在B D,因为这是一个历史转换,转换到A C是不正确的。因为如果我在那时转到A C,那么下次我进入B时,我将在C进入而不是D。所以如果我转到A,我必须记住我在细化中的位置。A C表示我在A,如果我将来再次进入我的细化,我应该在C进入。而这个状态表示我在A,如果我将来某个时候进入我的细化,我应该去D。必须记住你需要从哪里继续的问题必须在状态机中捕捉,因为它是状态信息。

所以如果我有更多状态在这里,那么我系统中的状态总数将是那个状态数和细化中状态数的乘积。假设我在细化中有四个状态,这里有四个。那么这个东西会爆炸到16个状态;如果是八个和八个,它会爆炸到256个状态。因此,你可以非常紧凑地表达非常复杂的行为。

这里的表示方式解释是:我在状态A,下次我进入B时,我应该在C进入。记住,状态是关于过去的一切,我需要这些信息才能前进到未来。这就是拥有状态的含义。

现在,假设我们没有历史转换,而是有一个重置转换。那么我就不再需要记住我离开的地方了。所以扁平化的状态机只需要有一个单一的状态来表示A,我不需要记住我离开的地方。所以如果我在A,如果我将来重新进入B,我将在C重新进入。所以重置转换更紧凑一些。

另外,作为一种符号上的便利,我们允许抢占式转换,David Harel也引入了这些。抢占式转换是指如果守卫启用,我们不允许细化反应。如果守卫启用,我们就排除了这种可能性。确保你理解这个状态机的扁平化版本以及抢占式转换是如何在这个扁平化版本中捕捉的,这将是一个很好的练习。

应用层次化状态机

现在让我们应用它。这是我的简化中断控制器。我只想说,如果我的中断不活动,那么我将有一个抢占式转换。所以当这个机器反应时,如果a为真,我将不允许细化反应。相反,我将立即转换到活动状态。在活动状态,当那个细化产生输出return时,我将使用历史转换转换回不活动状态。现在这开始看起来像一个中断了。实际上,我可以插入我的中断代码,看到,是的,这确实是它的行为方式。如果我使这些状态机细化,我可以说,我正在执行主程序,无论我在哪里。如果assert发生,主程序不允许反应,我将只是停留在原地。我将转换到我的中断服务例程,它现在不再需要空闲状态,它有一个初始状态D。注意,这是一个重置转换。当我进入中断服务例程时,我总是进入初始状态。我执行直到产生输出return,此时我将使用历史转换转换回不活动状态,并从这里我离开的地方恢复。

形式化分析与验证

现在我们有了一个形式化模型,我可以问一个形式化问题:C是否总是被到达?它没有说明速度,它只是说,如果我查看反应的无限历史,将有无限次反应中assert被断言。实际上,我不确定我是否需要这个约束来回答这个问题,因为如果我只有有限次断言,我不认为这会改变答案。这试图说明的是,我不想检查中断请求从未发生或只发生一次的特殊情况。实际上,这很重要,因为如果中断请求从未发生,计时器计数变量永远不会改变,因此,实际上我永远不会到达C,因为计时器计数永远不会达到零。所以我确实需要那个约束。它说的是,计时器硬件没有故障,它将持续请求中断。这就是那个假设所断言的。

现在我们有形式化模型,我们可以问形式化问题,并且只有一个答案:要么C总是被到达,要么C不是总是被到达。这里没有两个答案,它不再受人类解释的影响。那么答案是什么?如果在每次反应中assert都为真,那么实际上C永远不会被到达。

我想强调这一点,因为如果你在编写嵌入式软件,并且设置一个计时器每两毫秒触发一次,你不会想到这一点。你可能会想,它不会一直断言中断。但实际上,根据系统的定义方式,中断请求是一个输入,你对输入没有控制权。如果你的硬件有固定故障,实际上你的中断请求线一直为真怎么办?实际上,在我们定义的系统中,这是环境的一个有效情况,因为它提供了一个输入assert,只是碰巧一直提供它。但这也可能在C程序的实践中发生。假设这个代码体总是比中断之间的时间长,那么实际上,assert将总是……不,这不完全正确。假设这个代码体总是比中断之间的时间长,所以中断服务例程实际上需要很长时间来完成其动作,长到计时器硬件再次触发。那么它可能发生。但是要再次启用它们,所以如果计时器已经将中断请求线拉高,它可以保持高电平直到得到反应。

所以即使中断被禁用并且这些中断不嵌套,我很高兴你提出了嵌套的问题,因为如果你允许嵌套中断,这个层次化状态机可以被扁平化。现在,对于扁平化的状态机,我可以进行穷举搜索。我可以查看所有可能的行为,并找到一个从未到达C的跟踪。那个跟踪被称为反例。它证明了我关于这个程序最终会到达C的断言是不正确的。根据程序的定义方式,这就是你可以进行的那种分析。

如果你允许嵌套中断,那么你有一个更困难的分析问题,因为状态机现在变成无限的了。如果你考虑它在C中的工作方式,实际上,在C的逻辑中,它是无限的,因为每次中断发生时,一堆东西被压入堆栈,你进入中断服务例程,如果允许嵌套中断,当你在那个中断服务例程中时,如果发生中断,你将更多东西压入堆栈。你的堆栈无限增长,那是无限状态。所以那个分析问题变得困难得多。

总结

在本节课中,我们一起学习了如何使用状态机对嵌入式系统中的并发行为进行形式化建模和分析。我们回顾了同步和异步组合,并引入了层次化状态机来解决传统组合方式无法准确建模的问题(如中断处理)。通过一个具体的C程序示例,我们构建了主程序和中断服务例程的状态机模型,并利用层次化状态机(包含抢占式转换和历史转换)将它们组合起来。最后,我们展示了如何通过形式化分析(如状态空间探索)来验证程序属性(例如“代码点C是否总能到达”),并发现了一个反例,说明在特定环境行为下程序可能无法达到预期状态。这凸显了形式化方法在发现潜在错误和确保系统正确性方面的价值。

16:操作系统、微内核与调度 🖥️

在本节课中,我们将学习操作系统、微内核以及任务调度的基本概念。我们将探讨多任务处理的原理、其潜在问题,以及如何通过线程和互斥锁等机制来管理并发。课程内容将涵盖从底层中断机制到高级线程库(如Pthreads)的各个方面,并分析多线程编程中常见的陷阱和解决方案。


多任务处理与并发

上一节我们介绍了嵌入式系统的基本架构,本节中我们来看看多任务处理。多任务处理是指系统能够同时处理多个任务的能力。在计算机中,这通常通过并发机制实现。

并发是指逻辑上同时执行多个任务,无论它们是否在物理上同时执行。如果任务在物理上同时执行,我们称之为并行。在嵌入式系统中,并发通常通过中断机制实现,这是一种低级的并发方式。


操作系统与微内核

从底层中断机制向上抽象,操作系统或微内核提供了更高级的并发模型。微内核是一种轻量级的操作系统,通常不具备文件系统等完整功能,但能够提供任务调度和并发管理。

微内核的主要目标是在资源受限的嵌入式系统中提供高效的并发支持。它通过管理线程和调度任务,使得多个任务能够在单个处理器上交替执行。


线程与进程

在讨论并发模型之前,我们需要区分线程和进程。进程是操作系统分配资源的基本单位,每个进程拥有独立的内存空间。线程是进程内的执行单元,多个线程共享同一进程的内存空间。

在嵌入式系统中,微内核通常直接管理线程,而不提供内存保护机制。这意味着所有线程都可以访问系统的全部内存,这增加了灵活性的同时也带来了风险。


Pthreads 线程库

Pthreads(POSIX线程)是一种跨平台的线程库,广泛应用于Unix、Linux、OSX和Windows系统。它提供了一套标准的API,用于创建和管理线程。

以下是使用Pthreads创建线程的基本步骤:

  1. 定义线程函数:线程函数是线程执行的具体任务。
  2. 创建线程:使用pthread_create函数创建线程。
  3. 等待线程结束:使用pthread_join函数等待线程执行完毕。

以下是一个简单的示例代码:

#include <pthread.h>
#include <stdio.h>

void* thread_function(void* arg) {
    int* value = (int*)arg;
    printf("Thread received value: %d\n", *value);
    return NULL;
}

int main() {
    pthread_t thread_id;
    int value = 42;
    pthread_create(&thread_id, NULL, thread_function, &value);
    pthread_join(thread_id, NULL);
    return 0;
}

在这个示例中,主线程创建了一个新线程,并传递了一个整数值作为参数。新线程打印该值后结束,主线程通过pthread_join等待其结束。


线程调度与原子操作

线程调度是操作系统的核心功能之一。在单处理器系统中,线程通过交替执行实现并发。线程可以在任何两个原子操作之间被挂起,原子操作是指不可中断的操作。

然而,在C语言中,我们很难确定哪些操作是原子的。这导致线程调度的行为难以预测和控制,从而增加了多线程编程的复杂性。


多线程编程的陷阱

多线程编程虽然强大,但也存在许多陷阱。以下是常见的问题及其解决方案:

1. 竞态条件

竞态条件是指多个线程同时访问共享资源,导致结果依赖于线程执行的顺序。例如,两个线程同时修改同一个变量,可能导致数据不一致。

解决方案:使用互斥锁(Mutex)保护共享资源。互斥锁确保同一时间只有一个线程可以访问临界区。

2. 死锁

死锁是指多个线程相互等待对方释放资源,导致所有线程无法继续执行。例如,线程A持有锁1并等待锁2,而线程B持有锁2并等待锁1。

解决方案:遵循固定的锁获取顺序,避免循环等待。此外,可以使用超时机制或死锁检测算法来预防和解决死锁。

3. 数据不一致

数据不一致是指多个线程对共享数据的修改导致系统状态出现矛盾。例如,一个线程更新了数据,但另一个线程读取了旧数据。

解决方案:使用同步机制(如条件变量)确保数据的一致性。此外,可以复制共享数据以避免直接修改。


互斥锁的使用

互斥锁是保护共享资源的基本工具。以下是使用Pthreads互斥锁的示例:

#include <pthread.h>

pthread_mutex_t lock;

void* thread_function(void* arg) {
    pthread_mutex_lock(&lock);
    // 临界区代码
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main() {
    pthread_mutex_init(&lock, NULL);
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, thread_function, NULL);
    pthread_join(thread_id, NULL);
    pthread_mutex_destroy(&lock);
    return 0;
}

在这个示例中,互斥锁lock用于保护临界区代码,确保同一时间只有一个线程可以执行该代码。


设计模式与并发

为了简化多线程编程,可以使用一些设计模式。例如,观察者模式(Observer Pattern)用于在对象状态变化时通知所有依赖对象。然而,在多线程环境中实现观察者模式需要特别注意线程安全性。

以下是一个简单的观察者模式实现:

#include <pthread.h>
#include <stdlib.h>

typedef struct Listener {
    void (*notify)(int);
    struct Listener* next;
} Listener;

Listener* head = NULL;
pthread_mutex_t lock;

void add_listener(void (*notify)(int)) {
    pthread_mutex_lock(&lock);
    Listener* new_listener = (Listener*)malloc(sizeof(Listener));
    new_listener->notify = notify;
    new_listener->next = head;
    head = new_listener;
    pthread_mutex_unlock(&lock);
}

void update_value(int new_value) {
    pthread_mutex_lock(&lock);
    // 更新共享变量
    pthread_mutex_unlock(&lock);
    // 通知所有监听器
    Listener* current = head;
    while (current != NULL) {
        current->notify(new_value);
        current = current->next;
    }
}

在这个实现中,互斥锁用于保护监听器列表,确保在添加监听器时不会发生竞态条件。然而,通知监听器时释放锁可能导致数据不一致,因此需要进一步优化。


总结

本节课中我们一起学习了操作系统、微内核和任务调度的基本概念。我们探讨了多任务处理的原理、线程与进程的区别,以及如何使用Pthreads库创建和管理线程。此外,我们还分析了多线程编程中常见的陷阱,如竞态条件、死锁和数据不一致,并介绍了使用互斥锁和设计模式来解决这些问题的方法。

多线程编程虽然复杂,但通过合理的同步机制和设计模式,我们可以编写出高效且可靠的并发程序。在接下来的课程中,我们将进一步探讨更高级的并发模型和嵌入式系统的其他关键主题。

17:调度异常与经典调度理论 🧠

在本节课中,我们将学习嵌入式系统中的任务调度,特别是经典调度理论。我们将探讨调度异常、不同调度策略的假设与局限性,以及如何在实际设计中应用这些理论作为指导。


从线程到任务:调度问题的抽象

上一节我们介绍了线程的概念。本节中,我们将线程进一步抽象为“任务”。任务通常由线程实现,但也可能是被调用的过程或通过其他方式实现。调度器的核心职责是决定在给定时刻执行哪个任务。

调度器的基本机制

调度器通常作为微内核的核心部分运行。它依赖于一个周期性触发的定时器中断(例如,在Linux中称为“jiffy”,默认间隔为10毫秒)。当中断发生时,调度器需要决定是继续执行当前任务,还是切换到另一个任务。

以下是调度器在切换线程时需要执行的关键步骤:

  1. 保存当前线程的机器状态(寄存器、返回地址/栈指针)到其线程数据结构中。
  2. 从就绪线程列表中,根据调度策略(如优先级)选择下一个要执行的线程。
  3. 将新线程的状态从其数据结构加载到机器寄存器中,并更新栈指针。
  4. 执行中断返回,新线程开始运行。

调度策略:抢占式与非抢占式

调度决策可以在不同时机进行,主要分为两类:

  • 非抢占式调度:调度器只能在当前任务主动结束或阻塞时(例如,等待互斥锁)才进行切换。任务一旦开始运行,就会一直执行到完成。
  • 抢占式调度:调度器可以在定时器中断发生时,或在任何可能引起阻塞的系统调用(如获取互斥锁失败)时,强制暂停当前任务并切换到另一个任务。

还有一种折中的协作式多任务,任务在调用特定函数时才可能被切换,但这可能导致任务独占CPU。

在实时系统中,我们主要关注抢占式调度,并假设任何时刻都在执行优先级最高的就绪任务

经典调度理论:假设与现实

调度理论提供了许多优美的结果,但必须牢记,这些理论都建立在一系列理想化且通常不现实的假设之上。理论可以作为设计指南,但不能保证实际系统完全按定理运行。

最重要的假设通常包括:

  • 任务的最坏情况执行时间(WCET)是已知的。(现实中这很难精确确定)
  • 任务之间没有交互和依赖。(现实中任务常共享数据和资源)
  • 任务没有互斥锁或其他同步原语。(下节课将讨论引入锁后的复杂情况)
  • 上下文切换开销为零。(现实中切换需要时间)

速率单调调度(RMS)

这是由Liu和Layland于1973年提出的经典理论,是最常被引用的结果之一。

核心假设

  • 有N个周期性任务,周期为 T1, T2, ..., Tn。
  • 每个任务有已知的最坏情况执行时间 C1, C2, ..., Cn。
  • 任务间无交互无依赖关系
  • 采用固定优先级抢占式调度

定理

如果存在任何一种优先级分配方案能使调度可行(即每个任务都能在其周期内完成一次执行),那么按周期越短、优先级越高的规则(速率单调优先级分配) 也一定能产生可行的调度。

公式表示
对于任务 i 和 j,如果 Ti < Tj,则在速率单调调度中,优先级(i) > 优先级(j)

定理含义
该定理表明RMS在“可行性”意义上是最优的。也就是说,如果连RMS都无法满足所有任务的截止时间,那么其他任何固定优先级方案也做不到。它为优先级分配提供了一个简单可靠的启发式规则:给周期短的任务高优先级

最早截止时间优先(EDF)

由Horn在1974年提出,基于不同的优化目标和假设。

核心假设

  • 任务可以是周期性的或非周期性的(任意到达时间)。
  • 每个任务有明确的截止时间
  • 任务间无交互

调度策略

在任何时刻,都执行当前已就绪任务中绝对截止时间最早的那个任务。这是一个动态优先级的抢占式策略。

定理

EDF在最小化最大延迟的意义上是最优的。最大延迟定义为所有任务中(完成时间 - 截止时间)的最大值。

与RMS的比较

  • 优化目标不同:RMS保证可行性(任务在周期内完成),EDF最小化最坏情况下的延迟。
  • 优先级性质:RMS是固定优先级,EDF是动态优先级(截止时间越近,优先级越高)。
  • 处理器利用率:一个重要的理论结果是,对于周期性任务,EDF可以达到100%的处理器利用率,即只要任务总负载不超过CPU能力,就存在可行调度。而RMS在任务数趋于无穷时,可调度的最大利用率上限约为 69.3%
  • 异常行为:当过载导致调度不可行时,RMS可能导致低优先级任务完全“饿死”,而EDF则会让所有任务的延迟相对均匀地增加。

其他经典算法:最早到期日(EDD)

由Jackson在1955年提出,是运筹学中的经典结果。

场景

  • 一组独立的、一次性的任务。
  • 所有任务在开始时均已知,且无抢占
  • 每个任务有明确的到期日(截止时间)。

调度策略

按照到期日从早到晚的顺序依次执行任务(非递减截止时间顺序)。

定理

EDD算法能够最小化最大延迟

这个算法简单直观,适用于可以非抢占式批量处理的任务场景。


总结

本节课我们一起学习了嵌入式系统中的核心调度理论:

  1. 我们首先将线程抽象为任务,并理解了调度器进行上下文切换的基本机制。
  2. 我们区分了抢占式与非抢占式调度,实时系统通常基于抢占式调度和优先级。
  3. 我们重点学习了三大经典调度理论:
    • 速率单调调度(RMS):在固定优先级、周期性任务且无交互的理想假设下,按周期分配优先级是最优的(针对可行性)。
    • 最早截止时间优先(EDF):在动态优先级下,执行截止时间最早的任务,能最小化最大延迟,并能达到更高的处理器利用率。
    • 最早到期日(EDD):针对非抢占式一次性任务,按截止时间顺序执行可最小化最大延迟。
  4. 我们反复强调了这些理论的理想化假设(如已知WCET、无任务交互)在现实中往往不成立,因此理论应作为设计指南而非绝对保证。

下一节课,我们将探讨当现实因素“侵入”这些理想模型时会发生什么,特别是考虑任务间存在依赖关系和使用互斥锁所带来的调度异常和挑战。

19:同步反应模型

在本节课中,我们将要学习同步反应模型。这是一种用于设计和分析嵌入式系统的形式化方法,其核心思想是通过消除不确定性来确保系统行为的可预测性。我们将探讨其历史背景、核心概念、组合方式,特别是如何处理棘手的反馈回路问题。

同步反应模型的历史与动机

上一节我们介绍了并发系统组合的挑战,本节中我们来看看同步反应模型如何解决这些问题。

同步反应模型起源于法国的同步语言社区,例如 Esterel、Lustre 和 Signal。其设计动机源于硬件设计领域。在 20 世纪 70 年代,异步电路设计面临竞争条件问题,电路输出可能依赖于数据路径的延迟,导致行为不确定。

为了解决这个问题,硬件设计引入了锁存器和时钟信号。时钟周期性地对电路状态进行采样,只要时钟周期足够长,电路就能在采样前达到稳定状态,从而确保每次计算的结果是确定性的,与数据路径的延迟无关。

法国社区的研究者思考:既然图灵模型的异步性是软件难以编写正确程序的根本原因,为何不将硬件的同步思想应用到软件中?于是,同步反应模型应运而生。其核心原则是:系统的所有组件以锁步方式运行。当输入到达时,系统会“瞬间”查看所有变量的快照,基于此快照决定下一步的计算,然后等待下一个输入。这里的“瞬间”意味着在模型抽象中,时间概念被移除,计算被视为在逻辑时间(或反应索引)上发生的离散步骤。

同步系统的核心特征

同步系统的根本特征在于同步轮次的概念。一个同步轮次发生在一个反应索引到下一个反应索引之间。

  • 在每个反应索引上,系统读取所有输入。
  • 在同一反应索引上,系统产生所有输出。
  • 计算被视为在反应索引上“瞬时”完成。

考虑一个由模块 A、B、C 组成的同步框图,其中 A 的输出同时作为 B 和 C 的输入。在同步世界中,A 产生输出后,B 和 C 基于此输出“瞬时”计算。无论 B 和 C 的实际计算顺序如何,最终结果都是确定的。这实现了确定性并发。相比之下,使用线程的异步模型虽然能加速计算,但会因为交错执行而导致复杂且难以分析的行为。

同步反应语言(如用于铁路控制的 Esterel 和用于航空电子的 Lustre)的优势在于其程序可以通过构造被证明是正确的。当然,这种确定性的代价可能是性能,因为每个反应索引都需要评估所有组件,即使某些组件可能无需工作。

有限状态机的同步组合

既然在同步反应模型中一切以锁步进行,使用有限状态机进行建模就显得非常自然。当我们组合多个 FSM 时,需要定义三件事:

  1. 各部分的行为。
  2. 通信模式(即输入输出变量如何连接)。
  3. 通信的含义(即连接代表什么)。

在同步反应模型中,最常用的组合方式是同步并行组合。这并非指物理连接上的并行,而是指行为组合的机制。

以下是并行组合的定义:
假设有两个 FSM:M1 和 M2。它们的并行组合 M1 || M2 是一个新的 FSM。

  • 状态集是 M1 和 M2 状态集的笛卡尔积。
  • 转移存在于组合状态 (s1, s2)(s1‘, s2’),当且仅当在相应输入下,M1 能从 s1 转移到 s1‘,且 M2 能从 s2 转移到 s2’。
  • 反应是同时且瞬时的。

考虑一个级联组合的例子:FSM1 接收输入 a,产生输出 b;FSM2 接收输入 b,产生输出 c。
组合系统的状态是 FSM1 和 FSM2 状态的乘积。通过分析所有可能的输入(a 存在或不存在),我们可以推导出组合 FSM 的转移。通常,乘积状态空间很大,但许多状态可能是不可达的,实际分析中可以剔除。

反馈回路的挑战与固定点

反馈在控制系统中至关重要,但它也为同步组合带来了独特的挑战。反馈意味着一个组件的输出被用作其自身的输入。

在同步反应模型中,反馈连接施加了一个约束:在同一个反应索引上,反馈路径上的信号值必须满足一个方程。例如,对于一个函数 F,其输出被反馈回输入,这就要求存在一个值 S,使得 S = F(S)。这被称为固定点方程

系统是否有唯一、可确定的行
为,取决于这个方程:

  • 良构:存在唯一解。
  • 非良构:无解或多解。

Moore 机和 Mealy 机在处理反馈时表现不同:

  • Moore 机:输出仅取决于当前状态。这相当于在反馈路径中引入了一个“延迟”(输出基于上一反应索引的状态),因此通常能保证良构的反馈。
  • Mealy 机:输出取决于当前状态当前输入。这意味着输入到输出是“零延迟”的,容易导致固定点方程无解或多解,从而产生非确定性。

为了确定反馈系统是否良构,我们可以使用构造性方法(即“研磨机”算法):

  1. 将所有内部信号初始化为“未知”(除了已知的原始输入)。
  2. 根据逻辑规则(例如,AND 门的一个输入为 0,则输出必为 0)传播已知值,尽可能地将“未知”替换为确定值(0 或 1)。
  3. 重复此过程,直到没有新的值可以被推断出来。
  4. 如果最终所有信号都变为确定值,则找到了唯一固定点,系统是构造性的(因而是良构的)。如果仍有信号为“未知”,则构造性方法失败,系统可能非良构(即使数学上可能存在固定点)。

构造性语义与编译器

同步反应语言(如 Esterel)的编译器使用构造性语义来分析程序。如果编译器通过构造性方法能找到固定点,它就接受该程序并生成代码。如果方法失败(仍有未知),即使理论上可能存在解,编译器也会拒绝该程序,因为它无法保证生成确定性的实现。

这对于避免电路中的振荡等潜在问题至关重要。一个在纯布尔逻辑分析下有唯一固定点的电路,在实际中可能因为微小延迟而振荡,消耗功率甚至损坏。构造性语义通过要求信号值能被逐步推导出来,排除了这类有问题的设计。

总结

本节课中我们一起学习了同步反应模型。我们从其源于硬件设计的历史讲起,理解了它通过锁步执行和逻辑时间消除并发不确定性的核心思想。我们探讨了有限状态机的同步并行组合方式,并深入分析了同步模型中反馈回路带来的独特挑战——即求解固定点方程。最后,我们介绍了使用构造性语义来判断反馈系统是否良构的实用方法,这也是许多同步语言编译器的工作原理。同步模型虽然可能在性能上有所取舍,但它为构建高可信度的确定性并发系统提供了强大的理论基础和实用工具。

21:数据流模型 🚀

在本节课中,我们将学习数据流模型。这是一种用于描述异步、确定性并发系统的模型,特别适用于流处理和嵌入式系统设计。我们将探讨其核心概念、分析技术以及如何利用调度理论来设计高效、可预测的系统。


课程公告 📢

以下是三个重要通知。

  • 如果你没有留意邮件,可能错过了这个通知:请使用Doodle投票工具注册下周的项目小型演示。我计划参加所有演示,希望能给大家一些反馈,并期待看到出色的进展。
  • 你们在实验室使用的CyberSim工具仍处于实验阶段。实际上,只有少数研究生将其作为主要研究方向,他们非常希望得到你们的反馈。有一份匿名调查问卷,我们会知道你是否填写了它,但无法将反馈与个人对应。
  • 明天下午有一场讲座,我认为你们可能会非常感兴趣。Philip Koopman来自卡内基梅隆大学,他曾任职于NASA委员会,负责调查丰田汽车在发生一系列意外加速事故后的软件开发实践。他有一些故事,我认为会非常有趣,并且与本课程高度相关。

回顾与引言 🔄

上一节我们介绍了使用形式化工具(如Spin和LTL)进行模型验证。本节课我们来看看数据流模型。

我希望你们喜欢假期,也相信大家都享受使用Spin的过程。正如我多次提到的,本课程的目标之一是让你们了解当前的技术前沿,并培养一定的批判性思维。我们希望你们能使用最好的工具。

我真心希望我们在发布期中考试答案之前,能用Spin验证一下题目。我们没有这样做,结果出了问题,这算是自食其果。因为我们一直在强调,使用像LTL这样的形式化语言和状态机这样的形式化模型,可以对它们进行机械化分析。而对于非形式化模型,则无法进行这种分析。如果期中考试的真假判断题是用英文写的,你们也可能犯和我们一样的错误。

实际上,你们更可能犯错,也更难发现错误。直到Antonio准备Spin教程并决定使用我们的期中考试题时,我们才发现其中一个错误。显然,班上那些给出了我们认为错误答案的同学,没有提供让我们信服的解释。直到我们使用了形式化分析工具,才能确定那里到底发生了什么。在某种程度上,我们没有遵循自己所宣扬的原则,即没有使用工具验证我们的期中考试题。

但我希望你们实际创建Spin模型的经历也能带来一些批判性思考。使用这类工具时,有一点让我感到不安:你可以对工具给出的答案有很高的信心。它能明确告诉你,你指定的LTL公式是否被你指定的状态机满足。但它不能告诉你的是,你指定的状态机是否就是你想要指定的那个状态机。实际上,很难确保你指定给Spin的状态机就是你想要的那个。这部分原因在于,我认为Promela语言相当不美观,而不美观的语言可能导致编程错误。当然,美观的语言也可能导致错误。所有语言都可能导致错误。最好使用能让错误更明显的语言。

对于小型状态机,图形化语言或至少能将你的规范图形化呈现的语言可能很有用,因为图形可视化非常有用。但对于大型状态机,这就不太奏效了。所以,这项技术虽然强大,基于非常可靠的理论,但也有其局限性和陷阱。


问题背景与动机 🎵

现在,让我们进入今天的主题:数据流模型。目标之一是尝试让你们了解如何将我们看过的调度理论(如最早截止时间优先调度和单调速率调度)应用于实践,尽管它们存在各种缺陷和异常情况。

具体来说,我将使用一个调度问题作为例子:一个对音频信号进行频谱分析的系统。我们有一个近似正弦的信号,正在进行实时频谱分析。这是时域信号。我们感兴趣的是对此进行调度,并且场景更复杂一些,因为我关心的是一个对现实世界有反馈的系统。

具体来说,假设你有一些需要低延迟的音频处理。在我刚才展示的应用中,并没有对物理世界的驱动。驱动是对人类视觉系统的刺激。因此,时序要求与人类视觉系统的周期特性有关。为了让显示的频谱分析与音频信号相关联,存在延迟要求,但相当宽松。人类大脑的心理视觉系统可以容忍数十到数百毫秒的延迟,仍然能看到相关性。所以这是一个相当宽松的时序约束。

但你可能遇到延迟要求严格得多的场景。例如,在进行计算机音乐处理时,传感和驱动之间的处理延迟会产生巨大影响。人类听觉系统对大约10毫秒量级的时序现象非常敏感。如果你听到节奏模式,10毫秒的误差就非常明显。因此,当存在这种紧密反馈时,你会得到更严格的延迟约束。

这个应用的有趣之处在于,我们希望将具有严格延迟约束的任务与延迟约束不那么严格的任务结合起来。例如,频谱分析可能100毫秒的延迟是可以接受的,但10毫秒就非常紧张了。我们希望尽可能好,甚至更好。这种不匹配在嵌入式系统中很常见。问题是如何利用我们目前开发的调度理论来设计这类系统?你有一条时间关键路径和一条不那么时间关键的路径。

一种处理方法是创建两个线程,然后祈祷。但如果你在软件上做得非常仔细,也许能让它工作。那么你应该应用哪种调度理论?单调速率调度不适用,因为这些任务之间存在依赖关系。所以不能使用那个定理,必须使用其他方法。

那么,试试带优先约束的最早截止时间优先调度?但我们必须分配截止时间。而且,这类应用实际上是周期性的。所以单调速率似乎应该是正确的方法,因为它直接讨论周期性。而最早截止时间优先调度讨论的是截止时间,所以我们必须分配截止时间。我们可以随意地选择截止时间与周期结束时间对应。但这对于心理视觉方面的任务并不真正适用,因为它的要求更宽松。但如果要应用最早截止时间优先调度,我们必须选择一些截止时间,否则最早截止时间优先调度就没有意义。

在软件设计中,这是一个非常常见的问题:你最终不得不做出一些无法基于任何根本性、基础性理由做出的决定。因此,在提出一些数字时,你必须运用一些工程判断。


抽象模型与数据流概念 🔄

让我们看看这个问题的抽象版本,并做人们在使用调度理论时通常做的事情:假设不可假设的——所有任务都在已知时间内执行,或者至少有一个最坏情况执行时间。

我们将问题简化为三个任务:蓝色、绿色和红色。红色任务代表频谱分析,它对时间不那么敏感,但计算量大。A和B代表低延迟、时间关键的任务,它们将以更高的频率执行。所以频谱分析执行得较少。通常,进行频谱分析(如我刚才展示的例子)的方式是:收集大量样本,需要大量样本来进行频谱分析,然后对其运行FFT,后处理FFT数据,这需要大量计算,然后创建其视觉呈现。

假设A和B以更高的频率运行,并且有低延迟要求,所以我们希望在A执行后不久,B就能执行。基于这里的数据依赖关系,我们假设进行一个8点FFT。这意味着A每产生一个样本(我们将其视为任务A的一次调用),需要执行该任务8次才能调用红色任务。

这里的模型与同步编程模型有些不同。同步编程模型会说,这些任务在概念上是同时且瞬时执行的。但这里的情况并非如此。这里有一个作为数据源的任务,它必须执行多次,而消费数据的任务在积累了一定量的数据后才被启用。这不再是同步计算模型了。而且,它明显是非同步的,因为消费数据的任务(红色任务)可以被推迟。实际上,如果有足够的缓冲,你可以推迟它很长时间。如果它确实是非时间关键的,例如仅用于日志记录,你可能希望根据机器上的情况任意推迟它。

这明显不再是同步计算模型了。我们今天要做的是形式化一种模型,它既能捕捉这种异步性,又能保持执行中的确定性,这就是数据流计算模型。你们在实验室使用LabVIEW时已经用过这种模型的一个特例,那是一种非常专门化的数据流形式。我会谈谈这种专门化是什么。

这就是这里的场景。由于数据依赖关系,我们需要蓝色和绿色任务调用8次,红色任务调用1次。这些任务之间存在优先关系,我们可以为一个执行周期绘制优先图,这就是我在右边画的。我们可以利用这个优先图来制定调度。我可以通过执行红色和绿色任务8次,然后执行红色任务来调度它们。

这个调度看起来怎么样?它好吗?我们说的“好”是什么意思?我们希望红色和绿色之间的延迟低。我们做到了吗?是的,绿色在红色之后立即执行。我们想要蓝色和绿色的周期性执行。我们做到了吗?算是,但有相当大的抖动。如果你在进行音频处理,音频样本以固定的采样率到达。所以如果采用这样的调度,你将不得不对音频样本进行一些缓冲。如果对音频样本进行缓冲,就会引入延迟,从而增加延迟。现在,不仅仅是调度会影响延迟,抖动也会影响延迟。

尽管如此,我们可以让这个调度工作。数据流模型的一般工作方式是:在源和汇之间使用先进先出缓冲区来缓冲数据。然后,目标任务的调用取决于是否有足够的数据供其工作。因此,我们将形式化定义一个触发规则:B需要多少数据才能被启用?一旦它获得足够的数据,它就被启用,这种启用非常类似于我们在讨论最早截止时间优先调度时使用的调度意义上的启用,只有到那时你才能开始考虑调度它。

形式化地讲,连接这两个参与者的信号不再是时域信号,也不再是同步反应模型的信号。时域信号是时间的函数。同步反应信号是抽象时间概念(一系列节拍)的函数。数据流中的信号两者都不是。在这些参与者之间流动的是,它是一个序列,但其中没有时间概念。你有一个值序列,但没有时间概念。你可以将其形式化为从自然数到数据类型的函数。如果是音频样本,可能是实数。这个函数现在表示一个无界的值序列。我们不会将定义域解释为表示时间。这里的自然数不代表时间,也不代表像同步反应模型中那样的全局时钟节拍。

具体来说,如果你在数据流模型中有两个不同的信号,索引n对于这两个特定信号中的特定样本可能是相同的。但这并不意味着它们在某种程度上是同时的。所以这里没有时间概念,只有序列。这是关键。


数据流参与者与触发规则 ⚙️

我简要提到了触发规则。触发规则规定,计算的启用由输入数据的可用性决定。有几种常见的模式。

在LabVIEW中,基本上只有一种模式:所有输入都必须有一个新数据值。当该模块触发时,它会在所有输出上产生一个新数据值。我们称之为同构同步数据流。我为这里的“同步”一词道歉。实际上,这个术语是我很久以前创造的,差不多与同步语言被发明的时间相同。它并不是同步语言意义上的“同步”。它在另一种意义上同步,我稍后会解释。但关键是,同步数据流是一种异步计算模型。术语并不完美,但还可以。

同构同步数据流要求每个输入都需要一个新数据才能被释放(即可被调度)。但我们的频谱分析例子不符合这种模式,它需要八个新的数据块才能被释放。我们可以很容易地推广这一点,说非同构同步数据流模型的触发规则允许你指定需要一些整数个输入,并且在触发时将产生一些整数个输出。这是一个推广。

但你还可以更进一步。例如,这个“选择”是一个数据流参与者,它在底部输入需要一个布尔输入。这是其触发规则的一部分。如果布尔值为真,那么它需要T输入处的一个数据;如果布尔值为假,则需要F输入处的一个数据,只需要一个。所以现在实际上有两个不同的触发规则,执行将在它们之间选择。一个触发规则是:这里有一个真值令牌,这里有一个令牌,现在参与者可以触发。它所做的只是将该令牌复制到输出。第二个触发规则是:这里有一个假值令牌,这里有一个令牌,参与者将把该令牌复制到输出。

在数据流世界中,他们谈论令牌,令牌流是在参与者之间流动的数据。“开关”更简单一些,它的触发规则更简单:每个输入都需要一个令牌。然后根据输入的布尔值,它将令牌路由到T输出或F输出。

你可以使用这些参与者进行令牌的数据相关路由。顺便说一句,这些是非常底层的。在我看来,它们是数据流中的“Goto”。这种令牌的数据相关路由就像命令式程序中的Goto。如果你在构建程序时使用它们,基本上就是在用Goto编程。所以不要使用它们。不过,它们对理论有用,就像Goto一样,你可以理解数据流模型的表达能力。

例如,事实证明,如果你有一个单一的同构同步数据流参与者(与非门),并且只有布尔数据类型,你还有否定门、开关和选择,你就可以构建一个图灵机。这就足够了。这表明,这三个参与者的集合(你还需要另一件事:能够用初始令牌初始化一个流,这是打破循环的方式),用这四个原语,你可以构建一个图灵机。

我们为什么关心这个?因为你不会想用与非门、开关和选择来对音频信号进行频谱分析。我们关心的原因是,它告诉我们关于这些模型可分析性的一些基本问题。它告诉我们,我们可能感兴趣询问这些模型的一系列问题将被证明是不可判定的。我稍后会回到这一点,但请先记住这一点。我们将发现,如果你不允许这些参与者(开关/选择),而只使用这些参与者(同构/非同构同步数据流),所有这些问题都变得可判定。这在嵌入式系统中,特别是设计安全关键系统时,非常有用。


缓冲区有界性与调度 🧮

例如,在数据流模型中,我们使用先进先出缓冲区。参与者A产生一个令牌,它进入一个队列。当队列中有足够的数据满足其触发规则时,参与者B被启用。我们如何保持队列有界?事实证明,如果你用我建议的那四个原语构建数据流模型,那么能否保持队列有界是不可判定的。这意味着,给定一个数据流模型,没有算法可以分析任何数据流模型并保证其缓冲区使用有限的内存。

这就是基础理论开始重要的地方,因为我们希望能够限制缓冲区,特别是对于安全关键的嵌入式系统,缓冲区溢出是一个问题。如果缓冲区有界,如果它们无界,就需要内存分配和释放,这需要垃圾回收管理。如果你要实现一个具有无界限缓冲区的系统,那么你需要一种分配和释放的机制。这增加了系统的复杂性,如果你研究过这个问题,还会带来内存碎片化的可能性。内存碎片化对于有限的任务来说不是大问题,像大多数信息技术计算机程序,你运行它们,然后它们停止运行就结束了。但在嵌入式系统中,你通常关心的是理想情况下永远不会停止运行的系统。在这种情况下,内存碎片化可能是致命的,因为最终系统会退化到出现严重问题。

因此,在嵌入式系统中,有很多动机去限制这些缓冲区。如果你限制了缓冲区,你可以使用循环缓冲方案非常高效地实现任意长度的缓冲区,这里概述了这种方案。如果你有有界缓冲区,它可以非常高效地完成。


回到抽象例子:调度策略分析 🔍

让我们回到这个抽象的例子。我们希望限制缓冲区。我提出的这个调度限制了缓冲区吗?是的,因为A的每次调用都有B的相应调用。我们称之为平衡条件:产生的每个令牌都被消耗。红色参与者需要八个令牌。在这个调度中,每八次A的调用对应一次红色参与者的调用。所以这个连接也是平衡的。因此,这个调度的优点是保持缓冲区有界,但缺点是我们之前指出的抖动,存在这个暂停时间。如果这是一个繁重的计算,这个暂停时间可能相当长,这意味着你可能需要对传感器和执行器数据进行缓冲,从而导致延迟增加。所以我们想尝试解决这个问题。

这里有一种解决方法:改变调度,使红色和绿色实际上被周期性调用。不再需要缓冲。那么这个方法的缺点是什么?首先,你能够执行此任务的采样率将由你的非时间关键任务决定。这通常不是一个好的特性。你宁愿速率由你的时间关键任务决定,而不是由你的非时间关键任务决定。另一件事是,在计算中浪费资源在道德上是令人反感的。看看所有那些你没有使用的CPU时间。我不确定利用率是否真的是一个大问题,但这里确实存在一个问题:系统成本增加了。因为假设你必须处理特定的采样率,也许你正在进行过采样音频,需要能够处理192,000个样本/秒。你有一大块计算必须完成。现在你设计系统,必须选择一个处理器,它能够在一个采样周期内完成那部分计算。最终,你将选择一个比原则上需要的更昂贵、功耗更高的处理器,如果你能获得更好的利用率的话。

所以这不是一个很好的解决方案。让我们找一个更好的解决方案。如果我们使用多任务处理呢?我们创建两个线程。一个执行高频任务(A和B),另一个执行低频任务。我们误用单调速率定理,给这个线程高优先级,给那个线程低优先级。为什么说我误用?因为这里有数据依赖关系,所以定理不适用。但最早截止时间优先调度在某种程度上适用。给定这个调度,缓冲区会保持有界吗?你根本无法保证它们会保持有界。假设红色任务运行时间很长。红色任务的缓冲区将不断增长。实际上,当模型中存在循环时,这通常是数据流模型的一种可能性。这个系统没有反馈。所以没有什么能阻止A不断产生不被消耗的令牌。

我们可以通过加入一个互锁来修复这个问题。使用一个信号量,我们将阻止蓝色任务的第九次执行开始,直到红色任务的执行完成。这将防止那个缓冲区溢出。这足以防止缓冲区溢出吗?为什么A和B之间的缓冲区不会溢出?在我们生成的调度中,A和B的执行是交错进行的,因为我们把它们放在了同一个线程中,并且我们只是执行一个无限循环:触发A,然后触发B,然后触发A,然后触发B。所以平衡得到了保证。我们通过静态调度该连接所涉及的参与者,保证了该连接上的缓冲区有界。我们使用互锁保证了蓝色和红色之间连接上的缓冲区有界。

是的,在这个方案中你仍然会有抖动。所以现在你需要关心最坏情况执行时间。并且你需要能够验证系统是否能够跟上。这可能会是一个问题。你还有其他选择。一是你可以将其视为故障。如果你的红色任务运行时间比预期长,在安全关键系统中,这实际上可能是一个合理的做法,特别是如果红色任务是非关键任务,比如在进行一些日志记录,你可以将其视为故障,中止该任务。你可能切换到一种降级模式,停止记录日志等。

还有一种“任意时间计算”的概念可能有用。如果你正在进行数据分析,作为一种后台进程,一个结构良好的数据分析算法即使只部分完成,也可能给出有用的结果。例如,有一些频谱分析技术是增量式的,而不是预先决定进行1024点FFT。我先做一个16点FFT,然后用这些结果创建一个32点FFT,再用这些结果创建一个64点FFT。然后当我时间用完时,我就取我得到的FFT结果用于视觉呈现。这被称为任意时间计算,这是一种强制执行最坏情况执行时间的好方法,你只需定义这就是执行时间界限,然后我就取目前得到的结果,并在那时中止计算。

现在,我们这里需要另一个互锁来遵守数据依赖。因为线程2也在一个无限循环中,它只是反复调用红色参与者。但有什么能阻止它过早开始呢?如果它只是反复调用红色参与者,什么也没有。所以你必须在其中加入一些东西,确保红色参与者的第二次执行在其触发规则满足之前不会开始。它需要八个新数据。我认为这些被称为“data”。“data”是“datum”的复数。它需要八个新令牌,这就是为什么数据流中的人使用“令牌”这个词而不是“数据”,因为很多人认为“data”是单数,而不是复数。这让人困惑。

所以红色需要八个新令牌,我们需要另一个互锁来确保它在获得八个新令牌之前不会开始执行。有了这对互锁,我们可以得到确定性的执行。注意,如果蓝色或绿色任务中的一个运行时间很长,一切都没问题。我们不需要另一个互锁来保护这一点。为什么?幻灯片上写着:这个线程具有更高优先级的事实意味着……实际上,我甚至不认为优先级重要。如果我反转优先级,这还能工作吗?这是一个很好的问题。如果我给红色任务比蓝色和绿色更高的优先级,并且有这些互锁,时序约束可能无法满足,但数据流可能是正确的。这是真的吗?这将是期中考试一个非常好的真假判断题。给你一个在特定优先级下工作的调度,问你如果反转优先级,它是否仍然表现正确?在这个例子中,线程1中发生的静态调度(它给出了A和B的固定交错)保证了A和B之间连接上的数据流要求得到满足,无论任何任务执行多长时间。两个互锁保证了从A到C的数据流要求也得到满足。所以数据优先关系将得到满足,即使你反转优先级。唯一受影响的是时序,它会恶化。实际上,它会恶化,因为如果你反转优先级,红色任务将执行完成而不会被抢占,我们又回到了之前的高抖动调度。


更复杂的数据流模型与调度 🌀

假设你有一个稍微复杂一点的数据流模型。顺便说一下,如果你用过Simulink,不同速率运行的块的表示法是,你为它们指定一个采样时间。你应该将其理解为处理单个样本所分配的时间量。但它并不是真正的时间。所以这个名字有点误导。但无论如何,这意味着B的执行频率将是A的四分之一。在Simulink表示法中,大概消耗A产生的四个令牌。

在这种情况下,如果我在单个处理器上提出一个顺序调度,它看起来会是这样:我需要在能够调用B之前调用A四次。一旦我调用B,假设它在输出上产生四个令牌,那么我可以调用C四次。我得到这样一个调度。现在,如果我关心延迟和抖动,这是一个相当不吸引人的调度。那么有没有办法解决呢?试试多线程怎么样?

这是一个替代调度:将红色放在低优先级任务中,将绿色和蓝色放在高优先级任务中,并设置互锁。一旦蓝色执行了四次,红色就被启用。为什么有一个互锁阻止绿色?绿色需要来自红色的四个数据令牌。所以它将在此时被触发。

现在,在延迟方面问你一个问题。我们可以将延迟定义为A产生一个令牌与C消费依赖于该令牌的某个东西之间的时间。如果A是一个传感器,它进行一次读数。你必须等待多长时间,该读数才能对执行器产生影响?这就是延迟。那么这个调度的延迟是多少?不是很好。传感器读数在这里获取,它可能产生的第一个影响在这里。第一个读数在这里,它产生的影响在这里。实际上,在实践中,这两个延迟最终会是相同的,因为为什么这些东西间隔开,而这些没有?传感器触发四次然后在一段时间内不再触发的唯一方式是,如果有一些缓冲正在进行,这是以规则采样率进行传感。实际上必须有一些缓冲。所以,实际上,通过转向多线程调度,你并没有真正在延迟方面得到任何改善。而且你增加了大量开销,因为有上下文切换。


数据流模型的历史与同步数据流 📜

首先,数据流模型的研究历史很长,时间上有点断断续续。实际上,这个列表没有更新到现在,但之后还有很多其他工作。最早可以追溯到1966年我们自己的Dick Karp所做的一些工作。我们前系主任David Culler的博士论文就是关于数据流模型的。我的博士论文也是关于数据流模型的,实际上我创造了“同步数据流”这个术语,造成了巨大的混淆,因为差不多在同一时间,Lustre编程语言出现了,并被Paul Caspi描述为一种同步数据流语言。Paul在他关于Lustre的第二篇论文中,引用了我的论文,但没有读过。他只是因为标题中有“同步数据流”就假设我谈论的是和他一样的计算模型。但我不是,它们是两种完全不同的计算模型。所以引用别人的作品时要小心,最好真的读一下论文。

但无论如何,因为这是我最喜欢的话题之一,我要告诉你们这个同步数据流模型,以及一些更具表达力的模型。这个特定模型在表达能力上不是很强,因为它不能描述图灵完备的计算,它不是图灵完备的。但有一类事情它做得非常好,对于这些事情,完全分析模型的能力是一个非常强大的工具。所以这里有一个权衡,就像我们为什么使用有限状态机一样的论点:有限状态机也不是图灵完备的?但我们仍然使用有限状态机,因为我们可以详尽搜索它们的行为以发现异常行为,这就是Spin所做的。Spin查看所有可能的行为,然后给出关于这台机器可能具有的整个行为家族的明确答案。对于图灵完备的计算模型,你通常不能这样做。

所以同步数据流是另一个更受限的模型,就像有限状态机一样,但它与有限状态机非常不同,具有非常不同的特性,它是关于流处理的。同步数据流的关键约束是,每个参与者的触发规则都非常简单:它们需要在输入上有固定整数个令牌才能被启用。当它们触发时,它们将在输出上产生固定整数个令牌。

如果你有两个参与者之间的连接,你可以写下一个非常简单的方程,将这两个参与者的执行关联起来,以保证一切保持平衡。这就是这个特定连接的平衡方程:如果你触发参与者A Q_A次,它在连接C上产生P_C个令牌,那么这应该等于你触发参与者B的次数乘以它消耗的令牌数。非常直接,这就是保持平衡的方式,你产生的所有令牌都应该被消耗。

但这有点奇怪,对吧?这个模型本质上永远不会终止。A可以不断产生令牌,B可以不断消耗令牌。那么Q_A和Q_B不都是无限的吗?是的,它们可能是。理论上,它们仍然会平衡,无穷等于无穷,在某种意义上是这样。至少这两个无穷大会相等。但这不是一个有用的结论,所以我们希望进行更有限的分析。


平衡方程与矩阵表示 🧮

让我们看一个稍微不那么简单的例子。这是一个由三个参与者组成的数据流模型。这个参与者有两个输出端口,触发时在这里产生一个令牌,在这里产生两个令牌。这个参与者消耗一个并在这里产生两个。这个参与者消耗一个和一个。

那么,这能保持平衡吗?也许是的,这取决于你说的“明显”是什么意思。你必须注意到,参与者3需要执行这些参与者的两倍频率,而这两个需要执行相同的次数才能保持平衡。这些连接的平衡方程会告诉你这一点。

平衡方程可以非常紧凑地描述为一个非常简单的矩阵方程。如果你学过图论,这个方程看起来很像描述图的邻接矩阵。但它不仅仅是描述邻接,还告诉我产生了多少令牌和消耗了多少令牌。构造这样一个矩阵的方法是:为图中的每个连接创建一行。这是连接1,连接参与者1和参与者2。然后为每个参与者创建一列。在矩阵中输入该参与者在该连接上产生或消耗的令牌数。参与者1在连接1上产生一个令牌。参与者2(第二列)从连接1消耗一个令牌,所以你放一个负数。参与者3根本不参与连接1,所以它在该连接上不产生也不消耗任何东西,你放一个零。你可以构造这样一个矩阵。然后构造一个向量,它是每个参与者的调用次数。Q1是参与者1执行的次数,Q2是参与者2执行的次数。然后平衡简单地要求Γ乘以q的矩阵乘积等于0。

这是一个非常紧凑的平衡约束表示。对于这个特定的例子,平衡方程看起来像这样。注意,这个矩阵不一定是方阵,因为连接的数量不一定等于参与者的数量。每个连接一行,每个参与者一列。你可以看到这个方程……我的意思是,这个矩阵代表了这些参与者的生产消费模式,并适当标记了列和行。

回到这个稍微有趣的例子。我们有了这个矩阵方程,一个关键问题出现了:我们是否有任何保证存在解?这是第一个问题。你能找到一个向量Q,使得Γ乘以Q等于0吗?对于这个具体的,你已经给出了解,所以一般来说……是的,如果它是满秩的……嗯,实际上不完全是。总是有一个解。总是有效的解是什么?零。通过根本不触发任何参与者,你总是可以保持数据流模型平衡。但这可能不是你想要的。所以你感兴趣的是找到一个非零解。非零解的存在将取决于这个矩阵的秩。

我们不是在寻找任何解……我们实际上在寻找什么?我们感兴趣的是整数解。如果解说你应该触发参与者1 π次,参与者2 π次,参与者3 2π次,这不是很有用,因为你不能触发一个参与者π次。你也不感兴趣解说类似触发参与者1一次,参与者2负一次,参与者3两次,因为你不能触发一个参与者负一次,就像你必须“取消触发”参与者。所以我们实际上感兴趣的是平衡方程的正整数解。

有理数解就足够了,因为如果你能找到有理数解,你可以找到分母的最小公倍数,然后找到一个整数解。在这个特定例子中,平衡方程有许多解,这个零解有效,非整数触发有效。但在这些解中,只有这两个是有趣的。而这个比这个更有趣,因为它是最小的正整数解。

如果我们对解决调度问题感兴趣,直观上,处理较少的触发次数应该比处理较多的触发次数更容易。但这并不那么明显,因为事实证明,如果你处理更多的触发次数,你在调度方面有更大的灵活性,可以提出更好的调度。但无论如何。

关键结果是:如果这个矩阵不是满秩的,那么平衡方程总是存在一个正整数解,并且总是存在一个最小的正整数解。而且,有一个非常简单的线性时间过程可以找到那个解。这是一个关键结果,实际上很容易看出为什么这个结果成立,因为产生和消耗的令牌数总是整数,而且总是正整数。因此,如果我任意设Q_A为1,那么我可以得到Q_B的有理数解。现在我的向量元素中,我有了一些有理数值。如果图是连通的,那么参与者A或参与者B也将连接到另一个参与者。因此,我可以找到那个参与者的触发次数的有理数解。一旦我有了那个有理数解,我看看它连接了什么,我可以找到那个参与者的有理数解,我就这样遍历我的模型,直到我得到所有参与者的有理数解。现在我有了一个正有理数解。现在我可以遍历并找到所有分母的最小公倍数,然后乘以那个最小公倍数……我感兴趣的是什么,最大公约数?取决于你看的是分子还是分母,是乘还是除。但无论如何,你可以找到一个有理数解,如果存在的话。但如果矩阵是满秩的,那么唯一的解就是零解。这如何体现?当你尝试这样做时,它会简单地体现为:你将不得不有一个循环。所以当你连接到下一个参与者时,你发现,哦,我已经有了那个参与者的解,猜猜看,这个新的平衡方程不满足那个解。发现不平衡也是在线性时间内完成的。

术语:如果平衡方程存在非零解,则称模型是一致的。这是一个不一致模型的例子。它与前一个例子只有非常细微的差别:参与者2只产生一个令牌,而不是两个。这是生产消费矩阵的样子。现在平衡方程的唯一解是不触发任何参与者。你可以直观地看到这一点:如果我触发参与者1一次,它将在这里产生两个令牌,在这里产生一个令牌。那时,我可以触发参与者2一次,它将在这里产生一个令牌。所以现在我这里有一个令牌,这里有两个。这个参与者被启用一次触发。它将消耗这里的一个和这里两个中的一个。但现在我有一个剩余的令牌,没有办法消耗它。如果我重复这个过程,最终这个连接上会发生缓冲区溢出。我没有给你证明,但我给了你一个证明草图。你应该读我的博士论文,里面有证明。

你也可以确定,如果秩不是满的,就存在一个解,存在一个最小的正整数解。实际上,证明方法是:我也给你那个证明的草图。如果矩阵不是满秩,那么模型上的一个生成树具有与完整模型完全相同的信息。因为不满秩意味着存在线性相关的行或列,所以那些线性相关的行或列是冗余的。你可以找到一个生成树,然后证明由于矩阵不满秩,生成树具有与原始模型相同的解集。对于树,它总是有效的,树总是有效的……这就是证明的草图。

在这个例子中,没有非平凡解。好处是你可以分析这个模型并拒绝它,认为它是一个错误的模型,并说:我无法用有限内存执行这个东西。注意,能够对程序做出这样的陈述(这是一个程序)是非平凡的。对于图灵完备语言中的任何程序,你通常不能做出这样的陈述。或者更准确地说,没有算法能够回答图灵完备语言中给定程序是否能在有限内存中执行的问题。但对于同步数据流图,有一个算法可以回答它是否能在有限内存中执行的问题。顺便说一下,这证明这不是一个图灵完备的计算模型,因为有界性问题是可以判定的。

所以,一致性是拥有有界内存、无限执行的必要条件。你可以用有限的内存永远执行模型。这也是充分条件吗?换句话说,如果我知道我有一个一致的模型,我是否知道存在一个有界内存的无限执行?你怎么看?如果我知道瓶子是一致的,我能得出结论存在有界内存的无限执行吗?不,它不依赖于时间。有界内存无限执行的存在只与……任务执行多长时间无关。我为你概述的定理说:如果模型不一致,就不存在有界内存的无限执行。但我没有说如果模型一致,是否存在有界内存。问题是,存在吗?我看到一个,你想解释为什么不存在?你可能会有死锁。考虑这个模型。平衡方程有一个解。你可以执行两个参与者各一次。但你无法执行两个参与者各一次,因为最初它们的触发条件都不满足。所以没有无限有界内存执行,实际上根本没有执行。这是一个有界执行,不是无限执行,它执行所有参与者恰好零次。这是一个死锁。

顺便说一下,我想指出,在数据流模型中,死锁被认为是邪恶的。在Carpen和Miller 1966年的第一篇论文中,他们恰恰相反。在经典计算理论中,无法停止是邪恶的。在那篇论文中,他们明确只对死锁的模型感兴趣,那些是唯一有趣的,因为它们指定了一个停止的计算。在图灵-丘奇计算理论中,所有无法停止的计算都是等价的,而且是有缺陷的。这是一个有趣的转变。但在数据流模型的情况下,我们实际上……停止是一种邪恶的属性,所以当停止是邪恶的,我们称之为死锁;当它是期望的,我们称之为停止。但这是相同的现象。所以这个模型立即停止……我的意思是,立即死锁。

现在,死锁可能更难确定。在这里,你可以一眼看出这将死锁。假设你有一些初始令牌可用。这会很快死锁吗?一眼看去,你无法判断,你必须运行一下执行。当然,随着模型变大,回答是否死锁的问题可能变得更加困难。但幸运的是,同步数据流模型是否死锁的问题也是可以判定的。我想我有时间解释一下。


调度构造与有界性证明 🛠️

确定模型是否具有有界无限执行的一种方法是:首先检查模型是否一致。如果不一致,那么就没有有界无限执行,完成。如果一致,那么你仍然需要担心它是否会死锁。确定是否死锁的一种方法是构造调度,一个可以无限次执行的调度。但你怎么知道构造调度是一个有界过程?这是我们必须能够确定的关键。

以下是构造调度的方法,同时证明构造调度的过程是有界过程,它会终止。所以它属于图灵-丘奇那类有用的程序,因为它终止,而不是死锁。它是如何工作的?我们将通过跟踪缓冲区中有多少令牌来稍微扩充模型。我们引入一个新的向量B,它为每个缓冲区设置一个元素,简单地告诉我该缓冲区中有多少令牌。

在这个特定模型中,我们开始时所有缓冲区中都没有令牌,所以B是0,0,0。我们已经解出了平衡方程,并发现1,1,2是平衡方程的一个解,这意味着参与者1应触发一次,参与者2应触发一次,参与者3应触发两次。有趣的是,当我们触发时会发生什么?如果我们触发参与者1,我们就用掉了一次参与者1的触发。所以我们可以递减向量Q,说:好了,我们已经处理了那次触发。并且我们对缓冲区产生了影响:我们放了一个令牌到缓冲区1,两个令牌到缓冲区3。顺便说一下,从这里到这里的操作,只是将生产消费矩阵乘以一个告诉我触发了哪个参与者的向量。我稍后会展示那个操作,但直观上你可以看到我们可以跟踪缓冲区。

在那一刻,我们可以触发参与者2。顺便说一下,我们做的一件事是:现在触发下一个参与者的可能性有哪些?在数据流理论中,当我的缓冲区是这样时,被启用的参与者是参与者1和参与者2,两者都被启用。但我不会考虑触发参与者1,因为我剩下的触发次数是零,这正是使过程有界的原因。我将排除触发参与者1,因为我已经完成了参与者1所需的所有触发。所以我唯一的选择是触发参与者2。我可以再次更新缓冲区。然后我递减触发向量,这样我就不再需要对参与者2进行任何更多触发。我仍然需要对参与者3进行两次触发,但幸运的是,这两次触发现在都被启用了。我可以将我的缓冲区带回到初始状态,满足了我的平衡方程解中所需的所有触发。这个过程保证会终止,因为这个向量是一个有界的正整数向量。

在数据流模型中,你通常有很多选择。在这个特定例子中,每一步我只有一个选择。但通常你有很多选择,让我给你看一个关于这个非常有趣的例子。就是这个。这是我的一个学生在他的博士论文中研究的一个例子,这是Shuvra S. Bhattacharyya,他现在是马里兰大学的教授。他考虑的问题是将音频信号从光盘格式转换为数字音频磁带。

数字音频磁带,我敢肯定你们对它一无所知,因为它昙花一现,没有持续很久。但它是索尼推出的一项技术,旨在取代盒式磁带。但它失败了。但在开发这项数字音频磁带技术的过程中,索尼决定数字音频磁带录音的采样率应该与光盘的采样率不同。在光盘上,采样率是每秒44,100个样本。所以他们决定在数字音频磁带上使用每秒48,000个样本。为什么?是的,营销论点是更高的采样率更好。所以我们做更高的采样率。这不是真正的原因。真正的原因是,是的,他们想让将CD完美复制到数字音频磁带变得非常困难。你知道,这是在每个人都有CD刻录机之前。CD刻录机在那时是昂贵的机器。所以他们想让它难以将CD完美地数字复制到数字音频磁带上。所以他们选择了一个极其尴尬的采样率比例。

事实证明,你可以通过先进行二比一上采样,然后进行二比三下采样,然后进行八比七下采样,然后进行五比七上采样,将CD转换为数字音频磁带。这是一个数据流参与者,消耗七个令牌并产生五个。这是一个数据流参与者,消耗七个令牌并产生八个。这些数字……数字由于某种原因是反的。平衡方程的解显示在参与者下方。这是这个特定模型的平衡方程的最小整数解。你执行这个参与者147次,这个参与者147次,因为这个产生一个,这个消耗一个。这个产生两个,这个消耗三个,所以这些触发之间存在二比三的比例,所以这个执行98次,这个执行28次,这个执行32次,这个执行160次,这将使你从44.1千赫兹到48千赫兹的采样率。

总之,Shuvra研究了所有你可能为这个模型构造的调度,针对不同的标准。这是最小化缓冲区大小的调度。这里没有模式,这就像混沌。调度很难用编码算法编码,因为它里面没有重复的模式,至少没有重复很长时间的。所以,是的,在调度方面你有很多选择,根据你试图满足的标准(在这个例子中,他专注于最小化缓冲区大小),你可能在构造这些调度时有一个非常有趣的优化问题需要解决。是的,它们都给出相同的数据流计算。所有调度最终都会产生相同的结果。但它们将使用不同数量的资源。我的意思是,他还研究了调度编码大小也有内存成本的问题。所以我们也要考虑这一点,以及缓冲区大小。

关于这个有一本书,由Shuvra领导数据流爱好者们编写,这被称为“绿皮书”,因为它是绿色的。它表明你可以得到非常大的效果。在各种优化不同目标的调度中,用于数据缓冲的内存量可以从1000的高点到32的低点。对于这个相当小的模型

22:时间触发架构 🕒

在本节课中,我们将要学习时间触发架构。这是一种在工业界应用日益广泛的架构,其核心思想是利用同步模型来构建分布式系统。我们将探讨其基本原理、实现方式以及与事件驱动模型的对比。

概述

时间触发架构是一种用于设计安全关键系统的平台。其核心功能是通过时分多址方案来管理通信。这种架构旨在解决分布式系统中信息传递的准确性和时效性问题,特别是在汽车、航空等安全关键领域。

上一节我们介绍了同步模型在软件和电路设计中的应用,本节中我们来看看如何将这一概念扩展到分布式系统的架构设计中。

现代系统的挑战

如果你打开一辆现代汽车,你会发现超过80个处理器相互连接,执行不同的任务。这些系统包括信息系统、传感器网络、车身电子设备以及安全关键的控制模块。

以下是当前汽车架构面临的主要问题:

  • 复杂的连接:不同功能的组件通过不同的线缆和协议连接,导致系统杂乱无章。
  • 冗长的线束:一辆大型汽车中可能有多达10公里的线缆,增加了重量和复杂性。
  • 集成的困难:汽车制造商负责系统集成,而一级供应商制造各个模块,二级供应商提供半导体。这种复杂的供应链使得保证整个系统正确工作变得非常困难。

类似的架构也出现在造纸机、轧机等工业系统中,它们同样是包含传感器、计算节点和执行器的分布式系统。

同步范式的目标

在异步模型中,传感器采集数据后,通信、计算和执行可能随时发生并产生冲突。同步范式的目标是将计算和通信安排在不重叠的阶段。

一个理想的工作周期如下:

  1. 传感器采样阶段:所有传感器向系统报告状态。
  2. 通信分发阶段:传感器数据被分发给需要的计算节点。
  3. 计算阶段:各个节点进行计算。
  4. 执行器通信阶段:计算结果被发送给执行器。

关键问题在于:我们如何在由时钟和寄存器实现的同步电路设计技巧基础上,在物理分布式系统中实现这种机制?

什么是时间触发架构?

时间触发架构是一个用于安全关键系统设计的平台。其核心通信机制是时分多址

TDMA(时分多址) 意味着在一个循环调度周期中,每个节点都拥有自己专属的时间槽。在某个时间槽内,只有对应的节点可以进行操作(如发送数据),从而避免了冲突。

这种通信方式也广泛应用于今天的蜂窝网络。对于安全关键系统,网络需要满足极高的可靠性要求,例如故障率低于每小时10⁻¹⁰,最大中断时间少于10毫秒。

拓扑结构与协议

TT协议是构建在物理层(如总线)之上的一个通信层。其实物拓扑结构可以多样化:

  • 单通道拓扑:所有节点连接到同一总线上。
  • 级联星型拓扑:智能传感器连接到网关,网关之间再进行通信。
  • 混合星型总线拓扑:结合了星型和总线结构。

无论物理拓扑如何,只要通信协议是TDMA,就能实现时间触发的特性。

核心机制:接口与时间同步

在分布式系统中实现类似电路寄存器“写入-锁存-读出”的机制,关键在于节点与总线的接口。

全局时间同步 是其中最核心的概念。所有节点必须对“当前时间”有一致的认知,否则整个调度就会混乱。这就好比芯片设计中的时钟同步问题,但在长达数百米的飞机或汽车中,这个问题要复杂得多。

实现时钟同步的一种常见方法是:一个中央主节点收集所有节点的本地时间,计算出一个平均值作为“全局时间”,然后通知所有节点与之对齐。

时间触发架构将物理时间的处理视为首要任务,它提供了容错的、基于全局时间的同步服务,并利用全局时间来规定节点访问总线的接口时刻。

节点结构与数据流

一个时间触发网络节点由两部分组成:

  1. 主机子系统:负责计算、测量或执行操作。
  2. 通信子系统:负责控制数据从节点到网络的传输。

通信子系统包括通信控制器和通信接口。其工作方式类似于双缓冲:

  • 主机将计算结果写入一个发送缓冲区
  • 在由全局时间确定的专属时间槽到来时,通信控制器将缓冲区数据打包并发送到网络。
  • 接收节点根据消息中的地址信息判断是否读取该数据。

实时数据与时间槽设计

实时数据具有时效性。在TDMA调度中,必须确保调度周期足够短,使得节点处理的数据不是过时的“旧快照”。

设计一个良好的TTP网络,关键在于决定两个参数:

  • 时间槽的长度:每个节点发送数据的时间窗口。
  • 整个TDMA循环周期的长度

这是一个重要的设计优化问题,需要在数据新鲜度和系统响应能力之间取得平衡。

容错与成员关系

系统必须能够检测和处理节点故障。TTP架构主要考虑两种故障模型:

  • 沉默故障:节点故障后停止发送任何信息。监控者发现某个时间槽为空,即可判定该节点故障。
  • 唠叨故障:节点故障后持续发送信息,甚至占用其他节点的时间槽。监控者发现数据溢出指定时间槽,即可判定故障。

一旦检测到故障,系统可以通过发送重新配置消息,将故障节点的功能分配给其他节点,从而实现故障容错。节点的“成员关系”状态(是否在正常通信)被持续监控。

设计流程:自上而下与自下而上

时间触发架构支持可组合的设计流程,这与基于平台的设计原则一致。

  1. 架构设计阶段(自上而下)
    • 将系统划分为近乎自治的组件。
    • 在高层指定功能性和及时的信息流需求。
    • 设计通信控制器的消息描述列表,确定通信激活的时间点。
    • 最终结果是跨越TDMA时间槽的时间防火墙规范。

  1. 组件设计阶段(自下而上)
    • 组件设计师根据架构阶段给出的约束(如计算必须在指定时间槽内完成)进行设计。
    • 如果组件无法在给定时间槽内完成计算,则需要迭代修改设计(如调整时间槽长度或优化组件)。

事件触发 vs. 时间触发

以下是两种通信模式的对比:

特性 事件触发通信 时间触发通信
消息类型 事件消息(含事件信息,需队列缓存) 状态消息(系统在某个时刻的快照)
时序控制 由事件发生驱动,不可预测 由时间推进驱动,可预测
灵活性 高,易于即插即用 低,需要预先规划
确定性 非确定性(可能丢包、覆盖) 确定性
时间抖动 小(最大为时间槽长度)
接口规范 无精确时间规范 有精确的时间规范
故障检测 困难 容易(通过时间槽状态)
适用场景 稀疏、不规则的数据流 规律、周期性的数据流

未来方向:时间触发以太网

以太网成本低廉、性能极高且技术成熟,但其协议是异步的,具有非确定性。未来的方向是结合两者的优点。

时间触发以太网 的构想是:将以太网作为底层物理媒介,在其之上构建时间触发协议层。这样既能利用以太网的经济性和高性能,又能获得时间触发架构的确定性和可靠性。

TT以太网可以支持多种流量等级:

  • 时间触发流量:安全关键,严格按调度传输。
  • 速率约束流量:保证最小间隔的通信。
  • 尽力而为流量:标准以太网异步通信。

这种混合临界系统允许关键和非关键消息在同一基础设施上共存,是汽车、航空等领域未来的发展趋势。

总结

本节课中我们一起学习了时间触发架构。它是一种在分布式系统中实现同步模型的工程方法,核心是通过全局时间同步TDMA调度来保证通信的确定性和时序性。我们探讨了其节点结构、数据流、容错机制和设计流程,并将其与事件触发模型进行了对比。最后,我们展望了结合以太网优势的时间触发以太网,这代表了未来安全关键分布式系统的发展方向。实现同步机制需要处理通信、接口、时间同步等诸多复杂问题,但由此带来的确定性和可验证性,使其成为高可靠性系统不可或缺的基石。

23:网络技术

在本节课中,我们将学习嵌入式系统中的网络技术。我们将从有线网络开始,探讨其确定性访问机制,然后介绍无线网络中的各种协议及其应用场景,最后深入了解实现精确时钟同步的关键技术。课程内容旨在为初学者提供一个清晰、全面的网络技术概览。


有线网络与确定性访问

上一节我们介绍了嵌入式系统的基本概念,本节中我们来看看网络通信的基础——有线网络。与通用网络不同,嵌入式系统常使用设计上更具确定性的网络技术,以确保在资源争用时的可预测行为。

例如,CAN总线是一种较老但成熟的技术。其关键思想是使用共享总线,并采用一种独特的电气接口:设备只能将总线电压拉低,而不能拉高。当一个设备向总线写入数据时,它会立即读回。如果写入和读回的数据不一致,则表明发生了冲突。这种设计的优点是能立即检测到冲突,但缺点是功耗较高且速度受限,因为信号被视为在整个线路上同时生效,限制了比特率。

相比之下,以太网采用CSMA/CD(载波侦听多路访问/冲突检测)机制。设备在发送前侦听线路,空闲时开始传输整个数据包,传输过程中不持续侦听冲突。如果未收到确认,则重新发送。这种方式允许信号在线路上传播,从而获得更高的比特率,但牺牲了协调性,可能导致数据包在远端发生碰撞而发送方不知情。

另一种重要的访问机制是时分多址(TDMA),即每个设备有预定义的时隙进行传输。这要求设备间时钟高度同步。如果到不同设备的路径长度不同,就会产生时钟偏移,因此必须在时隙间设置保护带,这会导致网络资源利用率降低。

以下是几种网络访问机制的对比:

  • CAN总线:立即冲突检测,确定性高,但速度慢、功耗高。
  • 以太网 (CSMA/CD):高带宽,但存在非确定性延迟和冲突风险。
  • TDMA:确定性时隙分配,需要时钟同步,存在保护带开销。

时间触发以太网与服务质量

上一节我们讨论了基本的网络访问机制,本节中我们来看看如何将以太网改造得更具确定性,以满足安全关键系统的需求,这就是时间触发以太网(TTEthernet)。

TTEthernet的核心思想是结合以太网和精确的时钟同步。所有连接到同一以太网总线的设备共享紧密同步的时钟,从而可以预先分配传输时隙,避免物理介质上的冲突,甚至能避免交换机中的缓冲区溢出。

TTEthernet的调度表结合了多种访问方案:

  1. 时间触发窗口:预分配给特定设备,用于确定性通信。
  2. 速率约束窗口:任何设备都可传输,但有严格的比特率上限,用于非预期但需保证一定服务质量的数据。
  3. 尽力而为窗口:采用标准TCP/IP方式,无速率约束,服务质量最低。

设置此类网络需要所有设备遵守调度。存在“喋喋不休的故障”风险,即故障设备不遵守调度。因此,一些硬件解决方案会集成保护机制,防止故障设备在错误时间访问网络。这种技术正被考虑用于航空等安全关键领域,以减少线缆重量并提高可靠性。


精确时钟同步协议

网络中的许多高级功能,如确定性调度和协同工作,都依赖于精确的时钟同步。本节我们将深入探讨实现这一目标的关键技术:精确时间协议(PTP),也称为IEEE 1588。

PTP协议旨在通过网络同步两个设备的时钟,精度可达纳秒甚至皮秒级。其工作原理基于一个简单而巧妙的想法:测量主从设备间的消息往返时间,并假设网络路径是对称的。

以下是PTP时钟同步的基本步骤:

  1. 主设备在时间 T1(根据其本地时钟)发送一个同步消息给从设备,并将 T1 值包含在消息中。
  2. 从设备在时间 T2(实际时间)收到消息,但其本地时钟记录的时间是 T2 + E,其中 E 是从设备时钟的误差。
  3. 从设备在时间 T3(实际时间)发送一个延迟请求消息回主设备,其本地记录为 T3 + E
  4. 主设备在时间 T4(根据其本地时钟)收到该请求,并将 T4 发送回从设备。

此时,从设备拥有四个值:T1, T2+E, T3+E, T4。从设备可以计算往返延迟 R = (T4 - T1) - ((T3+E) - (T2+E)) = T4 - T1 - T3 + T2。假设路径对称,则单向延迟为 R/2。由此,从设备可以推算出实际接收时间 T2 = T1 + R/2,进而计算出自身的时钟误差 E = (T2+E) - T2,并据此校正本地时钟。

协议的性能高度依赖于连接的对称性。软件实现因中断处理等不对称性,精度比硬件辅助实现差数个数量级。像“白兔”项目这样的高端应用,通过使用同一光纤传输双向信号、温度控制芯片等手段确保极致对称,实现了皮秒级同步。


无线网络协议概览

前面我们主要关注有线网络,本节我们将视野转向无线领域。无线网络协议种类繁多,根据覆盖范围大致可分为个域网、局域网和广域网。

以下是几种常见无线协议的简要介绍:

  • SigFox:一种广域网技术,专为物联网设计。其特点是极低比特率(约100bps)、超长距离和极低功耗,目标是为设备提供每年约1美元的网络接入服务。
  • 蓝牙:最初设计用于短距离替代线缆(如连接打印机)。现在也广泛应用于信标技术,用于近距离感知和设备发现。
  • Zigbee:基于IEEE 802.15.4标准,常用于构建低功耗、低数据速率的无线网状网络。它集成了时间同步网格协议(TSMP),允许设备同步唤醒和睡眠以节省电量。
  • Wi-Fi:最常见的无线局域网技术,提供高带宽,但功耗相对较高。

对于资源受限的设备,还有更高层的协议,如受限应用协议(CoAP)。CoAP充当网关,在本地为设备分配16位短地址,并将其映射到IPv6地址,从而减轻了终端设备处理长地址的开销和能耗。


总结

本节课中我们一起学习了嵌入式系统中的网络技术。我们从有线网络的确定性访问机制(如CAN、以太网CSMA/CD、TDMA)开始,探讨了如何通过时间触发以太网(TTEthernet)在以太网上实现确定性服务。接着,我们深入了解了实现高性能网络基石——精确时钟同步协议(PTP/IEEE 1588)的工作原理。最后,我们概览了从个域网到广域网的各种无线协议(如SigFox、蓝牙、Zigbee、Wi-Fi)及其适用场景,并提到了为低功耗设备设计的CoAP协议。理解这些网络技术的特性和权衡,对于设计和实现可靠、高效的嵌入式系统至关重要。

25:期中复习与课程展望 📚

在本节课中,我们将回顾课程的核心内容,并探讨嵌入式系统与信息物理系统领域的关键挑战与未来方向。我们将重点关注确定性模型的重要性,以及当前技术如何应对系统中的不确定性。

课程公告与项目安排 📢

以下是关于课程项目与期末安排的重要通知。

  • 今天是订购项目所需电子元件的最后一天。请务必在今天联系你的助教。部分供应商的元件可能无法及时送达。
  • 期中考试将于本周四在本教室进行。
  • 项目展示将于两周后的明天开始。我们将从早上8点开始使用本教室。我们鼓励所有同学前来观摩,届时将提供午餐披萨作为激励。
  • 每个团队的展示时间为10分钟,之后会有简短的问答环节。如果你的项目演示不便搬运,请告知助教,以便安排在下午3点之后进行。

无线网络技术补充 📡

上一节我们介绍了无线网络的基础分类。本节中,我们来看看两种值得关注的新兴无线网络技术。

无线网络主要分为三类:个人局域网(短距离)、局域网(中距离)和广域网(如手机网络)。其中,专为物联网设计的SGFox等技术成本极低,旨在实现泛在连接。

网状网络与时钟同步

当前广泛部署的是星型网络,每个设备单独与接入点通信。而网状网络允许设备相互通信并为彼此中继消息,这能显著降低平均通信距离,从而节省能耗。但作为中继节点的设备需要持续监听,这会快速耗尽电池。

解决方案是采用分时隙网络,节点仅在特定时间窗口唤醒、通信,然后休眠。这需要节点间的时钟同步。与有线网络中的IEEE 1588协议不同,无线网络(如802.15.4E标准中的WirelessHART协议)使用更简单但足够精确(毫秒级)的同步协议,使无线传感器节点能够依靠纽扣电池运行多年。

受限应用协议

物联网设备数量庞大,32位的IPv4地址空间不足,而128位的IPv6协议栈能耗又太高。CoAP协议在局域网内使用16位地址,并通过网关与全功能互联网接口进行转换,从而为低能耗设备提供了可行的网络接入方案。

Wi-Fi与RESTful接口

Wi-Fi需要更大的天线和更多能量,但能提供完整的互联网接入。目前一个强趋势是使用Web技术访问终端设备。例如,Electric Imp设备通过手机屏幕闪烁将Wi-Fi凭证传输给传感器,然后通过HTTP协议将数据发送到云端服务器。

这种使用URL访问资源的方式通常被称为RESTful接口。REST的关键原则之一是服务器不保存客户端状态,这使得服务器更简单、更具可扩展性,因为请求可以被数据中心内的任何机器处理。

物联网架构的挑战

典型的物联网架构是:设备 -> 厂商网关 -> 云端服务器 -> 手机应用。这种架构面临诸多挑战:

  • 可扩展性:每个设备一个手机应用或一个专用网关的方式难以扩展。
  • 延迟:云服务的延迟可能很大。
  • 隐私:数据经过云端,用户失去控制。
  • 可靠性:环节众多,难以调试。

根据Gartner的“技术成熟度曲线”,物联网在2014年正处于“过高期望的峰值”,随后将不可避免地进入“幻灭的低谷”。这需要技术变革才能走向“启蒙的爬升期”和“实质生产的高原期”。

课程核心脉络与确定性模型 🧭

现在,让我们回到课程的主线。本课程不仅关注嵌入式系统的设计,更强调建模分析的价值,而分析离不开模型。

信息物理系统包含计算平台、网络结构和物理对象,它们之间存在复杂的相互作用。这类系统充满了不确定性,例如物理噪声、网络延迟、数据包丢失、不可知的程序执行时间以及不可控的调度。

面对如此多的不确定性,我们是否还应该讨论确定性模型?答案是肯定的。因为确定性模型是强大工程实践的基础。关键在于区分模型被建模的系统。我们可以对模型做出确定的陈述,而物理系统只是尽可能地匹配模型。

我们已有多个成功的确定性模型先例:

  • 同步数字逻辑:对混乱的晶体管电子行为进行了确定性抽象,是信息技术革命的关键。
  • 单线程命令式程序:提供了确定性的编程模型,其物理实现(同步数字逻辑)具有极高的保真度。
  • 微分方程:对物理动力学进行了确定性建模,是工业革命和现代工程的基础。

信息物理系统的核心挑战 ⚙️

然而,当我们将这些强大的确定性模型(物理微分方程、单线程程序、离散事件网络模型)组合起来构建信息物理系统时,确定性就丢失了。因为程序正确性与时序无关,而物理世界却高度依赖时序。我们缺乏一个统一的、包含时序语义的确定性模型来描述整个系统。

因此,当前的工程实践被迫退回到“原型-测试”的设计模式。课程中探讨的多个主题正是为了理解和尝试解决这个问题:

  • I/O与中断:中断服务例程破坏了程序的确定性和可分析性。
  • 多任务处理:线程通过共享内存交互会引入竞态条件,导致非确定性,使得系统几乎无法分析和充分测试。
  • 调度:试图控制而非消除非确定性。

相比之下,同步反应式状态机组合等模型提供了确定性,可以进行形式化分析和验证。基于确定性模型,我们才能讨论:

  • 组件的可替代性:在什么条件下一个组件可以安全地替换另一个。
  • 状态空间探索验证:自动检查模型是否满足某些属性。
  • 定量分析:在假设基本块执行时间有界的前提下,分析程序路径的执行时间。

未来方向:PRET项目 🚀

那么,如何重建软件对时序的控制?PRET项目旨在解决这个问题。其核心思想是:底层微处理器基于同步数字逻辑,本身具有可预测的时序能力,但上层的软件抽象(如缓存、流水线优化、动态调度)却隐藏了这种能力。

PRET项目从微架构层面着手:

  • 流水线:采用线程交错技术,让多个线程在流水线中交替执行,每个线程都有自己的寄存器组,从而避免分支预测和推测执行,获得确定性的时序。
  • 内存层次:采用类似技术管理缓存访问。
  • I/O:将中断处理作为新增线程处理,可以精确界定其对原有线程执行时间的最大影响(例如,从4个线程变为5个,每个线程速度减慢20%)。

这种改进带来了可分析性可测试性的提升,并使得硬件具有可替代性。目前,像波音777这样的安全关键系统,因为无法保证更换处理器后软件行为完全一致,不得不囤积特定批次的芯片,这是工程上的失败。PRET这类技术旨在解决此类问题。

从编程模型上看,未来我们希望能够直接表达时序约束。例如,指定一个代码块必须在500毫秒内完成,否则触发错误处理。在传统C语言中,这需要使用setjmp/longjmp等复杂且容易出错的技术。而在PRET机器上,我们可以设想使用类似MTFD的指令来直接声明“必须满足最终期限”。

总结 📝

本节课中,我们一起回顾了无线网络的新兴趋势,并深入探讨了本课程的核心主题:在充满不确定性的信息物理系统中,追求确定性模型的价值与挑战。我们分析了当前中断驱动I/O、多线程等主流技术如何引入非确定性,并展望了通过改进机器架构(如PRET)和采用新的计算模型来重建系统确定性、可分析性和可靠性的未来方向。希望这门课能让大家认识到嵌入式系统技术的现状与未来,并为各位今后的学习和职业发展提供有益的视角。

25:期中复习与课程展望

概述

在本节课中,我们将首先完成关于无线网络技术的简短讨论,然后回顾本学期课程的核心内容,并探讨嵌入式系统领域当前面临的挑战与未来的发展方向。我们将重点理解确定性模型在构建可靠网络物理系统中的重要性。


无线网络补充与新兴技术

上一节我们讨论了无线网络的基本分类。本节中,我们来看看两种值得关注的新兴网络技术:无线网状网络和低功耗物联网协议。

无线网状网络

无线网状网络与目前广泛部署的星型网络不同,其设备之间可以直接通信并为彼此中继消息。这种架构在能量消耗方面具有显著优势,因为平均通信距离更短。然而,主要挑战在于中继节点需要持续监听,这会快速耗尽电池。

为了解决这个问题,人们开发了时隙网络。节点在同步的时钟控制下周期性地唤醒、监听、通信,然后休眠。这需要节点间的时钟同步。

  • 关键技术:WirelessHART(后标准化为IEEE 802.15.4e协议的一部分)是一种相对简单的时钟同步协议,精度在毫秒级,足以让无线传感器节点依靠纽扣电池运行多年。

低功耗物联网协议

对于电池供电的设备,运行完整的IP协议栈是主要的能量消耗之一。IPv4的32位地址空间(约40亿个地址)已无法满足物联网设备海量连接的需求,而IPv6的128位地址栈能耗更高。

  • 关键技术:受限应用协议(CoAP)创建了使用16位地址的局域网“飞地”,并通过网关与全功能互联网接口进行转换,从而实现了极低能耗设备与互联网的通信。

基于Web的物联网接口

Wi-Fi设备通常提供完整的网络接口,包括HTTP支持。目前一个强烈的趋势是使用Web技术(RESTful接口)来访问终端设备(传感器和执行器)。

  • RESTful接口核心原则
    1. 使用URL(统一资源定位符)访问资源,例如:http://example.com:8080/sensor?id=1234
    2. 服务器不保存客户端状态(无状态)。所有必要的状态信息都编码在URL中,这使得服务器端更简单、更具可扩展性。

  • 典型物联网架构:设备 -> 厂商网关 -> 云端服务器 -> 手机App。这种架构存在延迟高、隐私泄露、调试困难以及服务组合不便捷(需要多个App)等问题。

当前物联网技术正处于“过高期望的峰值”,随后必将经历“幻灭的低谷”。要走向“稳定的生产高原”,需要技术的进一步演进。


课程核心脉络与挑战

现在,让我们回顾本学期课程的知识地图,并深入探讨网络物理系统设计的核心挑战。

课程知识结构

本课程教材围绕三条主线编写:

  1. 设计:如何构建嵌入式系统。
  2. 建模:如何抽象和描述系统行为。
  3. 分析:如何验证和评估系统属性。

与许多只关注设计主线的嵌入式系统课程不同,本课程强调建模与分析的重要性,因为分析离不开模型。

网络物理系统的非确定性挑战

网络物理系统包含计算平台、网络结构和物理对象,其交互中存在大量非确定性来源:

  • 物理层面:传感器噪声、执行器不精确、部件故障。
  • 网络层面:不可知的延迟、数据包丢失。
  • 计算层面:不可知的执行时间、不可控的调度。

面对如此多的非确定性,我们是否还应该使用确定性模型?答案是肯定的。历史证明,确定性模型是技术成功的关键。

确定性模型的威力

以下是三个成功的确定性模型范例:

  1. 同步数字逻辑:对底层混乱、模拟、随机的晶体管电路进行了确定性抽象,使得物理电路能够以极高的保真度模拟该模型,这是信息技术革命的关键使能器。
  2. 单线程命令式程序:构建在同步数字逻辑之上的确定性模型。其正确性与执行时间无关,物理实现也能高度忠实地模拟该模型。
  3. 物理动力学微分方程:对物理系统的确定性建模,是工业革命和许多现代工程系统(如飞机、汽车)可靠运行的基础。

关键洞见:工程师的创造性在于,他们可以构思出有用的模型,然后想办法让物理系统去匹配这个模型,而不是让模型去匹配一个不可控的物理世界。

当前网络物理系统模型的问题

当我们尝试将上述优秀的确定性模型组合起来构建网络物理系统时,问题出现了:组合后的模型失去了确定性

  • 根本原因:网络模型和程序模型缺乏对时间的描述。程序正确性独立于时间,而物理世界高度依赖时间。当我们通过内存映射寄存器、中断等机制将程序与物理世界连接时,我们跨出了程序模型的边界,引入的硬件行为没有被包含在原有的确定性模型中。
  • 后果:由于模型是分离且语义不兼容的,我们无法获得一个统一的确定性模型来描述整个系统。这导致我们不得不严重依赖“原型-测试”的设计方法,使得系统难以分析、测试和验证。

课程中探讨的应对方案

本课程介绍了一些试图理解和解决这些问题的初步方案:

  • 理解问题
    • 第十章:输入/输出:展示了中断服务例程如何破坏程序的良好特性,使得时序分析变得极其困难。
    • 多任务与调度:多线程通过共享内存交互会引入竞态条件,导致非确定性,使系统几乎不可分析和测试。信号量等机制只是在尝试约束非确定性,而非根除它。
  • 探索解决方案
    • 同步反应式状态机:这是一种确定性并发模型,可以进行分析和定理证明,并且能够在物理世界(如数字电路)中实现高保真度的实现。
    • 抽象与精化:定义了组件可替换性的条件,这对于系统升级和维护至关重要。
    • 状态空间探索:基于确定性模型的形式化验证技术。
    • 最坏情况执行时间分析:基于程序(确定性模型)的路径分析,其前提是能够估算基本块的执行时间边界。

未来展望:PRET项目

上述方案只是初步尝试。本节中,我们来看看一个旨在从根本上解决问题的研究项目——PRET(精确时序处理器)项目。

目标与思路

核心问题:底层硬件(同步数字逻辑)具有高度可预测的时序,但上层的软件抽象(特别是单线程命令式编程模型)隐藏了这一特性。

PRET项目目标:在微架构层面进行创新,重新搭建硬件与软件之间的桥梁,使软件能够重新获得对时序的控制能力,同时不牺牲性能。

关键技术方向

现代处理器为提高平均性能引入的技术(如深度流水线、分支预测、缓存)严重损害了时序的可预测性。PRET从以下三个方面着手:

  1. 流水线:采用线程交错技术。让多个线程的指令在流水线中交错执行,每个线程拥有独立的寄存器组。这样无需分支预测和推测执行,每个指令流都能以确定性的时序执行。
  2. 内存层次:研究使缓存行为更可预测的技术。
  3. 输入/输出:采用线程交错处理中断。当中断到来时,处理器不是抢占当前线程,而是启用一个新的硬件线程来执行中断服务程序。这样,中断对原有线程的影响仅仅是使其执行速度按比例略微减慢(例如,从4个线程变为5个线程,每个线程慢20%),这是一个严格且可分析的边界。

带来的好处

确定性提升意味着可分析性和可测试性的提升。例如,在航空领域,波音777因其飞控软件与特定年代生产的微处理器深度绑定,不得不花费巨资囤积芯片。如果有基于确定性模型的、可保证时序行为的软硬件接口,就能实现硬件的可替换性,避免这种“工程上的尴尬”。

编程模型展望

在PRET这样的机器上,我们可以设想更强大的编程原语。例如,我们希望能够指定一个代码块必须在某个时限内完成。

以下是当前在C语言中实现类似功能的复杂方式(使用setjmp/longjmp):

#include <setjmp.h>
jmp_buf env;
if (setjmp(env) == 0) {
    // 设置定时器中断
    // 执行可能超时的代码块
    // 如果顺利完成,取消定时器
} else {
    // 超时处理(panic routine)
}
// 定时器中断服务程序中调用 longjmp(env, 1);

而我们期望的编程模型可能更简洁、更安全,例如一个MTFD(满足最终期限)指令或语言结构,它能保证代码块在指定时间内完成,否则仅因硬件故障才可能失败。


总结

本节课中,我们一起学习了:

  1. 无线网状网络和CoAP等物联网新兴技术的特点与价值。
  2. 当前基于云和RESTful接口的物联网架构的优势与局限。
  3. 网络物理系统设计中面临的根本挑战:将多个优秀的确定性模型组合后,整体却失去了确定性。
  4. 确定性模型(如同步数字逻辑、同步反应式状态机)对于系统可分析性、可测试性和可靠性的至关重要性。
  5. PRET研究项目如何从硬件架构层面着手,旨在为软件重新提供对时序行为的确定性控制,从而为构建更可靠、更易维护的网络物理系统开辟道路。

希望本课程不仅教会了你当前嵌入式系统的实现方式,更启发了你对未来技术发展方向的思考。嵌入式系统领域依然年轻且充满挑战,正是进行创新和贡献的大好时机。

posted @ 2026-03-29 09:26  布客飞龙II  阅读(1)  评论(0)    收藏  举报