目录
前言
本文为汽车电子嵌入式编程-【无人驾驶】QNX操作系统的补充篇,QNX是主要针对嵌入式系统市场的基于Unix的实时操作系统(RTOS)。该产品最初是由加拿大的Quantum Software Systems公司于1980年代开发的,后来更名为QNX Software Systems。2010年,QNX被黑莓收购。该公司成功地成为商业上最成功的微内核操作系统平台开发商之一,并用于广泛的领域,包括汽车、移动电话和IoT。
QNX与AUTOSAR RTAOS的区别
QNX和RTAOS的差别,最直接的区别就是,一个是加拿大的黑莓公司的,一个是ETAS的。相比于RTAOS,由于MMU的使用,QNX更凸显进程和线程的概念,而RTAOS,软件直接运行在内存上(也可以说LMA和VMA是相同的,虚拟内存地址和加载内存地址),没有MMU,所以还是主要凸显Task的概念。大家现在简单理解就将RTAOS的task理解为QNX中的线程吧。比如rtaos下cpu0 5ms的task,就简单的理解为QNX下一个5ms运行一次的线程。
常见的车载电子操作系统介绍
车载电子操作系统是汽车智能化的核心,能够有效分配车机的硬件资源,对车内各种任务功能进行协同管理,并控制各项任务优先级别。常见的车载电子操作系统有:QNX、Linux(Android,AaliOS)、Windows CE、iOS等,此外还有一些非主流操作系统如Wind River和micro-ITRON 等。
(1)QNX
黑莓旗下的一款微内核实时操作系统,是全球第一款通过ISO 26262 ASIL levelD安全认证的车载操作系统,目前市场占有率超过50%,已经应用在包括法拉利、劳斯莱斯、布加迪、宝马、奥迪、奔驰等超过40个品牌的6,000多万辆汽车中。
QNX特点是: 稳定性和安全性非常高,实时性也比较好,但缺点是兼容性较差。
QNX是嵌入式分布式操作系统,和微软的Windows2000几乎同步开发,用的是完全不同的架构,安全性能和可靠性确实处于世界一流水准,同级别的商业系统只有Windriver公司产品能与其抗衡。当然这并不是世界上最安全的系统,特殊军用产品系统才是最安全的操作系统,因为它几乎是在不计成本只追求性能的前提下开发的。
(2)Android
是目前为止基于Linux Kernel开发的最成功的操作系统,全球智能手机市场占有率超过80%。Android系统最大的优势在于兼容性,以及无数与之相匹配的应用。但作为车载操作系统,Android的稳定性和安全性较差。
(3)WindowsCE
是微软1996年发布的嵌入式操作系统。由于PC时代Windows称霸很多年,应用开发便利,同时提供大量的开发包(Kits)和调试工具(DebugTools),所以WinCE是当时最火的车载操作系统。但随着Linux和Android的冲击,微软在智能手机领域节节败退,已经退出了嵌入式操作系统市场,WinCE 7.0也将停止更新。
AliOS:2014年前后,互联网巨头纷纷推出了自己的“车载操作系统”:苹果的Carplay,谷歌的Android Auto,百度的Carlife,阿里的AliOS以及腾讯的WeLink。但除了AliOS可以算作类Linux的车载操作系统之外,其它产品均是通过MirrorLink、Miracast等通信协议将智能手机映射到车机屏幕上的解决方案,并不是“车载”的操作系统。
他们核心功能就是建立手机与车机互联,显示的UI界面也不是APP,信息全部来自手机端。AliOS2016年搭载到上汽荣威RX5中,销量非常不错。
(4)iOS
苹果对车载操作系统的研发还没有对外界披露细节,不过随着QNX前CEO兼创始人DanDodge加入苹果,负责苹果“ProjectTitan”自动汽车项目的研发,期待苹果未来在车载电子操作系统的一鸣惊人。
根据IHS数据,随着WinCE逐步退出车载操作系统市场,长期来看未来将会是QNX与Linux(Android)两家独大的市场。
对比车载电子操作系统与智能手机操作系统,智能手机初期在经历过Android、Symbian、Blackberry、Windows Mobile,BADA等竞争之后,最后剩下了Android与iOS,价值巨大。目前车载电子操作系统尚处在初期阶段,竞争对手较多,但智能汽车渗透率还处在比较低的水平,因此车载电子操作系统的价值还远未体现,未来车载操作系统蕴含巨大价值。
QNX产品介绍
QNX操作系统
QNX是一种商用的遵从POSIX规范的类Unix实时操作系统,目标市场主要是面向嵌入式系统。它可能是最成功的微内核操作系统之一。
QNX是一种商用的类Unix实时操作系统,遵从POSⅨ规范,目标市场主要是嵌入式系统[1]。QNX成立于1980年,是加拿大一家知名的嵌入式系统开发商。
QNX的应用范围极广,包含了:控制保时捷跑车的音乐和媒体功能、核电站和美国陆军无人驾驶Crusher坦克的控制系统[2],还有RIM公司的BlackBerry PlayBook平板电脑。
多任务、多用户的操作系统
首先QNX是一个多任务、多用户的操作系统。它支持在同一台计算机上同时调度执行多个任务;也可以让多个用户共享一台计算机,这些用户可以通过多个终端向系统提交任务,与QNX进行交互操作。由于QNX在设计实现时,遵循了POXIS 1003.1标准,使得它在许多功能上与UNIX操作系统极为相似,既支持多个用户同时访问,也支持多个任务同时执行。因此,它是一个多任务、多用户的操作系统。
并行操作系统
QNX提供普通RTOS版本只提供了对单处理器的支持;另外,它还提供了Neutrino版本,此版本可支持多个处理器,支持对称多处理。
分布式操作系统
QNX操作系统可以将网络中的多台计算机耦合起来。它使得任何一台计算机上的任何一个进程可以和其它任何计算机上的任何进程通信,象与本机进程通信一样;也使得任何一台计算机上任何进程可以使用其它任何计算机上的资源,象在本机上一样。唯一的要求是,用户具有相应的权限。这样,用户可以将任务分散到网络中,交给其它任何计算机来完成。而用户的感觉与在一台集中式多任务操作系统上工作没什么区别。
而象QNX这种分布式操作系统与网络操作系统的区别在于,网络操作系统是在松散耦合的硬件上松散耦合软件,分布式操作系统是紧密耦合软件。这种操作系统很容易建立任务分担的高可用机制。
实时操作系统
实时系统能够在限定的时间内执行完所规定的功能,并能在限定时间内对外部异步事件做出响应。QNX是一种理想的实时操作系统,它提供了一个实时操作系统所需要的一切基本要素:多任务、优先级驱动的紧急者优先式的调度方式和快速的上下文切换。对于实时性要求不同的应用,QNX可以按用户的要求,安排适当的调度,使各种应用得以在QNX环境中理想地运行。
嵌入式操作系统
说QNX是嵌入式操作系统,是因为它具备一个很小的内核,即微内核的操作系统。QNX的内核一般只有几十KB,整个操作系统可根据需要进行定制系统需要的模块。定制后的系统,所占用的空间也很小,而且不失实时、多任务的特性。因此,整个操作系统又是灵活可伸缩的。
QNX OS for Safety
QNX OS for Safety是一种软件解决方案,能够以经济高效且安全的方式为构建具有竞争力的汽车和关键任务系统提供必要的可靠基础。QNX OS for Safety支持广泛的应用程序,包括但不限于:
- 自动驾驶车辆的控制系统
- ADAS系统
- 数字和混合仪表组
- 高速列车系统
- 工业自动化
- 能源生产
- 医疗机器人手术
通过功能安全标准认证,QNX OS for Safety 2.0是QNX 7.0产品系列中第一个经过安全认证的产品。它基于QNX SDP 7.0,这是最先进,最安全的嵌入式操作系统,适用于所有安全和关键任务应用。QNX OS for Safety 2.0通过了ASIL D的ISO 26262和TÜVRheinland的IEC 61508 SIL3认证,TÜVRheinland是安全和质量持续发展的国际领导者。
1.安全认证
ISO 26262是功能安全的国际标准,正在被汽车行业广泛采用,作为系统,硬件和软件级别的车辆功能安全的最新定义。构建符合ISO 26262的汽车系统是一项重要任务。为了帮助降低不合规风险并降低开发和认证成本,BlackBerry QNX提供了可靠的RTOS基础,该基础经过ISO 26262最高级别的预认证 - ASIL D.使用QNX OS作为安全基础可以大大提高在构建具有安全关键要求的系统时,减轻汽车制造商和一级供应商的认证负担。
一般的嵌入式行业也正在经历对功能安全的不断增长的需求。随着系统设计变得越来越复杂,IEC 61508的高SIL级别是包括工业自动化,高速列车控制和能源生成系统在内的应用的关键要求,其中预先认证的RTOS通常是先决条件。
2.免于干扰
虽然功能安全要求强调系统具有确定性和可靠性,但其他竞争特性要求系统更具动态性,连接性和通用性。但是,重要的是混合关键性的这些要求不会发生冲突。
QNX OS for Safety基于QNX Neutrino RTOS,它具有微内核架构,能够在应用级别在空间和时间上分离多个域。这显着简化了确保在混合关键性系统中不受干扰的任务。有时这种系统具有混合关键性,这意味着这些系统结合了安全关键功能和非安全关键功能。通过充分分离安全关键域和非安全关键域,可以大大简化设计。更简单的设计还可以实现更简单的安全案例,从整体上讲,转换为较低的认证工作。
3.合格的工具链
ISO 26262和IEC 61508不仅要求构成系统的硬件和软件,还要求对用于创建系统的工具进行适当的鉴定。工具根据其对工作产品安全性的影响分为不同类别,范围从工具置信度(TCL或T)1到3,其中3为最高级别。了解工具链正确性的重要性,QNX OS for Safety包括C和C ++工具链到3级的认证。用于ARM和x86架构的C和C ++编译器,链接器和汇编器对于正确生成软件至关重要这将在汽车的微处理器上运行。通过承担这些工具链的资格,
4.API兼容性
QNX OS for Safety与BlackBerry QNX的标准RTOS版本完全API兼容。QNX OS for Safety 2.0与QNX软件开发平台7.0兼容。熟悉标准RTOS的开发人员在使用安全认证产品时不需要加速时间,并且可以使用相同的QNX Momentics工具套件开发环境来开发安全关键系统。API兼容性不仅简化了开发团队的学习曲线,而且使客户可以利用一个通用平台来处理安全关键和非安全关键应用程序,从而最大限度地重复使用代码。
5.技术
QNX OS for Safety包括软件和文档,均经过ISO 26262 ASIL D和IEC 61508 SIL 3的预认证。
技术
适用于ADAS的QNX平台提供众多功能,包括:
- 四个摄像机环绕视图的参考实现,单摄像机ADAS,具有多摄像机输入的传感器集线器
- 低延迟传感器数据采集:支持摄像头,雷达,激光雷达,IMU,GPS传感器
- 发布和订阅传感器数据访问
- 数据可视化
- 网络插件,通过汽车网络提供传感器数据
- 使用带时间戳的数据样本捕获传感器数据。传感器数据回放保持定时保真度
- 可配置的时间戳源,例如IEEE 1588 PTP或IEEE 802.1AS
- 机器人操作系统(ROS)集成,用于测试和原型设计。使用ROS将数据导出到Matlabs等兼容工具
- 集成的开源库,包括OpenCV,SOME / IP,Ceres等
支持的传感器
相机
- Point Grey USB 3.0相机
- Omnivision OV10640
- Omnivision OV10635
- Aptina AR0132 Enyo
- GigE视觉相机
- ONVIF Profile S相机
雷达
- 德尔福ESR
- 德尔福SRR2
激光雷达
- Velodyne VLP-16
- Velodyne VLP-16 HiRes
- Leddartech VU8
GPS / IMU
- Xsens MTI G-710
- Novatel GPS和IMU
处理器和参考板
QNX instrument clusters
QNX仪器集群平台旨在帮助汽车制造商通过开发全数字仪表板来应对新的挑战。面向仪表板的QNX平台基于BlackBerry QNX针对汽车仪表板参考硬件的高度优化的基于OpenGL的图形框架构建,并由领先的集群UI框架提供支持。这种集成提供了必要的功能,使汽车制造商能够构建具有惊人视觉效果的数字仪表板,同时满足其上市时间要求。更重要的是,QNX仪器集群平台还提供ISO 26262 ASIL B预认证图形监视器子系统。配合BlackBerry QNX的ISO 26262 ASIL D预认证RTOS和工具链。
安全认证的成本和风险降低
虽然当前仪表板的数字特性能够实现引人注目的动态UI,但它也为汽车制造商带来了新的挑战,即功能安全性。在传统集群中,诸如指示符号之类的关键信息由安全级LED照亮,但对于现代仪表板,此信息现在可以在软件中呈现。事实上,软件在汽车中日益增长的重要性远远超出了仪表板群到许多车辆子系统。认识到这一现实,业界已采用ISO 26262功能安全标准,以确保车载电子系统的安全谱系。今天,ISO 26262 ASIL B要求正在应用于数字仪表组中的指标,信号和档位。满足这些要求可能会大大增加范围,成本,和集群生产计划的持续时间。QNX仪器集群平台提供ISO 26262 ASIL B预认证图形解决方案。这种基于软件的解决方案提供了一个图形监视器子系统,用于验证显示的符号是否符合预期,符合客户可以指定的程度,并且对系统性能的影响最小,以确保UI的平滑性。图形监视器子系统利用“安全袋”的概念(由铁路安全标准EN 50128创造),这提供了一种确保复杂系统功能安全的有效方法。再加上黑莓QNX的ISO 26262 ASIL D预认证RTOS和工具链,集群制造商将发现支持混合关键性组件之间不受干扰,这对具有这些特征的系统的成功至关重要。QNX仪器集群平台还提供关键认证文物,如危害和风险分析以及安全案例,以帮助集群制造商进行系统级认证。就像ISO 26262 ASIL D预认证的RTOS一样,ISO 26262 ASIL B的图形框架认证由世界领先的安全审计机构之一TÜVRheinland执行。
高性能图形框架,以提供引人注目的UI
随着仪表组逐渐完全数字化,集群的动态和引人注目的特性成为汽车制造商的主要竞争优势。驾驶员期望在他们的驾驶舱显示器中生动,流畅地显示信息和响应式用户界面,这在当今的智能手机中很常见。汽车制造商需要强大的工具和组件,以充分利用新的自由选择信息的内容和风格,以显示在驾驶舱的不动产中。QNX的仪器群平台基于BlackBerry QNX的硬件加速和高度优化的基于OpenGL的UI框架,屏幕,并集成在流行的群集硬件上。BlackBerry QNX的重要产品,Screen Graphics Subsystem基于最新的OpenGL标准,已部署在数百万辆汽车中,支持信息娱乐和集群显示,具有令人惊叹的视觉效果和用户友好性。它是一个完整的UI框架,可与GPU(图形处理单元),显示器和触摸屏等硬件交互,以呈现,组合,处理和管理各种图形内容以及用户输入,从而提供一流的用户体验。它已经部署在数百万具有用户界面的车载系统中,例如信息娱乐系统,仪表盘和驾驶员信息显示器。显示和触摸屏,用于渲染,撰写,处理和管理各种图形内容以及用户输入,以提供一流的用户体验。它已经部署在数百万具有用户界面的车载系统中,例如信息娱乐系统,仪表盘和驾驶员信息显示器。显示和触摸屏,用于渲染,撰写,处理和管理各种图形内容以及用户输入,以提供一流的用户体验。它已经部署在数百万具有用户界面的车载系统中,例如信息娱乐系统,仪表盘和驾驶员信息显示器。
通过与硬件和集群UI框架预集成,缩短产品上市时间
随着汽车制造商的竞争加剧,上市时间压力推动了缩短的生产计划时间表。数字仪表板集成了各种组件,包括硬件(例如微处理器和数字显示器)和软件(例如板支持包,操作系统,图形框架,集群UI组件和其他软件) - 通常来自多个供应商。为了无缝集成所有硬件和软件组件并提供具有卓越性能的成品,集群制造商需要尽可能利用预集成。QNX仪器集群平台通过预先集成硬件(SOC),电路板支持包,帮助制造商满足上市时间要求 ,可靠的RTOS和高性能图形中间件,以及来自Crank Software,Disti,ElektroBit和Rightware的领先集群UI框架。这种预集成水平可以显着减少集群制造商的项目进度和成本。
技术
QNX仪器集群平台构建于基于QNX OpenGL的图形框架之上,在流行的集群硬件上进行了优化,并由领先的集群UI框架提供支持。QNX仪器集群平台使汽车制造商能够构建具有惊人视觉效果的数字仪表板,同时满足上市时间要求。更重要的是,QNX仪器集群平台还提供ISO 26262 ASIL B预认证图形解决方案。QNX仪器集群平台与BlackBerry QNX的ISO 26262 ASIL D预认证RTOS相结合,旨在解决对集群中关键信息(如指示器和档位)的功能安全要求。
产品包装
QNX仪器集群平台包括软件和文档。
软件
- QNX图形监视器子系统
- 适用于流行群集硬件的Board Support Package
- QNX OpenGL参考集群(源代码中)
- 来自领先集群UI框架供应商的示例集群应用程序(仅限二进制)
文档
- 德国莱茵TÜV颁发的QNX图形安全认证ISO 26262 ASIL B证书
- QNX安全图形安全手册
- 安装和使用指南
- QNX图形安全的危险和风险分析
- QNX图形安全的安全案例
- 发行说明
硬件支持
流行的x86和ARM集群硬件支持QNX仪器集群平台。该产品的2.0版包括对英特尔GordonRidge参考板的完全支持。QNX仪器集群平台可以通过BlackBerry QNX经验丰富的专业人员和安全专家团队进行调整,以支持客户选择的硬件。
QNX Hypervisor system
QNX Hypervisor是基于类型1实时优先级的微内核管理程序,用于管理虚拟机。在QNX ® 虚拟机管理程序可以更容易地获得并通过从不同的客户机操作系统的非安全关键组分分离安全关键部件保持安全认证。QNX Hypervisor能够满足嵌入式零停机生产系统的精度要求。
基于标准的访客通信(virtIO)和灵活的虚拟机配置确保了虚拟机管理程序环境可以扩展到大型服务器级设计(根据自动驱动器和高端计算系统的要求)。此外,虚拟机管理程序环境可以扩展到深度嵌入式系统(集群+信息娱乐汽车系统,ECU整合,医疗设备,工业控制)。QNX Hypervisor实现为经过业界验证的基于QNX Neutrino微内核的RTOS的虚拟化扩展; 继承QNX操作系统的所有实时性和稳定性,该操作系统已经在全球数百万个嵌入式系统中提供。
- Type 1 Hypervisor
- 安全认证谱系
- 虚拟CPU模型
- 根据优先级固定核心或共享核心
- 自适应分区 - 允许客户运行时的CPU保证
- 64位和32位客户:QNX,Linux,Android,RTOS
- 共享内存与触发
- VirtIO(0.95 / 1.0)设备共享
- TAP和具有桥接功能的点对点网络
- 故障检测并重新启动客人
- 用于访客完整性检查的虚拟监视器
- 低开销(典型<2%)
- 用于分析和调试的图形工具
安全关键隔离
关键流程的保护始于操作系统。凭借其微内核架构,QNX软件系统的OS技术在市场上已经将关键功能彼此之间以及非关键功能完全隔离和分离超过35年。QNX Hypervisor的架构旨在支持QNX OS等操作系统客户机,这些客户机非常适合安全关键型应用,同时还支持那些旨在提供不太重要但仍然令人满意的功能的设备,如图形人机界面(HMI),设备管理,或物联网或远程设备管理的云连接。
保护安全认证
QNX Hypervisor可以确定性地将安全关键应用程序和实时操作系统与非关键应用程序和操作系统隔离开来。
对于设备制造商而言,这意味着他们可以更轻松,更经济地获取和维护安全认证,仅适用于需要它们的系统组件 - 重新测试和重新认证的工作范围可以缩小到只有已更改的元素,移植时到虚拟化平台。
QNX Hypervisor的开发符合IEC 61508 SIL-3(用于工业安全),IEC 62304(用于医疗设备软件)和ISO 26262 ASIL-D(用于汽车安全)等标准。
空间分离和隔离
QNX Hypervisor使用硬件中的配置来强制虚拟机之间的内存和CPU核心隔离。这种方法比在软件中这样做更有效,从而提高了性能和可靠性。
时间分离和隔离
QNX Hypervisor实现并实施虚拟机优先级,确保在高优先级虚拟机中运行的实时进程获得对cpu计算资源的必要访问,无论优先级较低的虚拟机执行的繁忙程度如何。
这种方法比依赖Round Robin或FCFS调度算法更有效,并且可以提高性能和可靠性。在多核系统中,不同的要求需要对各个CPU核心进行不同的分配。QNX Hypervisor使用优先级驱动的虚拟CPU(vCPU)概念,允许系统设计人员将虚拟机限制为一个或多个核心,或者在多个虚拟机之间共享核心。这可确保安全性和实时行为,同时仍能实现最高的系统性能。没有浪费CPU带宽。
网络分离和隔离
QNX Hypervisor支持分配给虚拟机的多个NIC和VLAN,允许在安全关键过程和网络连接过程之间实现完全的网络隔离。
设备共享
QNX Hypervisor支持简单的设备共享模型,该模型利用已经是Linux和Android发行版的一部分的共享设备驱动程序,大大减少了开发和测试工作量。
直接设备分配
QNX Hypervisor还支持将设备直接映射到特定虚拟机的地址空间,从而允许该VM使用其操作系统本机的设备驱动程序。该机制还允许直接中断传递,避免了完全管理程序控制的开销。
QNX资源管理器
为了使微内核提供 POSIX 标准和 UNIX 惯例所定义的功能,可以增加被称为资源管理器的可选的进程。可以从微内核建立一个最小化的系统(比如没有文件系统和设备输入输出系统),包括一个进程管理器,和一系列的应用程序进程。
在 QNX 中第一个和唯一必须的资源管理器是进程管理器(Proc),它提供进程创建,进程记帐,内存管理,进程环境继承(对本地和网络远程进程都是),路径名空间管理。第一级路径名由 Proc 管理的原因是在 QNX中文件系统是可选择的,而不象单体内核系统中文件系统总是存在。无盘和基于ROM 的系统可能不使用文件系统,所以不强制必须包括文件系统。
在其他资源管理器执行之前,Proc “拥有”整个的路径名空间(根目录和其下面的所有东西)。如果没有其他的资源管理器出来提供服务,这基本上是一个空的文件系统。Proc 允许其他资源管理器通过一个标准的 API来接受其想管理的那一部分名字空间(拥有认证域)。Proc 响应并维护一个前缀树(prefix tree)来跟踪这些拥有各种各样的部分名字空间的进程。
当一个文件系统管理器例如 Fsys(POSIX 文件系统管理器)和一个设备管理器例如 Dev 运行了,前缀树看起来可能象下面这样:
/ 基于磁盘的文件系统(Fsys)
/dev 字符设备系统 (Dev)
/dev/hd0 (Fsys)原始磁盘卷
/dev/null 空(Null)设备 (Dev)
(1)基于磁盘的文件系统(Fsys)
当一个进程打开一个文件,open() 库例程首先把文件名发送到 Proc , Proc 会拿前缀树和路径名做比较,把open() 导向合适的资源管理器。在资源管理器的认证域有部分重叠的情况下,将选取路径名的最长的匹配。例如打开 /dev/tty0 最长的匹配发生在 /dev,导致打开指向 Dev 。路径名 /usr/fred 与 / 匹配 ,打开指向 Fsys。
在网络上的每个计算机的进程管理器维护自身的前缀树,在每个节点上的进程可能看到的“网络范围路径名空间”可以是一致的也可以是不同的。以 / 开始的路径名的使用基于本节点的前缀树。也可得到网络上唯一的名字,这样就允许应用程序用此名字指定资源在网络范围路径名空间中的绝对位置。通过使用前缀别名( Proc 管理的在内存中的重映射表),部分名字空间可以映射到网络上其他节点的资源管理器上。例如,一个从 LAN 上引导的无盘工作站希望拥有自己的以其他节点为根的文件系统,它就可以把自己的文件系统的根定义为一个远程的 Fsys 进程的别名。
当上面的别名存在时,对 /dev 的 open() 调用仍然被映射到本地的 Dev 进程来控制本地设备,但所有对文件的 open() 调用被转成一个 open 消息,此消息将被前面指定的远程节点的前缀映射表解析(通常把文件打开指向那个节点的 Fsys 进程)。通过连接到一个通常的根目录,在网络上任何地方的进程可以在一个单一的目录树中访问所有的网络文件系统。可替代的选择是,通过使用“网络绝对路径名”,网络路径名空间也可以作为一系列独立的根文件系统进行维护。
通过在传统的文件名空间上实现单独的认证域,OS 的总体功能中的很多部分可以用运行时可选的方式实现。既然资源管理器进程在内核空间外存在,那么就在运行时被动态的增加和删除,而不需要用重新连接内核来包含不同层次的功能。这种在大小上的灵活性允许 OS 易于按应用程序的需要而放大或缩小。
尽管对把资源管理器提供的服务放置在内核之外的第一印象是低效,但性能结果(参见附录)表明环境切换和 IPC 的性能充分的保持了硬件的原始性能。事实上,QNX 的快速环境切换允许 OS 的功能在协作进程间分配而不为此导致性能损失。这样就明确了这种结构在体系上的优势。
这种分布式名字空间的网络透明性允许远程运行的进程和本地处理器上运行的进程在逻辑上等价。独立管理的路径名空间实现了无缝的混合,也就是对名字空间的行为没有什么“意想不到”的。继承整个父进程的环境,包括打开的文件描述符,环境变量,当前工作路径。即使在分布式网络环境下,进程间通信和 I/O 操作也与 POSIX 1003.1 定义相一致。
1.Fsys - 文件系统管理
Fsys 是实现文件系统的多种资源管理器中的一种。Fsys 为 QNX 环境提供了一个高性能的 POSIX 兼容的文件系统。它实现了一个磁盘结构,用位图分配空闲空间,实现了延伸块(extent)的链表用来组织磁盘上的数据。这种实现允许系统提供给应用程序层次的的磁盘吞吐率接近硬件的原始能力(参见附录B)。Fsys 对保持文件系统完整性很关键的数据结构实行同步的写磁盘操作,这允许磁盘系统可以在意外的电源失效中幸存下来,在在磁盘上的数据结构中嵌入了特殊标志,令文件系统在灾难性错误事件后也能被重建。
多线程的 Fsys 体系结构允许它处理并行的多路请求,例如在其他线程因为等待物理 I/O 发生而阻塞时,执行ramdisk 和 cache I/O 的线程可以运行。如果设备支持多路等待 I/O 请求,那么这些请求可以由驱动来服务,以驱动认为合适的顺序执行.那么这种并行性也可以延伸到驱动程序,提交的要求可以按任何适当的次序得到服务。进一步,通过 Fsys 层的文件 I/O 请求按延伸块编码,而不是按单独的磁盘块编码。这导致对磁盘驱动器所表达的工作单元自然的映射到磁盘驱动器指定喜欢的 I/O 模式上。结果是得到了现代磁盘驱动器的顺序读写的 I/O 速率,和实质的文件系统性能提高。
从第一眼的印象上,一个消息传递的 OS 中的文件系统应该比单体内核的文件系统需要更多的数据复制。事实上不需要额外的复制。MX 的多块消息基本操作允许 Fsys 把应用在 read() 和 write() 调用中指定的连续的缓冲区映射成在 Fsys 中的非连续的 cache 块。对于一次读磁盘,磁盘驱动程序从磁盘读到多路非连续的 cache 中。 Fsys 接着调用在内核中的 MX 功能自动地把散开的块聚集和复制到应用程序指定的连续的读缓冲区中。结果是,即使在一个消息传递、网络透明的环境内的文件系统操作,也展示出与在单体内核中实现的高速缓存(cache)的文件系统同样数量的数据复制。
在一个无盘的网络连接的机器上,Fsys 进程可以用命令行启动。设备驱动程序也动态的挂接在 Fsys 上,在Fsys 将不再使用的情况下,Fsys 和它的设备驱动程序可以从内存中除去。
注意可得到许多文件系统管理器。例如包括 Dosfsys 来访问 DOS格式化的介质,Iso9660fsys 用于 CDROM,一些嵌入式文件系统用于 Flash-memory 设备,等等。作为运行时应用程序的需要,任何文件系统进程的组合均可以并发的运行。
(2)Dev - 设备管理
设备管理(Dev) 提供了 POSIX 兼容的设备控制而且还带有一些适于实现通信协议的实时扩展。用与 Fsys 类似的方式,Dev进程可以动态的启动和与设备驱动程序挂接上并且如果不再被使用可以从内存中除去。
即使在有非智能 UART 设备的现代硬件上,Dev 可以处理的波特率也高达 115 Kbaud,原因是微内核提供了低时延中断。通过增加智能通信板卡,可以轻易的配置一个高带宽的多线路通信服务器。
有赖于 MX 基本操作,Dev 可以 Receive() 一个应用程序对一个设备作的 write() 直接到一个中断处理程序管理的环状缓冲区中。通过适当的定义 MX 表,接收的数据可以放置在 Dev 管理的环状缓冲区当中。因为写到环状缓冲区可以要求数据被映射到物理上不连贯的(逻辑上连续的)内存区域中,一个有三个表项的 MX 表可以描述环状缓冲区中一个头部和两物理上不连贯的区段。对于 read() 的情况,从设备来的数据流从驱动程序直接进入环状缓冲区,从环状缓冲区直接进入应用程序的 read() 缓冲区,而没用于建立连续的消息的冗余复制。
(3)设备驱动程序支持
不要求设备驱动中断处理程序只能存在于内核空间中,QNX 提供了一个系统调用,用来允许用户进程去连接一个在有足够的权限的用户进程内的中断处理程序,此中断处理程序相应于处理内核中的某个中断向量。连接的这个中断处理程序就可以被内核调用来响应物理中断。通过在用户进程中存在,处理程序可以完全的访问这个有响应中断功能的进程的地址空间。一旦中断处理程序开始运行,它要么可以唤醒与它共享代码的进程要么是简单的返回内核。用于 Dev 的设备驱动程序从下面的行为中获利,使用单独的中断在Dev 管理的缓冲区中积累字符,只在事先定义的"重大事件"发生时唤醒 Dev (例如终端字符计数、行结束条件、或超时)。
通过中断处理程序驻留在内核之外的方式,用户可以在一个运行的系统中动态的增加或删除中断处理程序 (和包含他们的设备驱动程序)。 由内核处理的第一层次的中断处理也照顾了嵌套和共享的中断,而不强加硬件依赖的细节和用户写中断处理程序的复杂性。微内核提供外部的中断处理程序是允许一个资源管理器与单体内核可提供的的性能水平相匹配的基础。
因为设备驱动程序在用户级进程中存在,开发者可以从这个基本的优势中获利:开发 OS 的功能的扩展与开发用户级进程相一致。事实上,在 QNX Software Systems 内部使用的开发工具是运行在全屏幕、源代码级的调试器下的实验性的资源管理器,所以调试象一个新的 Fsys 进程这样的 OS 服务可以免去调试内核这样复杂的过程就能完成。并且因为资源管理器和设备驱动程序可以在需要的时候启动或去除,重新连接内核和重新启动来测试新内核的费力的过程就变的不在是必须的了。
作为 QNX 操作系统易于扩展的例子,象类似于在 [Pike 90]中描述的 /proc 资源管理器这样的服务可以被应用程序级的程序员(而不是内核设计师!)只用几个小时的努力和不到200行的易懂的 C 源程序实现。/proc 有效的把系统资源(一系列的在系统中的活跃的进程)包装起来,并且对于系统把它们表现得就象是在 /proc 的路径名空间中维护的文件和目录。
一个复杂一些的例子,实现了一个类似于在 [Presotto 91] 中描述的客户端网络文件系统的 cache 管理器。这个 cache 把某节点对网络上的远程 Fsys 最近访问的文件块保存在客户端复本中。在 open() 的时候,cache 验证了远程的文件未被更改(更改将使本地的缓存的数据失效)之后提供本地缓存的数据,这样来增强性能。通过提供一个容纳 cache “溢出”的文件系统,一个以慢速串行线路连网的系统仍可以提供合理的远程网络文件系统性能。这样一个服务器只用了1000 行源代码。再者,这是完全在应用程序员的范围内使用标准系统库实现的。
最后,客户的文件系统可以作为使用 Fsys 的裸磁盘块服务的资源管理器来实现,此客户的文件系统可表示为在根文件系统上的子树。一个例子是 PC-DOS 的文件系统 Dosfsys, Dosfsys 接受 /dos 路径名作为认证域并提供目录形式如 /dos/a, /dos/b 等等。这些目录映射到相应的 PC-DOS 介质上,并且Dosfsys 维护进入Dosfsys 认证域的 I/O 请求指示出的在此卷上的裸块。文件的维护可以被映射到所支持的下层文件系统上,而尝试其他的比如 link() 的时候则返回适当的出错信息。
(4)网络服务 - FLEETTM 网络技术
- Fault-tolerant 容错
- Load-balancing 平衡负载
- Efficient 高效
- Extensible 可扩展性
- Transparent 透明性
像前面曾提到的那样,网络管理器 (Net) 被直接连接进了内核。当一个本地进程调用微内核向其他节点上的进程传送消息的时候,微内核通过这个专用接口把一个指向消息的指针加入一个用于 Net 的队列中。 Net 进程也可以从其他微内核接受消息并把这些消息传给本地微内核。实质上,在网络上的网络管理器把所有局域网上的微内核融合成一个单一的逻辑上的微内核。因为所有的系统服务包括进程创建、调试、文件和设备 I/O 都凭借通过微内核消息传送完成,结果是在网络上的多个计算机的行为就象是一个单一计算机。局域网上的任何进程提供的任何服务均可被网络上的任何进程透明的访问。这是与 TCP/IP 的标志性的差异,TCP/IP 只是明显的提供了一系列服务--典型的终端任务和文件 I/O。作为对比,这个连接成的微内核体系结构允许下面的命令:
ls /usr/danh | grep abc | wc
以每个进程在网络上不同的处理器上运行的方式执行,而 Proc 提供的网络继承的文件描述符导致管道可以在网络上连接和转送数据。这种环境的透明性也给实现分布式应用提供了方便。一个由一系列的协作进程组成的应用可以在一个单一的 CPU 上开发,并在网络(LAN)上分布而不需要更改源代码或二进制代码。
就象 Fsys 和 Dev 可以从命令行启动和停止、它们各自都有一族驱动程序一样,Net 也有一族驱动程序并支持连接多种网络驱动程序。如果 Net 发现在一个节点上连接了多于一个网络驱动器,它在将驱动器之间进行平衡流量负载。这里的平衡负载使用基于介质传输速率和队列深度的算法。可得到用于按用户意愿手动强制网络流量的命令行选项。
在节点间的多条网络路径提供了更好的吞吐量和容错能力。应用程序级的变化不需要用到容错能力,因为容错支持已经存在于本地的网络管理器中了。例如,原子能反应堆监视系统就是用这种技术成功的实现的。
作为另一种途径,廉价的串行链路可被用于在主局域网出错时的后备网络链路。通过做数据压缩、启用客户方的文件系统高速缓存等方法使串行链路以高波特率运行,串行网络的性能可以很快。
当在局域网上的两个文件服务器进行点到点的高流量传输的时候经常导致局域网的拥塞问题,这样的设施也可以解决这种问题。通过 FLEET 的方式,加入一条专有的链路来连接两个服务器,把流量从主局域网上移到专有链路上。如果两服务器在物理上离的很近,那么象点到点的 SCSI 或总线到总线的 DMA 这样的非常规的局域网技术就成为可行的选择。这种方法可用于实现 CPU/文件服务器组[Presotto 91]。
通过制造使用 backplane (背板)总线的处理器板, FLEET 方式也允许用一个系统的 backplane 总线把多处理器系统构造为一个 VLAN (超局域网)。每个处理器板运行一个包含微内核、Proc、 Net、和 Net.vlan 驱动程序的 QNX OS,其中某个处理器可以同时使用 Net.vlan 驱动程序和 Net.ethernet 驱动程序,而 Net.ethernet 是用来访问一个外部的以太局域网的。通过为这些处理器增加额外的硬件,和适当的 Dev 或 Fsys 进程,这些处理器能变成分布式 I/O 处理器。每个在以太网上的节点可以有效的包含在一个机架上的增补处理器的 VLAN。这就使在每个以太网节点上的运行的一队处理器在这个节点内重新分布并以一个处理器队伍的方式运行。 Ziatech Inc (http://www.ziatech.com/)完成这种技术的一个实现。[Tanenbaum 89]描述的计算服务器也可以用这种硬件很容易的实现。
对于嵌入式应用,一个最小化的 QNX 系统可以被放入小于 256K 的 ROM 中(微内核、Proc 和一些应用程序)。增加一个 Net 进程和一个 Net 驱动程序( 大约 70K)之后,嵌入式系统就可以连接到一个大的网络上,变成一个大局域网的一个无缝的的扩展。这就允许嵌入式系统访问数据库、图形用户界面、局域网网关、和其他的服务。不受嵌入式系统有限的功能的限制,网络链路力图要使局域网提供给在嵌式系统上运行的进程的整个局域网资源的访问。嵌入式系统也可以从网络上引导,进一步的减少对 ROM 的要求。因为系统调试服务是通过运行被调试应用程序所在节点上的 Proc 进程的标准消息实现的,所以可以从在局域网上的其他节点上调试嵌入式系统上的应用程序。
对于主机传输层协议, Net 提供 一个“Clarkson兼容”的原始分组递送服务并可得到网络驱动器。通过以这种方式实现的协议栈,在同一个物理的局域网上的非QNX的机器可以通过协议栈的通信访问多处理器QNX 局域网。在局域网上的 QNX 节点对与外部世界(例如 TCP/IP)表现的就是一个单一的多处理器机器。
FLEET 协议的实现也支持网络桥接,允许分组在通过中介节点转发来把多个 QNX 局域网结合成一个局域网。因为 QNX 在所有基于 IEEE 802的网络上使用同样的分组格式和协议,你可以生成在以太网,令牌环、FDDI 局域网间的网桥。
1.可维护性
维护一个单体内核的 OS 的基本问题是所有内核代码在一个公共的、共享的地址空间运行。内核的任何一部分都能破坏其他部分的数据空间的危险是非常现实的和每当连接新驱动程序到内核时必须考虑的。QNX 使用的方式是显式的定义组成 OS 的各部分之间的接口,每个资源管理器,就象每个用户进程一样,在自己的受保护的内存空间运行,所有 OS 模块间的通信通过标准的系统 IPC 服务完成。结果是,任何一个资源管理器产生的错误将被约束在自己的子系统中而不破坏其他的在系统中与其不相关的资源管理器。
因为新的资源管理器和设备驱动程序可以使用与用户进程同样的工具调试和调整,系统开发与应用开发使用同样的手段。这是非常重要的,因为这样就允许通过新方式用更自由的实验来实现 OS 子系统,而不用招致使用有限的工具调试内核带来的巨大工作量。
另一个要点是因为组成 OS 的模块在分开的、受 MMU 保护的地址空间运行,并且这些模块在不同的系统上是以"一致的二进制"方式运行。当在一个新系统配置下使用一个模块的时候,就允许直接适用以前的运行经验和对一个 OS 模块(设备驱动程序、文件系统管理器、内部开发的服务器进程等)获得的可靠的质量控制。相比之下,单体内核要重新连接内核来包含一个可选的 OS 模块,结果是对于不同的配置有完全不同二进制形式。这导致了对过去的 OS 服务的相关的运行经验在新的使用当中的作用的相应减少。因为二进制形式的变更,与迷途的指针错误相连在一起的“运气”和其他编码问题也变更了。曾经是一个不产生任何系统错误的和善的内存重写,在重新连接内核之后就可能导致系统崩溃。
这个问题也适用于嵌入式实时执行者和不实现内核保护的内核中,当增加开发员工的时候,一个开发者的编程错误可以导致系统的行为不容易被作者跟踪,结果是非常费时的调试任务。通过在进程间的内存保护,一个开发者的错误的在 OS 中的直接结果是终止有错误的进程(迷途的指针引用和超出边界的数组访问)。避免了"Finger pointing"并且查找问题的原因可得到立即的回报。当嵌入式系统的开发队伍变得非常大的时候,质量控制与进程模块重用的结合,联合上在调试努力期间增加的生产率,提出一个选择内存保护的微内核操作系统的有说服力的论据。没有进程间的内存保护的大队伍的嵌入式系统开发事实上是难以想象的。
2.性能
QNX 必然要面对的挑战是使用者的实时应用对性能的需求。尽管一个幽雅的 OS 体系结构是很有意趣的,但学术上的优美不一定带来在商业上成功的 OS ,因为它在性能上要超过传统的单体内核的系统。许多微内核系统的一个设计目标就是与单体内核的系统在性能上相匹敌 [Guillemont 91]。但是仅仅在性能水平上可匹敌将不能提供给 OS 使用者以使用基于微内核系统的在技术上的显著利益。
QNX 把完全的硬件性能提供给应用程序层(超出了单体内核系统的性能),开发出许多体系结构上的变革。这些增强的前提是不能对实时系统的性能进行让步。即使最初的研究指出增加的消息传送的模型的共性是要比单体内核有更大的开销,这里有一些结构上的想法用来更正这些误解。两个对全面的系统性能有显著贡献的概念是在资源管理器内直接支持中断处理,和多部分消息原语。从以性能为目标中解放出来,就可以凭借内核体系结构而致力于有更大的意义的系统健壮性和对管理更大编程队伍的努力。
QNX进程、线程、通信与调度策略
QNX进程与线程
(1)QNX Neutrino RTOS的实现
QNXNeutrino®实时操作系统(RTOS)是一款功能齐全且功能强大的RTOS,旨在为汽车,医疗,运输,军事和工业嵌入式系统提供下一代产品。微内核设计和模块化架构使客户能够以较低的总体拥有成本创建高度优化和可靠的系统。借助QNXNeutrino®RTOS,嵌入式系统设计人员可以基于高度可靠的RTOS软件创建引人注目,安全且安全的设备,作为防止系统故障,恶意软件和网络安全漏洞的基础。
从历史上看,QNX的软件系统的"应用压力"是由内存有限的嵌入式系统从内存有限的嵌入式系统中得到的,一直到高端的SMP(对称多处理器)计算机,有千兆字节的物理内存。因此,QNX中微子的设计目标同时适用于这两种看似唯一的功能集。追求这些目标的目的是扩展系统的范围,远远超出其他操作系统实现所能解决的范围。
实际上,任何组件如果失败都可以自动重启,因为每个驱动程序,协议栈,文件系统和应用程序都在受内存保护的用户空间的安全下运行。
(2)POSIX实时和线程扩展
由于QNX Neutrino RTOS直接在微内核中实现了大部分的实时和线程服务,所以即使没有额外的OS模块,这些服务也是可用的。此外,POSIX定义的一些概要文件表明,这些服务在不需要进程模型的情况下存在。为了适应这一点,操作系统提供了对线程的直接支持,但是依赖于它的进程管理器部分将此功能扩展到包含多个线程的进程。注意,许多实时管理系统和内核只提供一个非内存保护的线程模型,根本没有进程模型和/或受保护的内存模型。没有进程模型,就无法实现完全POSIX遵从性。
(3)系统服务
微内核有内核调用来支持以下内容:
•线程
•消息传递
•信号
•时钟
•定时器
•中断处理程序
•信号量
•互斥锁(互斥锁)
•条件变量(condvars)
•隔离
整个操作系统是建立在这些调用之上的。操作系统是完全可抢占的,即使在进程之间传递消息;它恢复消息传递,然后在抢占之前停止。
微内核的最小复杂度有助于在经过内核的最长不可抢占的代码路径上设置上限,而较小的代码大小使解决复杂的多处理器问题成为一个易于处理的问题。选择服务作为包含在微内核中的基础,因为服务的执行路径很短。需要大量工作的操作(例如,进程加载)被分配给外部进程/线程,与线程中为服务请求所做的工作相比,进入该线程上下文的工作是无关紧要的。
严格地应用这个规则来划分内核和外部进程之间的功能,这破坏了一个神话,即一个微内核操作系统必须比一个单一的内核操作系统带来更高的运行时开销。考虑到上下文切换(隐含在消息传递中)的工作,以及从简化的内核中获得的快速上下文切换的时间,所花的时间运行的上下文切换就变成了为服务的工作所做的工作所做的工作的“丢失”的过程中传递的信息传递的过程中所传递的信息传递的过程中所传递的信息。
中断是被禁用的,或者抢占是被延迟的,只有非常短的间隔(通常是几百纳秒的顺序)
(4)线程和进程
在构建应用程序时(实时、嵌入式、图形化或其他),开发人员可能希望应用程序中的几种算法并发执行。这种并发性是通过使用POSIX线程模型实现的,该模型将进程定义为包含一个或多个执行线程。
线程可以被认为是最小的“执行单元”,即微内核中的调度和执行单元。另一方面,进程可以被认为是线程的“容器”,定义线程执行的“地址空间”。进程将始终包含至少一个线程。
根据应用程序的性质,线程可能独立执行,而不需要在算法之间通信(不太可能),或者它们可能需要紧密耦合,具有高带宽通信和紧密同步。为了帮助这种通信和同步,QNX中微子RTOS提供了丰富的IPC和同步服务。
下面的pthread_* (POSIX线程)库调用不包含任何微内核线程调用:
• pthread_attr_destroy()
• pthread_attr_getdetachstate()
• pthread_attr_getinheritsched()
• pthread_attr_getschedparam()
• pthread_attr_getschedpolicy()
• pthread_attr_getscope()
• pthread_attr_getstackaddr()
• pthread_attr_getstacksize()
• pthread_attr_init()
• pthread_attr_setdetachstate()
• pthread_attr_setinheritsched()
• pthread_attr_setschedparam()
• pthread_attr_setschedpolicy()
• pthread_attr_setscope()
• pthread_attr_setstackaddr()
• pthread_attr_setstacksize()
• pthread_cleanup_pop()
• pthread_cleanup_push()
• pthread_equal()
• pthread_getspecific()
• pthread_setspecific()
• pthread_key_create()
• pthread_key_delete()
• pthread_self()
下表列出了POSIX线程调用,它有一个相应的微内核线程调用,允许您选择其中一个接口:
可以将操作系统配置为提供线程和进程的混合(由POSIX定义)。每个进程都受到mmu的保护,每个进程可能包含一个或多个共享进程地址空间的线程。
线程属性
尽管进程中的线程共享进程地址空间中的所有内容,但每个线程仍然拥有一些“私有”数据。在某些情况下,这些私有数据在内核中受到保护(例如tid或线程ID),而其他私有数据则不受保护在进程的地址空间中(例如,每个线程都有自己的堆栈)。一些更值得注意的线程私有资源有:
tid: 每个线程由整数线程ID标识,从1开始。tid是线程进程中唯一的。
Priority: 每个线程都有一个优先级,帮助确定它什么时候运行。线程从父线程继承其初始优先级,但是优先级可以改变,这取决于调度策略、线程进行的显式更改或发送给线程的消息。
Name: 从QNX中微子核心OS 6.3.2开始,可以为指定一个名称线程;请参阅QNX中微子C库引用中的pthread_getname_np()和pthread_setname_np()条目。像dumper和pidin这样的实用程序支持线程名。线程名称是一个QNX中微子扩展。
Register set: 每个线程都有自己的指令指针(IP)、堆栈指针(SP)和其他处理器寄存器上下文。
Stack: 每个线程在它自己的堆栈上执行,存储在地址空间中它的过程。
Signal mask: 每个线程都有自己的信号掩码。
Thread local storage: 线程有一个系统定义的数据区域,称为“线程本地存储”(TLS)。TLS用于存储“每个线程”的信息(例如tid、pid、堆栈基数、errno和线程特定的键/数据绑定)。用户应用程序不需要直接访问TLS。线程可以拥有与特定于线程的数据键相关联的用户定义数据。
Cancellation handlers: 当线程终止时执行的回调函数。在pthread库中实现并存储在TLS中的特定于线程的数据提供了一种将进程全局整数键与每个线程的唯一数据值关联起来的机制。使用线程特定的数据,首先创建一个新键,然后将唯一的数据值绑定到键(每个线程)。例如,数据值可以是整数或指向动态分配的数据结构的指针。随后,键可以返回每个线程的绑定数据值。线程特定数据的典型应用程序是用于线程安全的函数,该函数需要为每个调用线程维护上下文。
线程的生命周期
进程中线程的数量可以有很大的变化,线程是动态创建和销毁的。线程创建(pthread_create())涉及到在进程的地址空间(例如线程堆栈)中分配和初始化必要的资源,并在地址空间中的某个函数上启动线程的执行。
线程终止(pthread_exit(), pthread_cancel())涉及到停止线程并回收线程的资源。当一个线程执行时,它的状态通常可以被描述为“就绪”或“阻塞”。“更具体地说,它可以是以下几项之一:
- CONDVAR: 线程被阻塞在一个条件变量上(例如,它被称为pthread_cond_wait())。
- DEAD: 线程已经终止,正在等待另一个线程的连接。
- INTERRUPT: 线程被阻塞,等待中断。,它叫做InterruptWait())。
- JOIN: 线程被阻塞,等待加入另一个线程(例如,它调用pthread_join())。
- MUTEX: 线程被阻塞在互斥锁上(例如,它调用pthread_mutex_lock())。
- NANOSLEEP: 线程睡眠的时间很短(例如,它被称为nanosleep())。
- NET_REPL Y: 线程正在等待通过网络(即,它叫MsgReply *())。
- NET_SEND: 线程正在等待通过网络传递脉冲或信号(即它称为MsgSendPulse()、MsgDeliverEvent()或SignalKill())。
- READY: 线程正在等待执行,而处理器正在执行另一个线程优先级相等或更高的线程。
- RECEIVE: 线程被阻塞在消息receive(例如,它称为MsgReceive())上。
- REPL Y: 线程在消息应答(即,它调用MsgSend()服务器收到消息)。
- RUNNING: 线程是由处理器执行的。内核使用一个数组(系统中每个处理器有一个条目)来跟踪正在运行的线程。
- SEM: 线程正在等待一个信号量被发布(例如。,它叫SyncSemWait())。
- SEND: 线程在消息发送时被阻塞(例如,它调用MsgSend(),但是服务器还没有收到消息)。
- SIGSUSPEND: 线程被阻塞,等待一个信号(即,它叫做sigsuspend())。
- SIGWAITINFO: 线程被阻塞,等待一个信号(即,它叫做sigwaitinfo())。
- STACK: 线程正在等待为线程的堆栈分配虚拟地址空间(父线程将调用ThreadCreate())。
- STOPPED:线程被阻塞,等待SIGCONT信号。
- WAITCTX: 线程正在等待一个非整数(例如浮点)上下文可用。
- WAITPAGE: 线程正在等待为虚拟地址分配物理内存。
- WAITTHREAD: 线程正在等待子线程完成自己的创建(即。,它叫ThreadCreate())。
(5)同步服务
QNX中微子RTOS提供了posix标准的线程级同步原语,其中一些在不同进程的线程之间是有用的。
同步服务至少包括以下内容:
互斥:互斥锁
互斥锁或互斥锁是最简单的同步服务。互斥用于确保对线程间共享的数据的独占访问。
互斥对象通常被获取(pthread_mutex_lock()或pthread_mutex_timedlock()),并在访问共享数据(通常是关键部分)的代码周围释放(pthread_mutex_unlock())。
在任何时候,只有一个线程可能锁定互斥锁。试图锁定已经锁定的互斥锁的线程将阻塞,直到拥有互斥锁的线程解锁为止。当线程解锁互斥锁时,等待锁定互斥锁的最高优先级线程将解除阻塞,成为互斥锁的新所有者。这样,线程将按优先级顺序通过一个关键区域。
在大多数处理器上,获取互斥锁并不需要进入内核以获得一个免费的互斥锁。允许这样做的是在x86处理器上使用比较和交换操作码,在大多数RISC处理器上使用load/store条件操作码。
只有当互斥锁已经被持有,线程才能进入阻塞列表时,才能进入内核;如果其他线程正在等待清除该互斥锁,则在退出时完成内核条目。这使得获取和释放一个没有争议的关键部分或资源变得非常快,只会引起操作系统的工作,从而解决争用问题。
可以使用非阻塞锁函数(pthread_mutex_trylock())来测试互斥锁当前是否被锁定。为了获得最佳性能,关键部分的执行时间应该很短,并且持续时间是有限的。如果线程可能在临界段内阻塞,则应使用条件变量。
优先级继承和互斥
默认情况下,如果优先级高于互斥锁所有者的线程试图锁定互斥锁,那么当前所有者的有效优先级将增加到等待互斥锁的高优先级阻塞线程的优先级。当前所有者的有效优先级在解锁互斥锁时再次被调整;它的新优先级是它自己的优先级的最大值,它仍然直接或间接阻止那些线程的优先级。
该方案不仅保证了高优先级线程在等待互斥锁的最短时间内被阻塞,而且解决了经典的优先级反转问题。
pthread_mutexattr_init()函数将协议设置为pthread_prio_inheritance,以允许这种行为;可以调用pthread_mutexattr_setprotocol()来覆盖此设置。pthread_mutex_trylock()函数不会改变线程的优先级,因为它不会阻塞线程。
还可以修改互斥对象的属性(使用pthread_mutexattr_settype()),以允许同一个线程递归锁定互斥对象。这对于允许线程调用一个可能试图锁定某个互斥锁的例程是很有用的,而这个互斥锁恰好已经锁定了。
条件变量
条件变量(condvar)用于阻塞临界段内的线程,直到满足某个条件为止。这个条件可以是任意复杂的,并且是独立于条件变量的。然而,为了实现监视器,条件变量必须始终与互斥锁一起使用。
条件变量支持三种操作:
•等(pthread_cond_wait())
•信号(pthread_cond_signal())
•广播(pthread_cond_broadcast())
下面是如何使用condvar的一个典型例子:
在这个代码示例中,在测试条件之前获取互斥量。这确保只有该线程能够访问正在检查的任意条件。当条件为真时,代码示例将阻塞等待调用,直到其他线程在条件变量上执行信号或广播。
需要while循环有两个原因。首先,POSIX不能保证不会发生错误唤醒(例如,多处理器系统)。其次,当另一个线程对条件进行了修改时,我们需要重新测试以确保修改符合我们的标准。当等待的线程被阻塞以允许另一个线程进入临界区时,pthread_cond_wait()自动解锁关联的互斥锁。
执行信号的线程将解除条件变量上排队的最高优先级线程的阻塞,而广播将解除条件变量上排队的所有线程的阻塞。关联的互斥锁被最高优先级的未阻塞线程原子性地锁定;然后,线程必须在通过临界区之后解锁互斥锁。条件变量等待操作的一个版本允许指定超时(pthread_cond_timedwait())。当超时过期时,等待线程可以被解除阻塞。
阻塞
阻塞是一种同步机制,它允许您“捕获”几个合作线程(例如,在一个矩阵计算中),强制它们在某个特定的点上等待,直到所有线程都完成了才能继续执行。
与pthread_join()函数不同,在pthread_join()函数中,需要等待线程终止,而在阻塞的情况下,需要等待线程在某个点会合。当指定数量的线程到达阻塞时,将解除所有线程的阻塞,以便它们能够继续运行。
首先使用pthread_barrier_init()创建一个阻塞:
这将在传递的地址创建阻塞对象(阻塞对象的指针位于阻塞中),其属性由attr指定。count成员持有必须调用thread_barrier_wait()的线程数。一旦创建了阻塞,每个线程都会调用pthread_barrier_wait()来表明它已经完成了:
当一个线程调用pthread_barrier_wait()时,它会阻塞,直到最初在pthread_barrier_init()函数中指定的线程数量调用pthread_barrier_wait()(并阻塞)为止。当正确数量的线程调用pthread_barrier_wait()时,所有这些线程将同时解除阻塞。
这里有一个例子:
主线程创建barrier对象,并初始化它,在线程继续执行之前,线程总数必须与barrier同步。在上面的示例中,使用了一个计数3:一个是主()线程,一个是thread1(),一个是thread2()。然后启动thread1()和thread2()。为了简化这个例子,我们让线程休眠以导致延迟,就好像计算发生了一样。为了同步,主线程简单地阻塞了barrier,因为它知道barrier只会在两个工作线程同时加入之后才会解除阻塞。
在这个版本中,包括以下barrier功能:
Sleepon locks
Sleepon locks和condvars非常类似,除了几个微小的不同。
与condvars一样,sleepon锁(pthread_sleepon_lock())可用于阻塞,直到条件变为true(比如内存位置更改值)。但是与condvars不同的是,sleepon锁在一个互斥锁上复用它们的功能,并动态地分配condvar,而不管检查的条件有多少。condvars的最大数量最终等于阻塞线程的最大数量。这些锁是在UNIX内核中通常使用的休眠锁之后形成模式的。
Reader/writer locks
更正式的说法是“多个读取器、单个写入器锁”,当数据结构的访问模式由多个读取数据的线程和(最多)一个写入数据的线程组成时,就会使用这些锁。这些锁比互斥锁更昂贵,但是对于这种数据访问模式很有用。
这个锁通过允许所有请求读访问锁(pthread_rwlock_rdlock())的线程在请求中成功工作。但是,当希望写入的线程请求锁(pthread_rwlock_wrlock())时,请求被拒绝,直到所有当前的读线程释放它们的读锁pthread_rwlock_unlock()。
多个写线程可以排队(按照优先级顺序)等待它们有机会写受保护的数据结构,所有被阻塞的写线程将在允许再次访问电子邮件线程之前运行。没有考虑读线程的优先级。
还有一些调用(pthread_rwlock_tryrdlock()和pthread_rwlock_trywrlock()),以允许线程在不阻塞的情况下测试实现请求锁的尝试。这些调用返回一个成功的锁或一个状态,指示不能立即授予锁。reader /writer锁不是直接在内核中实现的,而是由内核提供的互斥和condvar服务构建的.
信号量
信号量是另一种常见的同步形式,它允许线程在信号量上“post”和“等待”,以控制线程何时唤醒或休眠。post(sem_post())操作增加了信号量;wait(sem_wait())操作会递减。
如果你等待一个正信号,你不会阻塞。等待非正信号量将阻塞,直到其他线程执行post。在等待之前发布一次或多次是有效的。这种使用将允许一个或多个线程在不阻塞的情况下执行等待。信号量与其他同步原语的一个显著区别是,信号量是“异步安全的”,可以由信号处理程序操作。如果期望的效果是让一个信号处理程序唤醒一个线程,信号量是正确的选择。
信号量的另一个有用特性是它们被定义为在进程之间操作。虽然我们的互斥对象在进程之间工作,但POSIX线程标准认为这是一个可选的功能,因此可能无法在系统之间移植。对于单个进程中线程之间的同步,互斥量将比信号量更有效。
作为一种有用的变体,还可以使用命名信号量服务。它允许您在由网络连接的不同机器上的进程之间使用信号量。
因为信号量,像条件变量一样,可以合法地返回一个非零值,因为错误的唤醒,正确的使用需要一个循环:
调度策略同步
通过选择POSIX FIFO调度策略,我们可以确保在非smp系统上没有两个具有相同优先级的线程同时执行关键部分。FIFO调度策略规定,系统中具有相同优先级的所有FIFO计划线程在调度时都将运行,直到它们自愿将处理器释放到另一个线程。
当线程作为请求另一个进程的服务的一部分而阻塞时,或者当出现信号时,也会发生这种“释放”。因此,必须仔细编码和记录关键区域,以便以后的代码维护不会违反此条件。
此外,该进程(或任何其他进程)中的高优先级线程仍然可以抢占这些fifo计划的线程。因此,在临界区中可能“冲突”的所有线程都必须以相同的优先级调度fifo。执行了这个条件之后,线程就可以随意地访问这个共享内存,而不必首先进行显式同步调用。
消息传递同步
我们的发送/接收/回复消息传递IPC服务(稍后描述)通过其阻塞特性实现了隐式同步。在许多情况下,这些IPC服务会使其他同步服务变得不必要。它们也是唯一可以跨网络使用的同步和IPC原语(不包括基于消息传递的命名信号量)
原子操作同步
在某些情况下,可能希望执行一个简短的操作(例如递增一个变量),并保证操作将自动执行—即。,该操作不会被其他线程或ISR(中断服务例程)抢占。QNX中微子RTOS为:
•添加一个值
•减去一个值
•清理BIT位
•设置位
•切换(互补)位:这些原子操作都可以使用,包括C头文件< atomic.h >。尽管您可以在任何地方使用这些原子操作,但是您会发现它们在这两种情况下特别有用:
•在ISR和线程之间
•在两个线程(SMP或单处理器)之间
因为ISR可以在任何给定的点抢占一个线程,所以线程能够保护自己的唯一方法就是禁用中断。由于您应该避免在实时系统中禁用中断,所以我们建议您使用QNX中微子提供的原子操作。
在SMP系统上,多个线程可以并且确实可以并发地运行。同样,我们遇到了与上面的中断相同的情况——您应该在适用的情况下使用原子操作来消除禁用和重新启用中断的需要。
同步服务实现
下表列出了各种微内核调用和从它们构造的高级POSIX调用:
(6)时钟和定时器服务
时钟服务用于维护一天的时间,而内核计时器调用则使用时钟服务来实现间隔计时器。
注:QNX中微子系统的有效日期从1970年1月到2038年。time_t数据类型是一个无符号32位数字,它将这个范围扩展到2106个应用程序。内核本身使用无符号64位数字来计算自1970年1月以来的纳秒,因此可以处理到2554的日期。如果您的系统必须操作超过2554,并且无法在现场对系统进行升级或修改,那么您必须特别注意系统日期(请与我们联系以获得帮助)。
ClockTime()内核调用允许您获取或设置由ID (CLOCK_REALTIME)指定的系统时钟,该ID维护系统时间。一旦设置好,系统时间就会根据系统时钟的分辨率增加一些纳秒。可以使用 ClockPeriod()调用查询或更改此分辨率。
在系统页面(内存中的数据结构)中,有一个64位字段(nsec),它保存系统启动以来的纳秒数。nsec字段总是单调递增的,并且从不受ClockT ime()或ClockAdjust()设置一天当前时间的影响。
函数的作用是:返回一个自由运行的64位循环计数器的当前值。这在每个处理器上实现,作为一种高性能的定时短间隔机制。例如,在Intel x86处理器上,使用读取处理器时间戳计数器的操作码。在奔腾处理器上,这个计数器在每个时钟周期上递增。一个100mhz的奔腾频率的周期是1/100,000,000秒(10纳秒)。其他CPU架构也有类似的指令。在没有在硬件中实现这种指令的处理器上,内核会模拟一个指令。这将提供比在IBM pc兼容系统上提供指令(838.095345纳秒)更低的时间分辨率。在所有情况下,SYSPAGE_ENTRY(qtime)->cycles_per_sec字段在一秒内给出clockcycle()增量的数量。
ClockPeriod()函数允许线程将系统计时器设置为若干纳秒;操作系统内核将尽其所能以可用的硬件满足请求的精度。所选的间隔总是四舍五入到底层硬件定时器的精度的一个积分。当然,将其设置为极低的值可能会导致CPU性能的很大一部分被用于服务计时器中断。
为了减少功耗,内核可以以无滴答模式运行,但这有点用词不当。系统仍然有时钟滴答声,一切正常运行,除非系统空闲。只有当系统完全处于空闲状态时,内核才会关闭时钟节拍,而实际上,它所做的是减慢时钟的速度,以便下一个滴答声在下一次活动计时器触发后发生,这样计时器就会立即触发。为了启用无时间的操作,请指定- z选项。
时间校正
为了便于应用时间校正,而不会让系统在时间上经历突然的“步骤”(甚至让时间向后跳转),ClockAdjust()调用提供了指定时间校正应用间隔的选项。这将在指定的时间间隔内加速或延迟时间,直到系统与指定的当前时间同步。此服务可用于实现网络上多个节点之间的网络协调时间平均。
定时器
QNX中微子RTOS直接提供了POSIX定时器的全部功能,因为这些定时器可以快速创建和操作,所以它们是内核中廉价的资源。POSIX定时器模型非常丰富,提供了使定时器过期的功能:
•一个绝对的日期
•一个相对的日期(例如:, n纳秒之后)
•周期性(即。,每一个n纳秒)
周期模式非常重要,因为最常见的计时器使用往往是事件的周期性来源,它可以“踢”一个线程到生命中进行一些处理,然后再回到休眠状态,直到下一次事件发生。如果线程必须为每个事件重新编程计时器,那么就有时间流逝的危险,除非线程正在编程一个绝对日期。更糟糕的是,如果线程不能在计时器事件上运行,因为一个高优先级的线程正在运行,那么下一个编程到计时器的日期可能已经过去了!
循环模式通过要求线程只设置一次计时器,然后简单地响应由此产生的周期性事件源,从而避免了这些问题。
由于计时器是操作系统中另一个事件的来源,所以它们也利用了它的事件传递系统。因此,应用程序可以请求在超时发生时将QNX中微子支持的任何事件交付给应用程序。
操作系统提供的一个经常需要的超时服务是指定应用程序准备等待任何给定的内核调用或请求完成的最长时间。在抢占式实时操作系统中使用通用操作系统计时器服务的一个问题是,在超时规范和服务请求之间的间隔内,高优先级进程可能已经计划运行和抢占了足够长的时间,以至于指定的超时在请求服务之前就已经过期了。然后,应用程序将以一个已经失效的超时结束请求服务。,没有超时)。这个定时窗口可能导致“挂起”进程、数据传输协议中莫名其妙的延迟以及其他问题。
我们的解决方案是服务请求本身的超时请求原子形式。一种方法可能是在每个可用的服务请求上提供一个可选的超时参数,但是这样会使传递的参数过于复杂,而这些参数通常不会被使用。
QNX中微子提供了一个计时器Timeout()内核调用,该调用允许应用程序指定一组阻塞状态,以便启动指定的超时。稍后,当应用程序对内核发出请求时,如果应用程序即将阻塞指定状态之一,内核将自动启用先前配置的超时。
由于操作系统的阻塞状态非常少,所以这种机制的工作非常简洁。在服务请求或超时结束时,计时器将被禁用,并将控制权交还给应用程序。
(7)中断处理
无论我们多么希望它是这样,计算机并不是无限快。在实时系统中,没有不必要地消耗CPU周期是绝对重要的。从外部事件的发生到线程中负责对事件作出反应的代码的实际执行的时间最小化也是非常重要的。这个时间称为延迟。我们最关心的两种延迟形式是中断延迟和调度延迟。
中断延迟
中断延迟是指从断言硬件中断到执行设备驱动程序中断处理程序的第一条指令为止的时间。操作系统几乎总是完全启用中断,因此中断延迟通常是无关紧要的。但是代码的某些关键部分确实要求暂时禁用中断。这种禁用时间的最大值通常定义了最坏情况下的中断延迟——在QNX中微子中,这是非常小的。下面的图表说明了硬件中断由已建立的中断处理程序处理的情况。中断处理程序要么简单地返回,要么返回并导致事件被传递。
中断处理程序直接终止
上面图中的中断延迟(Til)表示最小延迟,即中断发生时中断已完全启用。最糟糕的中断延迟将是这次加上操作系统(或运行中的系统进程)禁用CPU中断的最长时间。
调度延迟
在某些情况下,低级硬件中断处理程序必须调度更高级别的线程来运行。在这个场景中,中断处理程序将返回并指示要交付一个事件。这引入了另一种形式的延迟—调度延迟—必须加以考虑。
调度延迟是指从用户的中断处理程序的最后一条指令到驱动线程的第一条指令执行之间的时间。这通常意味着保存当前执行线程的上下文和恢复所需驱动线程的上下文所需的时间。虽然比中断延迟更大,但在QNX中微子系统中,这个时间也是很小的。
需要注意的是,大多数中断在不传递事件的情况下终止。在许多情况下,中断处理程序可以处理所有与硬件相关的问题。传递事件以唤醒更高级别的驱动线程只在发生重大事件时发生。例如,串行设备驱动程序的中断处理程序将在每个接收到的传输中断时向硬件提供一个字节的数据,并且仅当输出缓冲区几乎为空时才会触发(devc-ser*)中的更高级别线程。
嵌套中断
QNX中微子RTOS完全支持嵌套中断。前面的场景描述了最简单也是最常见的情况,即只有一个中断发生。对未屏蔽中断的最坏情况下的计时考虑必须考虑当前正在处理的所有中断的时间,因为更高优先级的未屏蔽中断将抢占现有中断。
在下面的图中,线程A正在运行。中断IRQx导致中断处理程序Intx运行,它被IRQy及其处理程序Inty抢占。Inty返回导致线程B运行的事件;Intx返回一个导致线程C运行的事件。
堆放中断
中断调 用
中断处理API包括以下内核调用:
使用此API,具有适当特权的用户级线程可以调用InterruptAttach()或InterruptAttach Event(),在中断发生时将在线程的地址空间中传递硬件中断号和函数地址。QNX中微子允许将多个isr附加到每个硬件中断号上,在运行中断处理程序的执行过程中,可以为未屏蔽中断提供服务。
下面的代码示例展示了如何将ISR附加到PC上的硬件计时器中断(操作系统也用于系统时钟)。由于内核的计时器ISR已经在处理中断源的清除,这个ISR可以简单地在线程的数据空间中增加一个计数器变量,然后返回到内核:
使用这种方法,具有适当特权的用户级线程可以在运行时动态地将(和分离)中断处理程序附加到硬件中断向量。这些线程可以使用常规源代码级调试工具进行调试;ISR本身可以通过在线程级别和源代码级别上调用它来调试,或者使用InterruptAttachEvent()调用。
当硬件中断发生时,处理器将在微内核中输入中断重定向。这段代码将当前正在运行的线程的上下文寄存器放入适当的线程表条目中,并设置处理器上下文,以便ISR能够访问包含ISR的线程的代码和数据。这允许ISR使用缓冲区和代码在用户级线程解决中断,如果需要更高级别的工作线程,将事件队列的线程ISR的一部分,它可以工作在ISR的数据放入thread-owned缓冲区。
因为它使用包含它的线程的内存映射来运行,ISR可以直接操作映射到线程地址空间的设备,或者直接执行I/O指令。因此,操作硬件的设备驱动程序不需要链接到内核中。
微内核中的中断重定向代码将调用连接到该硬件中断的每个ISR。如果返回的值表示要传递某种类型的事件,那么内核将对该事件进行排队。当最后一个ISR被调用时,内核中断处理程序将完成对中断控制硬件的操作,然后“从中断返回”。
这个中断返回不一定会进入被中断线程的上下文中。如果排队事件导致高优先级线程准备就绪,那么微内核将中断返回到现在准备好的线程的上下文。
这种方法提供了一个具有良好边界间隔发生的中断执行的第一个指令级的ISR(中断延迟),和最后一个指令的ISR的第一个指令线程已经准备好的ISR(如线程或进程调度延迟)。最坏情况下的中断延迟是有限制的,因为操作系统仅在几个关键区域对一对操作码禁用中断。那些中断被禁用的间隔具有确定性的运行时,因为它们不依赖于数据。
在调用用户的ISR之前,微内核的中断重定向只执行一些指令。因此,对硬件中断或内核调用的进程抢占也同样快速,并且基本上使用相同的代码路径。
ISR在执行时,它具有完全的硬件访问权限(因为它是特权线程的一部分),但不能发出其他内核调用。ISR旨在应对硬件中断在尽可能少的微秒,做最少的工作来满足中断(从UART读取字节等),如果有必要,导致一个线程被安排在一些指定的优先级做进一步的工作。
最坏情况下的中断延迟可以直接从内核施加的中断延迟和每个中断的最大ISR运行时计算出给定的硬件优先级。由于硬件中断优先级可以重新分配,所以系统中最重要的中断可以成为最高优先级。
还要注意,通过使用InterruptAttachEvent()调用,不会运行用户ISR。相反,在每个中断上都会生成用户指定的事件;该事件通常会导致一个等待线程被调度运行并执行工作。当事件生成时,中断被自动屏蔽,然后由在适当时间处理设备的线程显式地解除屏蔽。
因此,硬件中断生成的工作的优先级可以在os计划的优先级上执行,而不是在硬件定义的优先级上执行。由于中断源在得到服务后才会重新中断,所以中断对关键代码区域运行时的影响是可以控制的。
除了硬件中断之外,微内核中的各种“事件”也可以被用户进程和线程“钩住”。当其中一个事件发生时,内核可以向上调用用户线程中指定的函数来执行此事件的特定处理。例如,当系统中的空闲线程被调用时,用户线程可以将内核向上调用添加到线程中,这样就可以很容易地实现特定于硬件的低功耗模式。
调度策略
(1)QNX 什么时候会发生调度?
QNX的设计目标是一个硬实时操作系统,所以,保证最高优先级的线程在第一时间占据CPU是很重要的。考虑到线程的状态都是在内核中进行变化的(都是因为线程进行了某个内核调用后变化的),所以QNX在每次从内核调用退出时,都会进行一次线程调度,以保证最高优先级的线程可以占据CPU。
得益于微内核结构,QNX的内核调用通常都非常短,或者说,每一个内核调用,都能够比较确定地知道要花多少时间。而且,因为微内核系统的基本就是进程间通信,所以在QNX上,一段程序非常容易进入内核并进行线程状态切换,很少能有长时间占满CPU的,在实际系统上测,现实上很少能有线程执行完整个时间片的。
举个例子,哪怕程序里只写一个 printf("Hello World!\n"); 可是在libc库里,最后这个会变成一个IO_WRITE消息,MsgSend() 给控制台驱动;这时,在MsgSend()这个内核调用里,会把printf() 的线程置为阻塞状态(REPLY BLOCK),同时会把控制台驱动的信息接收线程(从RECEIVE BLOCK)改到 READY状态,并放入 READY 队列。当退出MsgSend() 内核调用时,线程调度发生,通常情况下(如果没有别的线程READY 的话)控制台驱动的信息接收线程被激活,并占据CPU.
如果用户写了一个既不内核调用,也不放弃CPU的线程会怎么样?那时候,时钟中断会发生,当内核记时到线程占据了一整个时间片(QNX上是4ms)后,内核会强制当前线程进入 READY,并重新调度。如果同一优先级只有这一个线程(这是优先级最高线程),那么调度后,还是这个线程获取CPU。如果同一优先级有别的线程存在,那么根据调度算法来决定哪个线程获得CPU。
另一种常见情况是,由于某些别的原因导致高优先级线程被激活,比如网卡驱动中断导致高优先级驱动线程READY,所设时钟到达导致高优先级线程从阻塞状态返回READY状态了,当前线程开放互斥锁之类的线程同步对象,导致别的线程返回READY状态了。这些,都会在从内核调用退出时,进行调度。
(2)中断与优先级
如果用户线程长期占有CPU,时钟中断会打断用户线程。那中断的优先级是多少呢?
在QNX这样的实时操作系统里,“硬件中断”永远高于任何线程优先级,哪怕你的线程优先级到了255,只要有中断发生,都要让路,CPU会跳转去执行中断处理程序,执行完了再回归用户线程。事实上,能够快速稳定地响应中断处理,是一个实时操作系统的硬指标。
我们这里说的是“硬件中断”,就是说,当外部设备,通过中断控制器,向CPU发出中断请求时,无论当时CPU上执行的线程优先级是什么,都会先跳转到内核的中断处理程序;中断处理程序会去中断控制器找到具体是哪一个源发生了中断(中断号),并据此,跳转到该中断号的中断处理程序(通常是硬件驱动程序 通过 InterruptAttach() 挂接的函数)。在这个过程中,如果当前CPU正在处理另一个中断,那么这时,会根据中断的优先级来决定是让CPU继续处理下去(当前中断进入等待);或者发生中断抢占,新中断的优先级比旧中断高,所以跳转新中断处理。
当然,实际应用中,特别是微内核环境下,考虑中断其实只是中断设备给出的一个通知,对这中断的响应并不需要真的在中断处理中进行,驱动程序可以选择在普通线程中处理,QNX上有InterruptAttachEvent() 就是为了这个设计的。通常这里的“事件”会是一个“脉冲”,也就是说,当硬件中断发生,内核检查到相应中断绑定了事件。这时,不会跳转到用户中断处理程序,而是直接发出那个脉冲,以激活一个外部(驱动器中)线程,在这线程中,做设备中断所需要的处理。这样做,虽然稍微增加了一些中断延迟,但也带来了不少好处。首先,这个外部线程同普通的用户线程一样,所以可以调用任何库函数,而中断服务程序因为执行环境的不同,有好多限制。其次,因为是普通用户线程,就可以用线程调度的方法规定其优先级(脉冲事件是带优先级的),使不同的设备中断处理,跟正常业务逻辑更好地一起使用。
(3)多CPU上的线程调度
现在同步多处理器(SMP)已经相当普及了。在SMP上,也就是说当有多个CPU时,我们的调度算法有什么变化呢?比如一个有2个CPU的系统,首先肯定,系统上可执行线程中的最高优先级线程,一定在2个CPU上的某一个上执行;那,是不是第二高优先级的线程就在另一个CPU上执行呢?
虽然直觉上我们觉得应该是这样的(系统里的第一,第二高优先级的线程占据CPU1和CPU2),但事实上,第二高优先级的线程占据CPU2这件事,并不是必要的。实时抢占系统的要求是最高优先级”必须“能够抢占CPU,但对第二高优先级并没有规定。拿我们最开始的双CPU图再看一眼。
线程4以优先级15占据CPU1这是毫无疑问的,但线程5只有优先级12,为什么它可以占据CPU2,而线程3明明也有优先级15,但只能排队等候,这是不是优先级倒置了?其实并没有,如上所述,系统确实保证了“最高优先级占据CPU”的要求,但在CPU2上执行什么线程,除了线程本身的优先级以外,还有一些别的因素可以权衡,其中一个在SMP上比较重要的,就是“线程跃迁”。
“线程跃迁”指的是一个线程,一会儿在CPU1上执行,一会儿在CPU2上执行。在SMP系统上,线程跃迁而导致的缓存清除与重置,会给系统性能带来很大的影响。所以在线程调度时,尽量把线程调度到上次执行时用的CPU,是SMP调度算法里比较重要的一环。上述例子中,很有可能就是线程3上一次是在CPU1上执行的,而线程5虽然优先级比较低,很有可能上一次就是在CPU2上执行的。
实际应用中,因为QNX的易于阻塞的特性,其实大多数情况下,还是符合“第一,第二高优先级线程在CPU上执行”的。只是,如果你观察到了上述情形,也不需要担心,设计上确实有可能不是第二高优先级的线程在运行。
另一个多处理器上常见的应用,是线程绑定。在正常情况下,把可执行线程调度到哪一个CPU上,是由操作系统完成的。当然操作系统会考虑“线程跃迁”等情形来做决定。但是,QNX的用户也可以把线程绑定到某一个(或者某几个)CPU上,这样操作系统在调度时,会考虑用户的要求来进行。绑定是通过ThreadCtl() 修改线程的 “RUNMASK” 来进行的,如果你有0,1,2,3 总共4个CPU,那么 0x00000003意味着线程可以在CPU0和CPU1上执行,具体例子可以参考ThreadCtl()函数说明。更简单的办法,是通过QNX特有的 on 命令的 –C 参数来指定,这个指定的 runmask,还会自动继承。所以你可以简单的如下执行:
# on –C 0x00000003 Navigation &
# on –C 0x00000004 Media &
# on –C 0x00000008 System &
这样来把不同的系统部署到不同的CPU上。
这样做的好处当然是可以减少比如因为系统繁忙而对导航带来的影响,但不要忘了,另一面,如果所有 Media 线程都处于阻塞状态,上述绑定也限制了导航线程使用CPU2的可能,CPU2这时候就会空转(执行内核 idle 线程)。
(4)自适应分区调度算法
前面我们提到过,在讨论优先级调度时,只是讨论当有多个优先级相同的线程时,系统怎样取舍。优先级不一样时,肯定是优先级高的赢。但是“高出多少”并不是一个考量因素。两个线程,一个优先级10,另一个优先级11的情况,和一个10,另一个40的情况是一样的。并不会因为10和40差距比较大而有什么不同。
假如我们有红蓝两个线程,它们的优先级一样,调度策略是RR,两个线程都不阻塞,那么在10时间片的区间里,我们看到的就是这样一个执行结果:
也就是说,各占了50%的CPU。但只要把蓝色线程提高哪怕1,执行结果就成了下面这样。
这种“非黑即白”的情形,是实时系统的基本要求(高优先级抢占CPU)。但是当然,现实情况有时候比较复杂。比如 “HMI渲染” 是需要经常占据CPU的一个任务(这样画面才会顺畅),但“用户输入”也是需要响应比较快的(不然用户的点击就会没有反应)。如果“用户输入”的优先级太高的话,那用户拖拽时,画面就会卡顿甚至没有反应?反之,如果”HMI 渲染“的优先级太高,那么有用户输入时,因为处理程序优先级低而造成用户输入反应慢。通常情况下,需要有经验的系统工程师不断调整这两个任务的优先级(因为优先级继承与传统,一个任务可能涉及到多个线程),来达到系统的最优。那么,有没有别的办法呢?
分区调度
传统上,有一种“分区调度”的方法,今天还有一些Hypervisor采取这个办法。这个想法很简单,就是把CPU算力隔成几个分区,比如70%,30%这样,然后把不同线程分到这些分区里,当分区里的CPU预算被用完以后,那个分区里所有可执行线程都会被”停住“,直到预算恢复。
假设我们把红线程放入70%红色分区,蓝线程放入30%蓝色分区,然后以10个时间片为预算滑动窗口大小,各线程具体就会如下图占据CPU:
在前6个时间片中,蓝红分区分别占据CPU,注意在第7个时间片时,虽然蓝分区中线程跟红分区中线程有相同的优先级,虽然调度策略是轮回,应该轮到蓝线程上了,但是因为蓝线程已经用完了10个时间片里的3个,所以系统没有执行蓝线程,而是继续让红线程占据CPU,一直到第8第9和第10个时间片结束。
10个时间片结束后,窗口向右滑动,这时我们等于又多了一个时间片的预算,在新的10个时间片中,蓝线程只占了两个(20%),这样,新的第11个时间片,就分给了蓝分区。
同理再滑动后,第12个时间片,分给红线程;一直到17个时间片时,同样的事情再度发生,蓝分区线程又用完了10个时间片里的3个,而被迫等待它的预算重新补充进来。
综上,在任意一个滑动窗口中,蓝色分区总是只占30%,而红色分区却占了70%。QNX的自适应分区调度,跟上面这个是类似的。只是传统的分区调度,有一个明显的弱点。
想一下这个情况,如果红线程因为某些情况被阻塞了,会发生什么呢?
对,蓝线程是唯一可执行线程,所以它一直占据CPU。但是,当3个时间片轮转之后,因为蓝分区只有30%的时间预算,它将不再占据CPU,而因为红线程无法执行,接下来的7个时间片CPU处于空转状态(执行Idle线程)。
一直到时间窗口移动,那时,因为蓝分区只占用了20%的算力,所以它再次占据CPU……
所以你也看到了,在传统的分区调度里,当一个分区的算力有富裕的时候,CPU就被浪费了。
自适应分区调度
QNX在传统的分区调度上,增加了“自适应”的部份。其基本思想是一样的,给算力加分区,然后把不同的线程分到分区里。这样,当所有的线程都忙起来时,你会发现情况跟图7是一样的。但是当分区算力有富裕时,“自适应“允许把多出来的算力”借“给需要更多算力的分区。
如上,当蓝色分区里的线程消耗完了他自己的分区预算后,自适应分区会把有富裕算力的红色分区的预算,借给蓝色分区,蓝分区内线程得以继续在CPU上运行。注意,在第8个时间片时,红色分区需要使用CPU,蓝色分区立即让路,把CPU让给红色分区。而当红色分区里的线程被阻塞住以后,蓝色分区线程继续使用CPU。
自适应分区似乎确实带来了好处,但是也带来了一些潜在的问题,需要在系统设计的时候做好决定。
自适应分区调度与线程优先级
在分区调度的系统里,线程的优先级代表了什么?
答案取决于各个分区对各自算力的消耗情况。我们假设蓝色分区里的线程优先级比较高,红色的优先级比较低,当两个分区都有预算时,内核会调度(所有分区里的)最高优先级线程执行。如果系统一直不是很忙,那么不论分区,永远是有最高优先级的线程得到CPU,这个,跟一个标准的实时操作系统是一致的。
当两个分区中某一个有预算时(意味着那个分区中所有的线程都不在执行状态),那么多出来的CPU算力会被分给另一个分区,另一个分区中的最高优先级线程(虽然用完了自己分区的预算,但得到了别的分区的算力),继续占据CPU。这个,也是跟实时操作系统是一致的。
比较特殊的情况是,当两个分区都没有预算,都需要占据CPU时,这时,蓝色线程虽然有较高的优先级,但因为分区算力(30%)被用完,面且没有别的算力可以“借”,所以它被留在READY队列中,而比它优先级低的红色线程得以占据CPU。
自适应分区调度富裕算力分配
我们上面的例子只有两个分区,考虑这样一个例子。假设我们现在有A (70%),B (20%),C (10%) 三个分区,A分区没有可执行线程,B分区有个优先级为10的线程,C分区有个优先级为20的线程。我们知道A分区的70%会分配给B和C,但具体是怎么分配的呢?
如上所述,当预算有富裕时,系统挑选所有分区中,优先级最高的线程执行,也就是说C分区中的线程得到运行。在一个窗口以后,你会发现A的CPU使用率是0%,B是20%,C则达到了80%。也就是说A所有的富裕算力,都给了C分区(因为C中的线程优先级高)。
也许,在某些时候,这个不是你所期望的。也许C中有一些第三方程序你无法控制,你也不希望他们偷偷提高优先级而占用全部富裕算力。QNX提供了SchedCtl()函数,可以设SCHED_APS_FREETIME_BY_RATIO 标志。设了这个标志后,富裕算力会按照各分区的预算比例分配给各分区。上面的例子下,最后的CPU使用率会变成 A 是0%, B是65%,而C是35%。A分区富裕的70%算力,按照大约 2: 1的比例,分给了分区B和C。
“关键线程”与“关键分区”
在实际使用中,有一些重要任务,可能需要响应,不论其所在的分区还有没有算力。比如一个紧急中断服务线程,不管分区是不是还有预算,都需要响应。为了解决这种情况,在QNX的自适应分区调度里,除了给分区分配算力预算以外,还允许有权限的用户为分区分配“关键响应时间”,并把特定线程定义为“关键线程”。
当一个“关键线程”需要执行时,如果线程所在分区有预算,它就直接使用所在分区预算就好,如同普通线程;如果所在分区没有预算了,但是别的分区还有预算,那么“自适应”部份会把别分区的预算拿过来,并用于关键线程,这个跟普通的自适应分区调度一样。
只有当系统里所有分区都没有预算了,而有一个关键线程需要运行,而且线程所在的分区已经预先分配了关键响应预算,那么线程允许“突破”分区的预算,使用“关键响应预算”来执行。在QNX里,一个关键线程消耗的时间,从退出RECEIVE_BLOCK开始,到下一次进入RECEIVE_BLOCK。而且,关键线程的属性是可传递的,如果关键线程在执行中,给别的线程发送了消息,那个线程也会变成关键线程。
总的来说,关键线程是用来保证关键任务不会因为系统太忙而无法取得CPU时间。即使所有的分区都被占满了,至少还有“关键响应时间”可供关键线程来使用。当然,一个系统里不应该有太多的关键线程和关键响应时间。理论上,假设所有的线程都是关键线程,那么整个系统其实就变成了一个普通的按优先级调度的实时系统,所有的分区和预算都不起作用了。
在最紧急的情况下,关键线程可以使用“关键响应时间”来完成它的任务。如果“关键响应时间”还是不够,会怎么样?这个是系统设计问题,在设计系统的时候,你就应该为关键线程分配它能够完成任务所需要的最大时间。如果依然发生“关键响应时间”不够的状况(被称为“破产”状态),这个就是一个设计错误了。
关键线程的破产
如上所述,关键线程的破产是一个设计问题。或者线程完成的任务并不那么“关键”,或者设计时给出的预算不够。这种情况下需要重新审视整个系统设计(因为系统在某些情况下无法保证关键任务在预定时间内完成)。QNX在自适应分区里提供了侦测到关键线程破产时的多种响应办法,可以是强行忽视,或者重启系统,或者由自适应分区系统自动调整分区的预算。
自适应分区继承
想像这个场景,文件系统在System分区里,但另一个Others分区里的第三方应用拼命调用文件系统,很有可能造成System分区的预算耗尽;这样,首先可能导致别的应用无法使用文件系统;更严重的,可能是System分区里别的系统,比如Audio也无法正常工作。这个,显然是自适应分区系统带来的安全隐患。
解决办法,就是跟优先级在消息传递上可以继承一样,分区也是可以继承的。文件系统虽然分配在System系统里,但根据它响应的是谁的请求,时间被记到请求服务的线程分区里。这样,如果一个第三方应用拼命调用文件系统,最多能做的,也只是消耗它自己的分区,当他自己分区的预算被耗尽时,影响它自己的CPU占用率。
自适应分区的小结
自适应分区有一些有趣的用法,比如我们常常被要求“系统需要保留30%的算力”。有了自适应分区,就可以建一个有30%预算的分区,在里面跑一个 for (;;); 这样的死循环。这样,剩下的系统就只有70%的算力了,可以在这个环境下检验一下系统的性能和稳定性。
自适应分区的具体操作方法,可以参考QNX的文档。不同版本的QNX有稍微不同的命令行,但基本设计是一样的。这篇文章只是介绍了自适应分区的基本概念,实际使用上,还是有许多细节需要考虑的,真的要使用,还是需要详细参考QNX对应文档。
QNX 工具介绍
QNX Neutrino实时操作系统(RTOS):内存受保护的微内核架构。
QNX Neutrino实时操作系统架构如下图所示:
QNX Neutrino RTOS是功能齐全性能可靠的简化版操作系统,可满足最小规格的实时嵌入系统的有限资源要求。其真正的微内核操作系统和模块化架构可使客户以较低的运行总成本创建高度优化的可靠系统。
- (1). 该系统建立在真正的微内核架构上。在这种系统中,所有驱动程序、应用程序、协议栈和文件系统都在内核外部内存受保护的安全的用户空间内运行。几乎所有组件在出现故障时都能自动重启而不会影响其他组件或内核。
- (2). 该系统采用模块化结构,可允许用户动态升级模块、引入新功能或实施问题修复,而无高增加停机时间和系统中断的成本。
- (3). 该系统采用多核技术和内置透明分布处理技术。
- (4). 该系统根据POSIX标准设计,只需通过简单地重新编译,就可移植既存代码、开源UNIX、Linux和因特网代码。通过标准应用程序接口,用户能重新使用应用程序代码。
- (5). 该系统利用自适应分区技术确保系统资源满足应用要求。
- (6). 该系统支持x86、PowerPC和ARM平台。
QNX Momentics工具套件(Tool Suite):基于Eclipse的灵活集成开发环境。
- (1). 该工具套件包含用户所需的所有工具,便于迅速创建和优化用于QNX Neutrino实时操作系统的应用程序。从板卡启动到远程诊断,QNX Momentics工具套件为用户提供在整个开发周期内节省时间的工具,而且全部在单独、使用简便的环境中进行。
- (2). 用户可选择自己喜欢的编程语言、主机和目标机。利用QNX Momentics工具套件,用户可使用C、C++、嵌入式C++编程;可以在Windows或Linux主机中开发程序;并以ARM、PowerPC和x86处理器为开发目标,所有工具都取自相同的集成开发环境(IDE)。该工具套件还提供极大的灵活性,允许用户同时混用多种编程语言和处理器架构。
- (3). 该工具套件提供了大量的优质高效的分析工具,以加快产品交付所有阶段的进展。该工具套件包括许多非侵入式图形分析工具,以帮助用户轻松隔离并呈现资源的使用情况、定位瓶颈并对系统性能进行精确调整,包括应用程序剖析、系统剖析和内存分析。
- (4). 该工具套件支持所有QNX Neutrino实时操作系统技术,包括多核技术、扩展网络、闪存文件系统、高级图形和透明分布处理技术等。
- (5). 该工具套件中含有经过优化的GCC编译器、GDB调试程序。
QNX Neutrino RTOS 7.1新功能介绍
QNX Neutrino是面向hard RTOS的操作系统。它从1986年开始开发,目标是成为一个模块化的OS,具有核心功能的小微内核,并可以添加用于附加功能的额外模块,包括网络工具和图形界面。微内核主要处理与POSIX兼容的进程之间的消息传递,并允许进行一系列基于优先级的线程处理,从而可以对CPU负载共享进行精细控制,并且可以很好地扩展多个内核和设备。它也可以编译并转移到设备上,增加了对各种嵌入式设备的可移植性。虽然QNX不是专门为自动驾驶系统开发的,但它已被纳入商业和AV研究平台。
(1)完整的多级安全性
可配置:最佳安全级别可由系统范围内策略驱动的安全模型指定。
运行时保护:高度安全的系统可以利用安全功能,例如地址空间布局随机化、安全启动、信任链建立、完整性度量、强制性访问控制、路径空间控制、无根执行和异常检测。
安全的软件交付:通过数字签名的软件包交付和软件更新警报启用软件供应链完整性管理。
(2)设计安全
安全认证谱系:用于汽车的ISO 26262 ASIL D,用于工业的IEC 61508 SIL3和用于医疗的IEC 62304。
组件隔离:通过QNX Neutrino RTOS成熟的微内核架构实现的用户应用程序、系统服务和设备驱动程序之间的隔离。
可选的调度算法:基于优先级的偶发和时间触发的确定性算法,可提高系统的灵活性。
确保的CPU分配:线程或进程级别的最小CPU分配。
(3)提高计算能力
高性能:完全支持ARMv7、ARMv8和Intel x86架构的64位和32位。
全面的硬件优化:AMD、英特尔、英伟达、恩智浦/Freescale、高通、瑞萨、三星、德州仪器和赛灵思的 SoC,全面的板级支持包。
GPU集成:ARM、Imagination英特尔、英伟达、高通和Vivante。
知识拓展
1.QNX的实时性分析
背景
实时操作系统主要用于计算机实时控制。在分散控制系统中,现场控制站担负着系统的数据采集、实时运算和网络通信等功能,其软件的开发是基于实时操作系统平台的,因此,选择一个良好的操作系统平台对于分散控制系统(DCS)的开发具有至关重要的意义。
QNX作为一种分布式嵌入式实时操作系统,在国产DCS开发中受到广泛青睐,山东鲁能控制公司的LN2000、北京和利时自动化工程公司的Hollysys等都使用了QNX操作系统,并取得了良好的应用效果。
1 实时操作系统
1.1 概念
有关实时系统的定义在C.M.Krishna著《实时系统》[1]一书中给出了一个广义的定义: 能够及时地对外部事件响应的计算机系统称为实时系统。这个定义强调了系统对于外部事件的响应能力是判定实时系统的关键,如果再加上系统对指定任务执行的能力,就可以比较全面地考察一个系统的实时性。所以可以这样认为:实时性是指系统在限定的时间内完成所给定的功能并对外部异步事件做出响应的能力。
实时系统中有不同的任务,在实时操作系统中,不同的任务有不同的驱动方式。实时任务总是由于某事件的发生或条件满足而激活,所以实时任务又可分为时间驱动和事件驱动。
时间驱动有两种:绝对时间驱动和相对时间驱动。绝对时间驱动是指在某个指定时刻执行的任务,可以使用计算机内部时钟,有时需要进行卫星对时或系统间对时。相对时间驱动是指周期性执行的任务。一般可用计算机内部时钟或软时钟计时触发。
事件驱动有两种:内部事件和外部事件。内部事件驱动是指某一程序运行的结果导致另一任务的启动,一般属于同步任务范畴。外部事件驱动是指工业现场状态发生变化或出现异常,立即请求 CPU处理,一般属于异步事件,是最典型的实时任务,也是衡量系统实时性的最重要的指标。
实时操作系统中也包含一些无实时性要求的任务,如系统初始化工作,只是在系统启动时执行一次即可。
实时操作系统除具有通用操作系统的特性和功能外,其主要特点就是实时性强。系统的实时性除了由硬件质量作为基本保证外,主要由实时操作系统内部的事件驱动方式和任务调度方式来决定。衡量实时操作系统实时性能强弱的指标是系统执行完规定的功能和响应外部异步事件所需时间的长短。所以,对于QNX系统的实时性分析,主要从系统的两个方面进行:中断处理方式(即外部事件驱动方式)和任务调度方式。对于时间驱动方式和内部时事件驱动方式作简要介绍。
1.2 QNX简介
在智能化设备、仪器仪表的应用场合,出于对产品体积、成本等因素的考虑,往往要求将计算机控制部分安装于设备内部且占用空间尽可能的小。在这种情况下,处理器一般没有多少可用的内存,更没有可用的外存,而操作系统就装在这有限的内存中(一般在 ROM中),这种系统人们称之为嵌入式系统。嵌入式系统是智能化设备、仪器仪表的灵魂。QNX是由加拿大QSSL公司开发的一种分布式嵌入式实时操作系统,能够广泛应用于嵌入式系统领域,实现实时控制。
实时操作系统本身的设计水平直接影响到系统的实时性能。因此,操作系统必须是高效率的,系统本身的开销应尽可能小,进程调度、进程间通信、中断处理等系统公用程序应精练而有效,它们造成的延迟应尽可能地短,使同样的硬件配置能满足更强的实时性要求,为应用的开发留下更大的余地。
2 中断处理
中断是计算机硬件与操作系统之间的纽带,中断处理是实时操作系统的基础。中断控制器将与计算机有关的外部事件信号发送给CPU,使CPU正在执行的程序中断而优先响应外部事件的请求,即实时请求。系统实时性的强弱与中断响应时间密切相关。
2.1优先级抢占
是否按优先级抢占CPU是衡量操作系统实时性的重要特征。所谓优先级抢占CPU是指当中断发生时,若中断优先级是当前最高级,才能立即得到响应,这时的响应时间即为最小响应时间,若中断请求发生时还有更高优先级中断处理正在执行,甚至还有较高级中断请求在排队等待处理,则该中断请求得不到及时响应。QNX系统允许为硬件中断分配优先级,因此也允许更高优先级的中断抢占CPU,即当较低级的中断处理程序正在执行时,更高级的中断发出中断请求,CPU将转向更高级的中断处理程序,直至高级中断的处理执行完,再返回来执行当前的程序。在分析低优先级中断处理所需的处理时间时,还应将所有较高级中断处理所用的时间考虑进去。
2.2 时间延迟
对于中断处理所消耗的时间即时间延迟是衡量系统实时性的重要指标,时间延迟指从一个事件发生到程序中负责响应该事件的代码实际开始运行所需要的时间。在QNX系统中中断处理中时间延迟主要包括中断延迟和调度延迟。这两种延迟与QNX的中断处理方式有关系统,在QNX系统中依据返回情况,可将中断处理方式分为两类,一类是单纯的返回,如图2,另一类是在返回的同时触发一个代理,如图3。
图中Til为中断延迟,Tint为中断处理时间,Tiret 为中断结束时间,Tsl为调度延迟。
中断延迟时间Til是指系统确认中断开始直到执行中断服务程序的第一条指令为止整个处理过程所需要的时间。实时操作系统的中断延迟时间由下列三个因素决定:
- 1) 处理器硬件电路的延迟时间,通常这个时间可以忽略。
- 2) 实时操作系统处理中断并将控制权转移给相关处理程序所需要的时间。
- 3) 实时操作系统的中断禁止时间。当系统运行在核心态或执行某些系统调用的时候,是不会因为外部中断的到来而中断执行的。只有当系统重新回到用户态时才响应外部中断请求,这一过程所需的最大时间就是中断禁止时间。QNX对这些特别关键的代码也需要对中断实行暂时的屏蔽,这一屏蔽的时间延时很小,可以忽略。
调度延迟Ts1是从中断处理程序结束时起到一个驱动程序进程的第一条指令开始运行之间这一段时间。大多数中断处理结束时并不触发代理。在很多情况下,仅用中断处理程序就足以解决所有问题,所以仅在出现最大事件时才需要触发有关代理来调度更高一级的驱动程序进程。
QNX系统的这两种延迟指标见下表1:
实时系统应尽量减少中断延迟响应,其主要方法是可提高CPU速率,合理安排外部事件的中断优先级,优化各级中断处理程序,尤其是优先级高的中断处理程序。
3 任务调度
实时操作系统都是多任务系统。中断处理程序虽然能及时把现场信息取回来,但对信息的分析处理、依据处理结果所采取的对现场的应答与控制工作,一般由中断任务来完成。一个外部事件的发生往往会引起一系列同步任务的执行,系统中还有许多由时间驱动的任务。这就需要进行任务调度,其作用是决定如何把CPU资源分配给执行中的各个任务。每个就绪的进程都在某个队列中排队,等待使用CPU。用什么样的策略决定哪个就绪的进程使用CPU,以及正使用CPU的进程什么情况下放弃使用CPU,是任务调度的关键问题。任务调度方式由任务调度算法实现,是决定系统实时性能的重要环节。
这里需要解释两个概念的关系:任务是指一个程序分段,这个分段被操作系统当作一个基本单元来调度,任务是系统运行前已经设计好的。进程是指任务在作业环境中的一次运行过程。可以把任务和进程同等看待,认为进程是一个动态过程,即执行任务的动态过程。在本文中对这两个概念不作严格的区分。
在 UNIX分时操作系统中,每个进程都有一个优先级,优先级高的进程先使用CPU,调度程序用一个算法来计算正在执行的进程的优先级,对于使用过大量CPU时间的进程则降低它们的优先级,对于等待时间长的进程则升高它们的优先级。进程自己只能通过系统调用改变自己的优先级,而没有其他控制进程先后执行的手段。因此,分时系统不能保证在限定的时间完成指定的功能。
在QNX实时操作系统中,提供了控制进程执行的手段,使用户能够设定和改变进程的优先级,并让用户能够选择进程调度算法。进程的优先级大小为1(最低)到31(最高),调度程序在选择下一个运行进程时,将检查每个处于就绪状态的进程的优先级,具有高优先级的进程将首先被执行。
QNX基本的任务调度算法使用的是按优先级抢占的调度方法。这种方法保证在任何时刻都是优先级最高的任务占用CPU时间。优先级最高的任务可以中断当前运行的任务 – 哪怕它是中断任务 – 而抢占CPU,这种方法用于工业实时性要求高的场合。而不是采用优先级加轮询的调度方法和非抢占式优先级调度的方法。
在基本调度算法的基础上,对于当两个或更多个具有同样优先级的进程同时处于就绪态并且都是当前就绪队伍中优先级最高的任务时,QNX提供了三种调度方法来解决这个问题。
- 1) 先入先出调度法:先进入任务队列的进程被选择运行,直到它自动放弃运行或者被一个级别更高的进程打断运行。
- 2) 循环式调度法:先进入任务队列的进程被选择运行,直到它自动放弃运行或者被一个级别更高的进程打断运行或者它用完了自己的时间片。一个时间片是50ms,是系统分配给每个进程用于运行的时间单位。
- 3) 适应式调度法:在这种调度法中,一个进程的优先级会在运行中发生变化:
- 4) 如果该进程用完了自己的时间片仍未被阻塞,进程的优先级将被减1,称为优先级衰退,系统中一个进程只能降低一次优先级。如果该进程被阻塞,则将立即恢复为原来的优先级。
QNX系统提供的适应式调度法为计算密集型的进程提供对CPU更有效的利用,同时还保持了对其他进程的快速响应能力。不过在系统设计中应注意同优先级的任务数量不宜太多,因为这样终归会影响系统的实时响应时间。
4 上下文切换
作为实时性的两个主要指标的上下文切换和中断延时,在QNX中操作系统中,其时间指标都在微秒一级。如:
5 结论
QNX操作系统对实时应用是理想的,它提供一个实时系统所需要的一切基本要素:多任务、由优先级驱动的急者优先式调度方式、适应式调度法和快速上下文切换。在保持全部真正微内核设计的固有优点的同时,QNX比传统内核结构更迅速发布系统服务。因此,开发者使用QNX可以开发出优于昂贵的高档系统的性能的低成本的产品。
一点思考
可以看到,微内核的消息传递可以很容易地扩展到跨设备的消息传递,从而达到“互连”的状态。然后QNX的资源管理器消息定义,又可以使在不同设备间的通信“互通”。
QNX的“用文件操作接口来操作硬件”,跟安卓的“硬件抽像层”(Hardware Adaptation Layer)的背后的思路,其实是一致的。想一想,如果 HAL 层的所有API都有对应的消息传递的话,那是不是所有安卓上的App,都可以控制远程安卓上的别的设备了?
QNX的透明分布式处理,本身是一个网络协议,默认是用以太网做数据传输层。但也可以加参数使它用IP做数据传输层。甚至还可以自己给io-pkt编写传输层驱动(比如通过共享内存)
大部份的QNX系统,都是在一个网络里有一些已知的,静态的节点。如果想在有多个不确定设备的情况下,需要考虑的事情就更多。怎样加入退出网络,怎样发布和搜索网络上的服务,怎样认证,以及保证网上传输数据的案全性,这些都需要额外地考虑与处理。
还有就是QNX的这个基于以太网调优的分布式处理系统,如果在Wifi或别的转输层上传, 怎样才能保证网络的稳定,以及达到足够的传输速度,这些都需要加以特别的注意。