嵌入式控制系统设计/操作系统
维基教科书
嵌入式控制系统设计
|
操作系统是一个管理计算机硬件和软件资源的计算机程序。它提供应用程序与系统硬件之间的接口。一般来说,嵌入式控制系统的操作系统具有以下职责: 任务管理 和 调度、中断服务、进程间通信 和 内存管理。这些详细内容将在后面讨论,因为我们首先从更通用的操作系统方法开始。
在为嵌入式控制系统选择操作系统时,首先要问的问题是:操作系统真的有必要吗?对于简单的任务或只需要做一项工作的系统,创建直接在 微处理器 上运行的程序可能更容易、更有效率,也许可以使用 超级循环架构。当嵌入式控制系统必须执行更复杂的任务或能够连接或与其他设备交互时,就需要操作系统。操作系统将允许更大的灵活性。以下列表概述了最常见的实时操作系统。
为了便于选择操作系统,可以考虑以下因素
- 兼容性:并非所有操作系统都能在任何类型的微处理器上运行!因此,在选择操作系统时,应该已经对可用的硬件有所了解。
- 系统要求和占用空间:嵌入式系统的 占用空间 是系统总大小。例如,如果嵌入式控制系统的总大小为 600 kB,则可以称系统的占用空间为 600 kB。这是一个小的占用空间,更大的占用空间将以 MB 为单位。必须考虑系统的占用空间,以确保可用硬件能够支持该系统。
- 软件 支持:操作系统选择中的另一个重要因素是可用的支持软件。是编辑器、编译器和库很多,还是很少?这与列表中的下一个要点相关;
- 用户数量:如果许多人使用一种操作系统,那么该系统将有更多可用的软件。也更容易找到文档、特定问题的解决方案或使用相同操作系统的其他人。
- 设备驱动程序的可用性
- 动态添加代码的可能性:有时可能需要向已工作的系统中添加程序。操作系统允许这样做吗?
- 认证:某些特定应用程序要求操作系统获得在该特定应用程序中使用的认证。一个例子是使用将在太空中使用的操作系统(卫星、火箭等)。
- 软/硬实时:满足定时约束有多重要?在软实时中,计算在指定时间内未完成并不是灾难(但不可取)。示例是音频和视频播放。没有人会注意到几微秒的延迟。在硬实时中没有例外:系统必须满足指定的定时约束。硬实时系统例如外科手术机器人、太空探测器或精密机床。
在选择操作系统时,还可以考虑以下趋势
- 远程访问:越来越需要能够远程访问系统。在全球各地运送机器的制造商,如果想访问控制系统,无法去安装机器的每个地方。因此,远程访问变得越来越重要。
- 安全完整性等级:安全完整性等级是对系统可靠性的相对衡量。控制危险过程的系统应该具有较高的 SIL 等级。为了达到该 SIL 等级,对软硬件提出了具体要求。这也可能会影响操作系统的选择。
- 操作系统开发:曾经有数百种可用的嵌入式操作系统。当微软推出他们的 WinCE 系统时,许多系统消失了,不同系统的程序员共同努力创建了一个通用的嵌入式操作系统,从而进一步减少了可用系统的数量。
本节旨在更详细地讨论操作系统的基础知识。如前所述,嵌入式操作系统需要完成的重要任务是:任务管理和调度、中断服务、进程间通信和内存管理。
任务(= 进程或线程)是正在执行的计算机程序的实例。程序本身只是一组被动的指令,而进程是实际执行这些指令的东西。多个进程可以与同一个程序相关联——每个进程都会独立执行。现代计算机系统允许在同一时间将多个进程加载到内存中,并且通过时间共享(或多任务处理),即使只有一个处理器,也能让人感觉它们是在同时执行的。
任务管理中困难的部分是确保特定任务能够退出而不会阻塞其他任务。事实上,不应该盲目地删除任务,因为它与其他任务共享了许多组件,因此在导致任务仍在使用其内存空间和锁时,不应释放其内存空间和锁。
一般来说,多个任务会同时处于活动状态。操作系统负责在任务之间共享可用资源(CPU 时间、内存等)。CPU 是重要的资源之一,决定如何将 CPU 在任务之间共享被称为调度。
解决调度问题最简单的方法是为所有任务分配静态属性。这意味着优先级是在任务创建时给定的。然而,有时会为任务分配动态属性:调度算法必须根据动态变化参数(到下一个截止时间的时间、要处理的工作量等)在线计算任务的优先级。很明显,调度纯粹是开销:所有花费在计算下一个要运行的任务上的时间都被真正生产性任务浪费了。
中断
[edit | edit source]操作系统还必须能够为外围硬件提供服务,例如定时器、电机、传感器、通信设备、磁盘等。所有这些都可以异步地请求操作系统的关注,即当它们想要使用操作系统时,操作系统必须确保它已准备好为这些请求提供服务。这种请求关注被称为中断。
有两种中断
- 硬件中断:外围设备可以在特定硬件通道上设置一个位,触发运行操作系统的处理器,以发出设备需要服务的信号。该触发的结果是处理器保存其当前状态,并跳转到其内存空间中的一个地址,该地址在初始化时已连接到硬件中断。
- 软件中断或同步中断:许多处理器内置了软件指令,这些指令可以用于在软件中生成硬件中断的效果。软件中断的结果也是触发处理器,使其跳转到预先指定的一个地址。软件中断出现的案例示例可能是除以零、内存段错误等。因此,这种中断不是由硬件事件引起的,而是由特定的机器语言操作码引起的。同步中断通常被称为陷阱。
许多系统拥有不止一条硬件中断线,硬件制造商通常将所有这些中断线组装成一个中断向量。硬件中断和软件中断共享同一个中断向量,但该向量随后会为硬件中断和软件中断提供独立的范围。
中断控制器是一块硬件,它可以保护操作系统免受中断线的电子细节的影响。一些控制器能够将中断排队,以确保没有任何中断丢失。
我们将在本书的其他章节中更详细地讨论中断,嵌入式控制系统设计/实时操作系统#中断服务和嵌入式控制系统设计/设计模式#中断。
进程间通信
[edit | edit source]不同的任务有时需要同步,例如,它们执行的顺序很重要。任务之间也可能需要交换数据。一个例子是外围设备,其数据由专用任务处理,该任务反过来将结果传递给另一个任务。同步和数据交换统称为进程间通信。操作系统应该提供各种进程间通信原语,以便任务可以轻松地同步或交换数据。
- 同步
- 信号量
信号量是一个受保护的变量(或抽象数据类型),是限制在多编程环境中访问共享资源(例如存储)的经典方法。它是由 Edsger Dijkstra 发明的,并首次在 THE 操作系统中使用。信号量的值初始化为它被实现来控制的等效共享资源的数量。在只有一个等效共享资源的特殊情况下,信号量被称为二元信号量。一般情况下的信号量通常被称为计数信号量。信号量是解决哲学家就餐问题的经典解决方案,尽管它们无法防止所有死锁。 - 互斥锁
互斥锁是“互斥”的缩写,是在抢占式环境中使用的一种机制,可以防止对当前正在使用的资源进行未经授权的访问。互斥锁遵循以下几个规则:互斥锁是系统范围内的对象,由内核维护。一次只能由一个进程拥有互斥锁。可以通过请求内核将该互斥锁分配给当前任务来获取互斥锁。如果互斥锁已被分配,则请求函数将阻塞,直到互斥锁可用。一般来说,尽快释放互斥锁被认为是良好的编程实践。 - 条件变量
为了避免进入繁忙等待状态,进程必须能够相互发出有关其感兴趣事件的信号。监视器通过条件变量提供这种能力。当监视器函数需要满足特定条件才能继续执行时,它会在关联的条件变量上等待。通过等待,它会放弃锁并从可运行进程集中移除。任何随后导致条件为真的进程都可以使用条件变量来通知等待该条件的进程。被通知的进程会重新获得锁并可以继续执行。
- 信号量
- 数据交换
- 缓冲区
缓冲区是内存中用于临时保存输出或输入数据的区域,类似于通信中的缓冲区。数据可以输出到或输入自计算机外部的设备或计算机内部的进程。缓冲区可以在硬件或软件中实现,但绝大多数缓冲区都是用软件实现的。当接收数据的速率与处理数据的速率不同时,或者当这些速率是可变的时,例如在打印机后台打印程序中,就会使用缓冲区。 - FIFO
这个术语指的是存储在队列中的数据处理方式。队列中的每个项目都存储在一个队列(simpliciter)数据结构中。添加到队列中的第一个数据(入队)将是第一个要删除的数据(出队),然后按顺序依次进行处理。 - 消息
消息传递是并发编程、并行编程、面向对象编程和进程间通信中使用的一种通信形式。通过将消息发送到接收者来进行通信。
- 缓冲区
内存管理
[edit | edit source]术语
在执行任务时,系统需要RAM内存。此 RAM 内存主要用于代码、数据管理和 IPC(进程间通信)与其他任务。RAM 使得任务看起来像是运行时唯一占用内存的任务。总结来说,它有三个主要任务
- 使内存看起来比实际更大。
- 将内存分配到多个页面。
- 保护内存不被其他任务访问。
在考虑这些一般操作系统要求时,必须记住它们不是实时系统的关注点。需要满足的实时要求是
- 快速且确定的内存管理:当然,最快的内存管理是根本没有内存。这意味着程序可以使用所有物理 RAM。这种方法仅在为小型嵌入式控制系统编程时有用。
- 页面锁定:每个任务都会在 RAM 中获得固定数量的页面。最近未使用过的页面会被一个非确定性进程交换出去。当任务需要来自已交换页面的代码或数据时,系统必须从磁盘中检索该页面,这通常需要 RAM 中的另一个页面被交换出去。这意味着系统必须锁定实时任务的页面。否则,在进程执行期间,必要的页面很可能被交换出去。因此,页面锁定是一种服务质量,意味着每个任务都能够获得它所需的内存量。
- 分配:在进程执行过程中的某个时刻,任务始终可能需要更多内存。这种额外的内存由物理 RAM 中锁定的一个空闲页面 池 提供。使用这种方法时要小心。无法保证内存池中始终有足够的空闲页面可用。
- 内存映射:实时和嵌入式系统必须访问 外设。这些设备将它们的数据存储在板载寄存器中。然后,这些寄存器必须映射到相应设备驱动程序任务的地址空间中。
- 内存共享:共享内存允许不同的任务相互通信。操作系统将释放共享内存,并同步对该内存的访问。
- RAM 磁盘:这些磁盘可以用来模拟硬盘。这意味着内存被组织并作为文件系统输入。这种内存管理的好处是
- 避免访问硬盘的非确定性开销。
- 无需额外成本。
- 无需额外空间。
- 不会降低机械磁盘设备的鲁棒性。
- 闪存盘:当系统在电源关闭时需要数据保存时,设计人员必须使用闪存盘。这些磁盘可以反复烧录,并且在没有电源的情况下仍然可以保留内存。闪存从系统本身内部加载。
- 精简库:RAM 资源有限,因此设计人员更倾向于使用精简版本的通用实用程序库,例如 C 库、GUI 库 等。
Linux 中的共享内存
在 Linux 中,有多种方法可以分配共享内存。使用 RTLinux 时允许分配,而使用 RTAI 时则不允许。
- 启动时分配:可以在启动时保留一块共享内存。这意味着 Linux 无法将其用于一般用途。不过,启动时分配并非最佳选择:当仅对内核映像中链接的代码可用时,使用这种分配的设备驱动程序只能通过重新构建内核并重新启动计算机来安装或替换。
- 内核空间中的分配:使用这种方法意味着内存不必在启动时准备好。此外,内存大小没有限制。不幸的是,这不是一个实时进程。
- 模块中的分配:在 Linux 中,有一个名为“可加载模块支持”的选项。这意味着 Linux 可以分为不同的 模块,可以访问和调用这些模块。这些模块实际上是将内核放到内存中并根据需要将其取出的代码片段。这种方法最好在启动时使用。
设备驱动程序
[edit | edit source]设备驱动程序 的主要目标是允许操作系统和外设之间进行通信。它还提供内核软件和用户软件之间的链接。实现某种系统化的方法来执行此操作非常重要。这使得用户更容易识别界面。驱动程序有责任隐藏硬件的细节,使其对操作系统乃至最终用户不可见。本章将讨论嵌入式控制系统中可用的几种设备驱动程序。
机制和策略
设备驱动程序必须提供机制,而不是策略。它必须提供接口功能,但不能提供更多功能(机制)。驱动程序不会修改数据,它永远不会更改数据流(策略)。它只是使设备能够以特定方式使用。
可用的设备驱动程序
- Unix 设备驱动程序:在 Unix 中,设备驱动程序实际上是 Unix 内核的一部分。这使得驱动程序与独立程序略有不同。内核由基本代码加上帮助系统与硬件通信的驱动程序代码组成。在 Unix 中编写设备驱动程序时,需要检查以下步骤
- 命名空间:在编写任何代码之前,务必为您的设备命名。
- 内存分配:使用 kmalloc() 函数。这意味着内存以大小为 2 的幂的块的形式提供。这方面有一个限制,即 131056 字节。此外,kmalloc() 需要第二个参数:优先级。然后,Kfree() 将为您提供分配内存的机会。
- 字符设备与块设备:这是 Unix 下两种主要的设备类型。字符设备不使用缓冲区,块设备通过缓存访问。
- 中断 与 轮询:中断设备驱动程序与轮询设备驱动程序之间存在差异。在 Unix 中,内核不是一个独立的任务。系统中的每个部分或进程都有自己的内核副本[citation needed]。当系统向某个进程发出调用以执行某个任务时,这被称为“内核模式”。进程没有将控制权转移到另一个进程。在内核模式下,数据放置位置显而易见,因为进程在其自己的模式下运行。但是,当发生中断时,它们会改变这种数据放置,并可能导致 宏[citation needed](允许系统访问用户空间内存)覆盖随机内存空间或导致内核崩溃。驱动程序必须安排中断,并且在安排中断时,它必须提供临时空间来放置信息。在块设备驱动程序中,缓冲区会安排这种临时空间。使用字符设备驱动程序时,设备驱动程序本身负责分配。
- 复杂设备驱动程序:普通设备驱动程序只做一件事,即向外设写入或读取数据流。当设备需要中断来与用户软件通信时,这些驱动程序往往会变得更加复杂。这种驱动程序需要 ISR,甚至可能还需要 DSR。设备驱动程序变得复杂的另一个原因是:某些设备允许使用共享内存甚至 直接内存访问。这意味着没有处理器参与数据交换。操作系统必须支持 DMA。在设计复杂的设备驱动程序时,最好对设备的结构和 API 进行标准化
- API:当设备提供与其他设备类似的机制时,设计人员可以使用相同的软件接口。
- 结构:设备驱动程序必须能够处理接口功能的更多层。
- Comedi:数据采集卡用于测量和控制目的。Comedi 将为它们提供接口。这是一个模块化和复杂性之间的平衡。以下是设计人员在处理 Comedi 时需要牢记的一些问题
编写 Comedi 设备驱动程序以及编写过程中的不同步骤在 此处 解释。
- 实时串行线路驱动程序:这些类型的设备驱动程序集成到 RTAI 中。它们提供
- 高度可配置的地址初始化,
- 中断处理,
- 缓冲、回调、非侵入式缓冲区检查…
- 实时并行端口驱动程序:这些驱动程序集成在 Comedi 中。它们允许实时中断处理程序连接到并行端口上的中断。
- 实时网络驱动程序:集成在 RTAI 中,它为RTOS 和以太网网络的设备驱动程序提供一个通用的编程接口。
在开发操作系统时,如果设计符合某些标准,将有许多优势。软件是团队开发的,标准使设计更容易、更快、更透明。标准提高了现有应用程序移植到新硬件的可移植性。它们还有助于不同软件组件之间的协作。
POSIX 或“可移植操作系统接口”是由 IEEE 制定的一个系列相关标准的统称,用于定义与 Unix 操作系统变体兼容的软件的应用程序编程接口 (API)。POSIX 在大约 15 个不同的文档中指定了对操作系统的用户和软件接口。对于实时系统,POSIX 具有以下标准
1003.1b | 实时扩展 | 实时系统所需的函数;包括对以下内容的支持:实时信号、优先级调度、计时器、异步 I/O、优先级 I/O、同步 I/O、文件同步、映射文件、内存锁定、内存保护、消息传递、信号量 |
1003.1d | 其他实时扩展 | 其他接口;包括对以下内容的支持:新的进程创建语义 (spawn)、零星服务器调度、进程和线程的执行时间监控、I/O 建议信息、阻塞函数的超时、设备控制 |
1003.1j | 高级实时扩展 | 更多实时函数,包括对以下内容的支持:类型化内存、nanosleep 改进、屏障同步、读写锁、自旋锁以及消息队列的持久通知 |
1003.21 | 分布式实时 | 支持实时分布式通信的函数;包括对以下内容的支持:缓冲区管理、发送控制块、异步和同步操作、有界阻塞、消息优先级、消息标签以及实现协议 |
POSIX 基于文件系统的存在和进程的数量,定义了实时系统的配置文件。
配置文件 | 进程数量 | 线程 | 文件系统 |
54 | 多个 | 是 | 是 |
53 | 多个 | 是 | 否 |
52 | 单个 | 是 | 是 |
51 | 单个 | 是 | 否 |
- 内核与用户空间?
- 死锁?