阅读前言
本文以QNX系统官方的文档英文原版资料为参考,翻译和逐句校对后,对QNX操作系统的相关概念进行了深度整理,旨在帮助想要了解QNX的读者及开发者可以快速阅读,而不必查看晦涩难懂的英文原文,这些文章将会作为一个或多个系列进行发布,从遵从原文的翻译,到针对某些重要概念的穿插引入,以及再到各个重要专题的梳理,大致分为这三个层次部分,分不同的文章进行发布,依据这样的原则进行组织,读者可以更好的查找和理解。
1. 自适应分区
在许多计算机系统中,保护不同的应用程序或应用程序组之间彼此不受干扰是很重要的。你肯定不希望某个应用程序破坏其他应用程序或者妨碍它们运行(无论是有缺陷的还是恶意的)。
为了解决这个问题,有些系统会在一组应用程序周围设置被称为"分区(partitions)"的虚拟屏障,以确保每个分区都能被分配到一组经过规划的资源。首要考虑的资源是 CPU 时间,但任何共享资源,比如内存和文件空间(磁盘或闪存),也都可能会被纳入考量范围。
QNX Neutrino 的自适应分区仅支持 CPU 时间的分配。
通过使用多个分区,你可以避免出现单点故障。例如,避免某个失控的进程占据整个系统的资源;这样做之后,其他分区中的进程仍然能够获得分配给它们的那部分系统资源。
即便没有自适应分区,QNX Neutrino 的进程模型所提供的保护也远比某些其他操作系统要多,这些保护包括:
- 进程间的全面内存保护;
- 通过消息传递来提供统一且受控制的进程间通信(IPC);
- 带有清晰"客户端-服务器模型"的优先级继承特性;
- 硬实时确定性调度;
- 针对设备、文件和内存的详细权限模型;
- 利用 POSIX 标准的 setrlimit() 函数对内存、文件描述符、CPU 以及优先级进行限制,以此约束失控的进程。
通常情况下,其他系统中的资源分区的主要目的是将一台计算机划分成一组相互间尽可能少交互的小型计算机,但这种方法不太灵活。在 QNX Neutrino 中,自适应分区采用了一种更为灵活的理念。
我们的分区之所以是自适应的,原因如下:
- 你可以在运行时更改配置;
- 分区的行为会在运行时根据实际情况自动调整。例如:
- 空闲时间会被重新分配给其他调度分区;
- 文件系统可以通过一种能在不同时间分区之间临时移动线程的机制,向客户端收取时间费用。
1.1. 为什么要自适应?
为了在确保能防止过载的情况下提供实时性能,QNX Neutrino 引入了自适应分区功能。在软件动态部署很少或几乎没有的相对静态的系统中,刚性分区(静态分区)能够发挥最佳效果。而在动态系统中,静态分区可能效率低下。例如,分区之间对执行时间进行静态划分可能会浪费 CPU 时间并导致延迟:
- 如果大多数分区处于空闲状态,而有一个分区非常繁忙,那么繁忙的分区不会获得任何额外的执行时间,而其他分区中的后台线程却在浪费 CPU 时间。
- 如果为某个分区安排了中断,它必须等到该分区运行时才能处理。这可能会导致不可接受的延迟,尤其是在出现中断突发情况时。
自适应分区是一组为了共同或相关目标或活动而协同工作的线程集合。与静态分区一样,自适应分区也会被分配一个资源预算,以确保其能获得 CPU 资源的最小份额。但与静态分区不同的是,自适应分区具有以下特点:
- 它并不局限于静态分区中的固定代码集;你可以根据需要动态地添加和配置自适应分区。
在正常负载情况下,它的表现如同一个全局硬实时线程调度器,但即便在过载条件下,它仍然能够提供最小的中断延迟。 - 当系统负载较低时,它能通过将某个分区未使用的预算分配给那些需要额外资源的分区,从而最大限度地利用 CPU 资源。
你可以引入自适应分区功能,而无需更改(甚至无需重新编译)你的应用程序代码,不过你确实需要重新构建系统的操作系统镜像。
你最多可以设置 32 个分区。在 QNX Neutrino 操作系统中,被调度的是线程,而不是分区。
1.2. 自适应分区的好处
自适应分区为系统的设计、开发、运行和调试提供了许多好处。
1.2.1. 工程化产品性能
自适应分区让你能够对系统进行设计,以便优化其性能。
分区对资源进行划分,使得资源能够被一组程序所使用。一个分区代表着一部分资源,并且包含一些用于定义资源使用情况的规则。资源涵盖了基础对象,例如处理器周期、程序存储空间,也包括高级对象,比如缓冲区、页表或者文件描述符。
自适应分区确保系统中任何可用的空闲时间(即分区预算中该分区不需要的 CPU 时间)能够提供给其他分区使用。这使得系统能够应对在正常系统运行期间出现的突发处理需求。在采用循环线程调度器的情况下,存在一种 "不用则废" 的方式,即未使用的 CPU 时间会被用于运行那些未用完预算的分区中的空闲线程。
自适应分区的另一个重要特性是分区继承的概念。这一特性使得设计人员能够开发出运行时无需(或只需极少)预算的服务器进程。当服务器处理来自客户端的请求时,会向客户端分区收取相应的时间费用。如果没有这一特性,无论服务器使用资源的频率高低或用量多少,CPU 预算都会分配给它。这些特性带来的好处包括:
- 你无需对系统过度设计,因此总体成本会降低。
- 如果你添加一个应用程序,无需重新规划诸如文件系统或服务器等公共服务的预算。
- 系统的运行速度会更快,并且对用户的响应也会更及时。
- 系统能够保障重要任务所需的时间。
- 你可以使用优先级来指定一个进程的紧急程度,并用分区的 CPU 预算来指定其重要性。
1.2.2. 处理设计复杂性
设计大规模分布式系统本身就是复杂的。典型的此类系统包含大量彼此独立开发的子系统、进程和线程。设计工作会分配给不同的团队,而这些团队有着不同的系统性能目标、不同的优先级确定方案以及不同的运行时优化方法。
若产品开发处于不同的地理位置和时区,这种复杂性还会进一步加剧。一旦所有这些各不相同的子系统被集成到一个通用的运行时环境中,系统的所有部分都需要在所有运行场景下提供足够的响应,例如:
- 正常系统负载情况;
- 高峰时段;
- 故障状况。
鉴于开发路径是并行的,在产品集成时总会出现系统问题。通常情况下,一旦系统开始运行,那些会导致严重性能下降的不可预见的交互情况就会暴露出来。当出现这类情况时,通常很少有设计师或架构师能够在系统层面进行诊断并解决这些问题。解决方案往往需要大量的修改(通常是通过反复试验)才能奏效。这会延长系统集成的时间,进而影响产品上市时间。
这类性质的问题可能需要花费一周甚至更长时间来进行故障排查,还需要几周时间来调整整个系统的优先级、重新测试并完善。如果这些问题无法得到有效解决,产品的可扩展性就会受到限制。
这在很大程度上是因为没有有效的方法能在这些不同团队之间 "规划" CPU 的使用情况。线程优先级提供了一种确保关键任务运行的方式,但却无法为重要的非关键任务保证 CPU 时间,这些任务在正常运行时可能会缺乏资源。此外,建立线程优先级的通用方法很难在大型开发团队中进行扩展应用。
利用线程调度器的自适应分区功能使得架构师能够为应急目的(比如灾难恢复系统或现场调试外壳)保留一定的资源储备,并且可以为每个子系统定义高级别的 CPU 预算,允许开发团队在给定预算范围内实施他们自己的优先级方案和优化措施。这种方法能让设计团队独立地开发子系统,并简化集成工作。最终效果是能够缩短产品上市时间,并促进产品的扩展性。
1.2.3. 提供安全性
许多系统都容易受到拒绝服务(DOS)攻击。例如,恶意用户可能会向系统发送大量需要由某个进程处理的请求。遭受攻击时,这个进程会使 CPU 过载,进而导致系统的其余部分实际上无法获得足够的资源(被"饿死")。
一些系统试图通过实现一个监控进程来解决这个问题,该监控进程会检测 CPU 的利用率,并且当它认为某个进程占用了过多 CPU 资源时就会采取纠正措施。这种方法存在若干缺陷,包括:
- 响应时间通常较慢。
- 这种方法在需要进行合法处理时会限制 CPU 的使用率。
- 它并非万无一失或绝对可靠;它依赖于恰当的线程优先级来确保监控进程能获得足够的 CPU 时间。
自适应分区可以通过为系统的各种功能分配独立的预算来解决这个问题。这能确保系统始终有为重要任务预留的一定 CPU 处理能力。线程可以自行改变其优先级,这可能会成为一个安全漏洞,不过你可以配置线程调度器,防止在分区中运行的代码自行更改其预算。
由于自适应分区能够将任何未使用的 CPU 时间分配给有需求的分区,所以在的合法情况下(确实需要增加处理能力),自适应分区不会不必要地限制控制平面(control-plane)的活动。
1.2.4. 调试
自适应分区甚至能够让嵌入式系统的调试工作变得更加轻松,无论是在开发阶段还是部署阶段,它通过为系统提供一扇 "应急门" 来实现这一点。
只需创建一个可以在其中运行诊断工具的分区就行;如果不需要使用这个分区,线程调度器会将该分区的预算分配给其他分区。这样一来,你就能在不影响系统性能的情况下访问系统了。如需更多信息,请参阅《自适应分区用户指南》的 "测试与调试" 章节。
1.3. 自适应分区线程调度器
线程调度器是一种可选的调度器,它能确保将 CPU 吞吐量的最小百分比分配给线程组、进程或应用程序。分配给一个分区的 CPU 时间百分比被称作预算。
线程调度器是基于 QNX Neutrino 核心架构设计的,其主要目的是解决嵌入式系统设计中的以下这些问题:
- 在系统过载时,保证指定的 CPU 时间最小份额;
- 防止不重要或不可信的应用程序独占系统资源。
如需更多信息,请参阅《自适应分区用户指南》。