
第一部分QNX OS 设计哲学1.1 概述QNX OS 的首要目标是以健壮、可扩展的形式交付开放系统的 POSIX API适用于从微小嵌入式设备到分布式计算环境的广泛系统。支持 x86 和 ARM 处理器家族。1.2 可嵌入的 POSIX OS根据一个流行神话如果刨开一个 POSIX 操作系统你会发现 UNIX 在表面之下“但 POSIX 不是 UNIX。POSIX 标准以接口定义而非实现。QNX OS 拥有绝非 UNIX的架构通过微内核方法以一种易于为实时嵌入式系统缩小或增量扩展的形式交付 POSIX API”。1.3 产品扩展由于微内核 OS 可以通过选择包含哪些进程来轻松扩展“你可以将单个微内核 OS 用于比实时执行程序更广泛的目的”。产品线开发中不同型号可以共享同一个微内核 OS —— “按需添加文件系统、网络、图形用户界面和其他技术”。可扩展方法的优势产品线成员间可移植应用代码整个产品线使用通用工具开发人员可移植的技能集缩短上市时间1.4 为什么嵌入式系统选择 POSIX标准描述1003.1进程管理、设备 I/O、文件系统 I/O、基本 IPCRealtime Extensions信号量、优先调度、实时信号、高精度定时器、增强 IPC、同步/异步 I/OThreads单地址空间中的多线程创建和管理AEPs实时和嵌入式系统的应用环境配置文件POSIX 的优势多 OS 来源 — 硬件制造商不依赖单一供应商开发人员可移植性 — 经验可跨 RTOS 转移开发环境 — 跨主机和目标系统 API 相同1.5 为什么嵌入式系统选择 QNX OSOS 的首要职责是管理计算机的资源。QNX OS “非常适合嵌入式实时应用”提供可扩展到非常小的占用空间多任务和线程优先级驱动的抢占式调度快速上下文切换所有功能都带有 POSIX 标准 API开发人员无需为小系统放弃标准。两个基本原则微内核架构基于消息的进程间通信1.6 微内核架构“微内核结构为一个提供最小服务的微小内核由一组可选的协作进程团队使用”。关键设计目标“模块化是关键大小只是副作用。”三种架构比较架构内存保护传统执行程序“无内存保护”宏内核 OS“系统进程无保护”微内核 OS“提供完整内存保护”QNX OS “充分利用 MMU 在受保护环境中交付完整的 POSIX 进程模型”。完整的 QNX OS 第一版于 1981 年发布。1.7 IPC 的哲学角色当线程在实时多任务环境中并发运行时OS 必须提供通信机制。IPC 是将应用设计为一组协作进程的关键每个进程处理一个定义良好的部分。第二部分QNX OS 微内核2.1 概述微内核实现了嵌入式实时系统中使用的核心 POSIX 特性以及 QNX OS 基础的消息传递服务。后续 QNX 微内核不断减少实现给定内核调用所需的代码。微内核并非用汇编编写。实现主要用 C 语言编写性能和大小通过不断改进的算法和数据结构实现而非汇编级窥孔优化。2.2 设计目标微内核面临来自计算领域两端的压力——从内存受限的嵌入式系统到高端 SMP 机器。核心目标跨越计算谱系的两端容纳看似互斥的功能集将系统范围扩展到其他 OS 实现无法达到的领域POSIX 实时和线程服务直接在微内核中实现支持 POSIX 配置文件所需的纯线程模型实现完整 POSIX 合规——需要带内存保护的进程模型2.3 系统服务微内核提供支持以下特性的内核调用线程消息传递信号时钟定时器中断处理信号量互斥锁 (mutexes)条件变量 (condvars)屏障 (barriers)关键特性“整个 OS 完全可抢占即使在进程间传递消息时也是如此”最小复杂性的微内核有助于设置最长不可抢占代码路径的上限“中断被禁用或抢占被保持的间隔非常短暂通常为几百纳秒量级”选择包含的服务基于具有短执行路径需要显著工作量如进程加载的操作被放在外部进程/线程中2.4 线程与进程线程是微内核中调度和执行的最小单位。进程是线程的容器定义了线程执行的地址空间。线程与进程关系概念描述Thread最小执行单位、调度和执行单位Process线程的容器定义地址空间。始终包含至少一个线程pthread 调用高级 API有对应的微内核调用进程模型每个进程受 MMU 保护可包含多个线程pthread 与微内核调用映射POSIX 调用微内核调用描述pthread_create()ThreadCreate()创建新线程pthread_exit()ThreadDestroy()销毁线程pthread_join()ThreadJoin()连接线程等待退出状态pthread_cancel()ThreadCancel()在下一个取消点取消—ThreadCtl()更改 QNX OS 特定线程特性pthread_mutex_lock()SyncMutexLock()锁定互斥锁pthread_mutex_unlock()SyncMutexUnlock()解锁互斥锁pthread_cond_wait()SyncCondvarWait()等待条件变量pthread_cond_signal()SyncCondvarSignal()信号条件变量pthread_getschedparam()SchedGet()获取调度参数/策略pthread_setschedparam()SchedSet()设置调度参数/策略2.5 线程调度调度决策何时发生—— 微内核在因内核调用、异常或硬件中断进入时进行调度。三种抢占情况情况行为线程阻塞从运行数组中移除下一个合格线程运行。解除阻塞时加入就绪队列末尾线程被抢占高优线程就绪时被抢占线程放入本优先级就绪队列开头线程主动让出通过 sched_yield()线程放入本优先级就绪队列末尾全局调度线程在所有进程中全局调度——不区分属于哪个进程。调度优先级每个线程分配一个优先级调度器通过比较 READY 线程的优先级选择下一个线程。2.6 同步服务原语进程间支持描述Mutexes✅带安全警告线程间数据的独占访问Condition Variables✅在条件满足前阻塞线程于临界区Barriers✅带安全警告强制线程在特定点等待直到全部到达Reader/Writer Locks✅带安全警告多读单写锁Semaphores✅通过 post/wait 控制线程唤醒/睡眠Send/Receive/Reply✅通过阻塞特性实现隐式同步Atomic Operations✅硬件直接处理不可中断所有列出的原语由内核实现原子操作除外由处理器直接处理。注意互斥锁、条件变量、屏障、读写锁和信号量只能分配在正常内存映射中——在未缓存内存中分配会导致调用时发生故障。2.7 时钟与定时器服务核心内核调用内核调用POSIX 等价描述ClockTime()clock_gettime/settime获取/设置系统时间ClockCycles()无返回自由运行的 64 位周期计数器值ClockId()clock_getcpuclockid返回进程/线程的 CPU 时间时钟 ID关键细节内核内部使用无符号 64 位数计从 1970 年 1 月 1 日开始的纳秒数——避免 2038 年问题覆盖范围从 1970 年到 2554 年多核系统上 ClockCycles() 的硬件必须在所有处理器上同步定时器类型绝对定时器 (absolute timer) — 特定固定时刻触发相对定时器 (relative timer) — 从当前时间偏移触发重复定时器 (repeating timer) — 自动重载并继续运行2.8 中断处理QNX OS 实现了调度用户线程处理硬件中断的低延迟路径。中断处理流程硬件设备断言中断可编程中断控制器 (PIC) 处理PIC 在 CPU 核心上断言中断触发异常内核异常处理程序执行处理程序识别中断为每个关联线程投递内部信号量标记线程为 READY处理程序屏蔽中断并发出 EOI解除阻塞的线程按正常调度规则调度调度的中断服务线程 (IST)“处理其硬件设备、取消中断屏蔽、然后阻塞等待下一个中断”IST 延迟“从硬件中断断言到 IST 返回内核后阻塞等待中断后第一条指令执行的时间”。2.9 CPU Offlining允许特权应用阻止 procnto 使用某个 CPU。用途降低工作负载时关闭 CPU临时在 QNX 外运行软件如 CPU 诊断使系统休眠使 CPU 下线步骤设置线程的处理器亲和性runmask设置策略为 SCHED_OFFLINING优先级高于该 CPU 上除 IPI IST 外的所有线程使用 TimerDelegate() 卸载软件定时器调用 SchedCtl(SCHED_PROCESSOR_OFFLINE)恢复 CPU 上线步骤通过 TimerDelegate(_NTO_TIMER_RECLAIM) 回收定时器调用 SchedCtl(SCHED_PROCESSOR_ONLINE)将策略设置为 SCHED_FIFO优先级降低到时钟 IST 优先级以下高优先级下受限的 C 库函数仅允许TimerDelegate(), SchedCtl(), InterruptDisable(), InterruptEnable(), ClockCycles(), clock_gettime*() with CLOCK_MONOTONIC/CLOCK_REALTIME, atomic_*()第三部分进程间通信 (IPC)3.1 概述IPC在将微内核从嵌入式实时内核转变为完整 POSIX 操作系统中发挥着基础作用。IPC 是将组件连接成一个有机整体的胶水。IPC 服务与实现位置服务实现位置消息传递内核信号内核POSIX 消息队列内核 外部进程管理共享内存进程管理器管道 (Pipes)外部进程FIFO外部进程3.2 同步消息传递QNX OS 中 IPC 的主要形式基于Send/Receive/Reply模型。客户端行为MsgSend() → 服务器尚未 MsgReceive()进入SEND-blocked状态服务器 MsgReceive() 后转为REPLY-blocked服务器 MsgReply() 后变为READY如果客户端发送时服务器已阻塞于 MsgReceive()“客户端线程立即变为 REPLY-blocked完全跳过 SEND-blocked 状态”服务端行为MsgReceive() 无待处理消息变为RECEIVE-blocked有消息已发送“MsgReceive() 立即返回消息”——服务器不阻塞MsgReply()从不阻塞服务器MsgReply() vs MsgError()MsgReply()返回状态 零或更多字节数据MsgError()仅返回状态两者都非阻塞3.3 通道与连接通道和连接是 QNX 消息传递架构的基础。概念创建函数描述ChannelChannelCreate()服务器接收消息的抽象对象ConnectionConnectAttach()客户端附加到通道的关系服务端事件循环模式chid ChannelCreate(flags); SETIOV(iov, msg, sizeof(msg)); for(;;) { rcv_id MsgReceivev(chid, iov, parts, info); switch(msg.type) { /* 处理消息 */ } MsgReplyv(rcv_id, iov, rparts); }通道的三个线程队列队列类型描述Receive listLIFO等待消息的线程Send list优先 FIFO已发送但尚未被接收的消息Reply list无序已被接收但尚未回复的消息3.4 脉冲 (Pulses)脉冲是固定大小的非阻塞消息携带 9 字节有效载荷8 位1 字节代码64 位8 字节数据“脉冲通常用于允许服务器通知客户端而不阻塞它们”。3.5 优先级继承与消息QNX OS 使用消息驱动的优先级继承机制防止优先级反转。工作原理服务器收到消息时其有效优先级变为与最高优先级发送者相同接收触发提升低优客户端发送时服务器接收消息时优先级改变发送触发提升高优客户端发送时服务器优先级在发送时改变服务器回复后优先级不会自动恢复禁用优先级继承在 ChannelCreate() 时指定_NTO_CHF_FIXED_PRIORITY标志。3.6 事件内核拥有处理事件的子系统。所有通知方法都构建在一个单一的、丰富的事件子系统之上。事件来源其他线程调用 MsgDeliverEvent()定时器到期POSIX 消息队列从空变为非空事件类型QNX OS 脉冲中断各种信号类型“强制解除阻塞事件”sigevent 机制客户端可以给服务器一个称为 sigevent 的数据结构作为cookie。服务器随后调用 MsgDeliverEvent() 时“微内核将 cookie 中编码的事件类型设置在客户端线程上”。安全/注册事件客户端可以注册事件以确保只获得它想要的事件并且没有人篡改过它们。使用 MsgRegisterEvent() 注册MsgUnregisterEvent() 注销。3.7 信号QNX 实现 “32 个标准 POSIX 信号和 POSIX 实时信号来自内核实现的 64 个统一功能信号集”。微内核调用到 POSIX 包装器内核调用POSIX 包装器目的SignalKill()kill(), pthread_kill(), raise(), sigqueue()在进程组、进程或线程上设置信号SignalAction()sigaction()定义接收信号时的动作SignalProcmask()sigprocmask(), pthread_sigmask()更改线程的信号屏蔽掩码SignalSuspend()sigsuspend(), pause()阻塞直到信号处理程序触发SignalWaitinfo()sigwaitinfo()等待信号并返回信息多线程信号规则同步信号 (SIGSEGV 等) 始终传递到故障线程不能被线程的信号掩码阻止信号动作在进程级别维护每个线程维护自己的信号掩码针对线程的信号仅到达该线程针对进程的信号到达第一个未阻塞该信号的线程3.8 POSIX 消息队列“一组非阻塞消息传递工具”需要 mqueue 服务器运行。与本地 QNX 消息的关键区别特性POSIX 消息队列QNX 本地消息阻塞发送者不需要阻塞同步阻塞排队多消息可排队无内置排队持久性独立于使用进程与进程关联消息长度固定无限制优先级继承不支持支持已知发送者未知已知文件系统映射所有消息队列出现在/dev/mqueue/下。3.9 共享内存提供最高带宽的 IPC 机制。“一旦创建了共享内存对象具有访问权限的进程可以使用指针直接读写”。同步要求通常与互斥锁或信号量结合使用以原子化更新共享数据。适用同步原语信号量POSIX 实时标准互斥锁POSIX 线程标准—— “通常比信号量更高效”效率更新大量数据块时最高效。3.10 管道与 FIFO特性PipesFIFOs命名未命名命名永久文件生命周期关闭后移除持久存在创建pipe() / popen()mkfifo() / mkfifo移除自动remove() / unlink() / rm典型用途兄弟进程间单向数据流无关进程间通信前提条件管道资源管理器 (pipe) 必须运行。第四部分微内核 Instrumentation概述微内核镜像procnto-smp-instr包含集成的跟踪和分析机制用于实时系统监控。在单 CPU 和 SMP 架构上均可工作。Instrumentation “uses very little overhead and gives exceptionally good performance.”无侵入设计无需修改源代码即可监控程序与内核的交互。用户可选择跟踪多少交互内核调用线程/进程状态变化其他系统活动中断所有这些被监控的活动统称为事件 (events)。事件控制实时系统生成大量事件数据量可能令人不知所措就数据量、处理需求和存储资源而言。但用户有直接的控制手段来调节数据发射量。数据解释每个事件记录包含高精度时间戳事件来源 CPU ID 号这使得诊断困难的时序问题成为可能特别是更可能在多处理器系统上发生的问题。主动跟踪除了内核被动、无侵入地监控进程/线程/系统状态外应用程序可通过专用 API 主动影响事件收集过程。Trace 库函数函数用途TraceEvent()核心跟踪入口trace_func_enter()标记函数进入trace_func_exit()标记函数退出trace_here()在当前位罝插入跟踪点trace_logb()/trace_logbc()记录二进制数据trace_logf()记录格式化数据trace_logi()记录整数数据trace_nlogf()/trace_vnlogf()命名格式化日志traceparser()解析跟踪数据traceparser_init()/traceparser_destroy()解析器生命周期traceparser_cs()/traceparser_cs_range()获取当前/范围状态traceparser_debug()调试解析器输出traceparser_get_info()检索解析器信息第五部分进程管理器 (procnto)概述进程管理器负责创建和管理多个 POSIX 进程每个进程可包含多个 POSIX 线程。它与微内核捆绑在单个模块procnto中是所有运行时 QNX 系统必需的。三大核心职责域描述进程管理进程创建、销毁和进程属性如 UID/GID内存管理内存保护能力、共享库、POSIX 进程间共享内存原语路径名管理管理资源管理器可附加到的路径名空间I/O 资源不属于微内核本身而是由可在运行时启动的资源管理器进程提供。通过标准 API资源管理器可将路径名空间的子集作为自己的权限域。架构关键点用户进程通过内核调用直接访问微内核函数进程管理器函数通过发送消息到 procnto使用MsgSend*()内核调用访问在 procnto 内执行的线程与其它进程中的线程以完全相同的方式调用微内核——共享地址空间不意味着特殊或私有接口所有线程使用相同的一致内核接口且在调用微内核时都执行特权切换第六部分动态链接基本概念动态链接让多个程序引用同一个共享库实例而非各自包含一份拷贝从而节省系统内存。关键组件组件描述链接器 (ld)编译后组合目标文件和归档文件重定位数据并解析符号引用运行时链接器 (ldqnx)程序运行时查找并加载共享对象。路径/usr/lib/ldqnx-64.so需包含在 OS 镜像中静态 vs 动态链接特性静态链接动态链接绑定时机链接时运行时文件扩展名.a归档.so共享对象可执行文件完全绑定部分绑定链接器仅记录函数在哪个共享对象中适用场景不确定运行时库版本测试新库节省内存多程序共享运行时灵活性示例通用磁盘驱动驱动启动后探测硬件发现硬盘动态加载io-blk代码处理磁盘块发现两个分区DOS Power-Safe运行时根据分区类型动态加载fs-dos.so和fs-qnx6.so通过推迟决定调用哪些函数增强了灵活性并减小了驱动体积。相关 APIdlopen(),dlclose(),dlsym(),dladdr(),dlerror()第七部分资源管理器 (Resource Managers)概述资源管理器是 QNX OS 中的用户级进程可动态启动和停止为各种设备硬件和虚拟提供接口。关键区别与传统操作系统中的设备驱动程序不同QNX 资源管理器与内核之间没有特殊绑定——“a resource manager looks just like any other user-level program。”管理的设备类型类型示例硬件设备串口、并口、网卡、磁盘驱动器虚拟设备/dev/null、网络文件系统、伪终端 (pty)客户端通信机制由于 QNX OS 是分布式微内核系统几乎所有非内核功能都由用户可安装的程序提供因此客户端程序与资源管理器之间需要清晰且定义良好的接口。资源管理器架构关键特性支持路径名空间映射 (pathname space mapping)定义良好的资源管理器接口提供通用资源管理器函数的库集合这些特性offers the developer unprecedented flexibility and simplicity in developing drivers for new hardware——对嵌入式系统至关重要。第八部分文件系统架构QNX 文件系统作为内核之外的资源管理器运行。应用程序通过消息传递与它们交互。关键架构特性文件系统可动态启动和停止多个文件系统可并发运行应用程序看到单一统一的路径名空间和接口每个文件系统在路径名空间声明挂载点资源管理器验证单个路径名组件的权限和访问授权支持的文件系统类型文件系统驱动读写特性Power-Safe (fs-qnx6.so)分区 177/178/179R/W防断电损坏日志式可靠性QCFS (fs-qcfs.so)分区 181只读文件和元数据压缩QTD (fs-qtd.so)分区 185取决于配置安全启动环境中的完整性保护IFS内置启动镜像只读OS 镜像内文件位于/proc/bootDOS FAT (fs-dos.so)分区 1/4/6/11/12/14R/WFAT12/16/32 透明访问Ext2 (fs-ext2.so)分区 131R/WLinux 磁盘分区透明访问UDF/ISO 9660 (fs-udf.so)无只读UDF ISO 9660 (Joliet/RRIP)FFS3 (devf-*)NOR FlashR/WQNX 特有的 POSIX 式闪存文件系统NFSv3 (fs-nfs3)网络R/W客户端文件访问转换为 NFS 请求Squashfs (fs-squash.so)只读只有读数据/inode/目录压缩Shmem (/dev/shmem)内核R/W每个 QNX 系统都有不支持许多 POSIX 语义第九部分字符 I/Oio-char 架构io-char 库充当应用程序与设备驱动之间的中介。每个字符设备关联三个 FIFO 队列三队列机制队列方向描述原始输入队列驱动 → io-char驱动放入接收数据io-char 在应用请求时消费。支持原始模式和编辑模式输出队列io-char → 驱动io-char 放入输出数据驱动在传输时消费。实现write-behind策略规范队列io-char 内部仅在编辑模式下使用。大小决定设备可处理的最大编辑输入行长度Write-Behind 策略io-char 仅在输出缓冲区满时才阻塞写入进程在此之前数据异步排队。新数据加入时 io-char 调用驱动的受信例程唤醒可能空闲的驱动。职责分工组件职责设备驱动向输入队列添加接收数据 / 从输出队列消费并传输数据io-char 模块高层逻辑输出传输何时暂停、接收数据如何回显等字符设备类型设备驱动控制台devc-con,devc-con-hid伪终端devc-pty串口devc-ser8250,devc-serpci,devc-serusb第十部分网络架构核心设计原则网络服务在内核之外运行与其他服务进程相同。提供单一统一接口无论配置和网络数量如何。关键能力网络驱动可动态启动和停止多种协议可以任何组合共存子系统组件组件描述网络管理器 (io-sock)原生网络子系统可执行文件共享库模块运行时加载的一个或多个模块协议和驱动包含在模块中如devs-em.so详细架构在线程模型方面参考High-Performance Networking Stack User’s Guide。第十一部分TCP/IP 网络TCP/IP 栈是io-sock网络栈的一部分“uses the common FreeBSD API”。Internet 协议的详细信息参考High-Performance Networking Stack User’s Guide的 Protocols 章节。第十二部分什么是实时 (Real Time)定义实时系统是指计算的正确性不仅取决于计算的逻辑正确性还取决于结果生成的时间。如果时序约束未满足系统失败已经发生。硬实时 vs 软实时类型定义硬实时 (Hard)“延迟的计算无价值延迟的后果可能对系统造成灾难”。所有活动必须按时完成软实时 (Soft)价值随延迟而递减。可容忍一定程度的延迟只要价值未降到零仅偏好运行实时活动但未正确考虑不可调度活动的 OS 应称为准实时 (quasi-realtime)或伪实时 (pseudo-realtime)。RTOS 五项要求 (OSRs)任务的固定优先级抢占式调度同步原语的优先级继承或优先级天花板模拟内核必须是可抢占的中断必须有固定的延迟上界需要嵌套中断支持OS 服务必须以客户端优先级执行——所有依赖服务继承该优先级优先级反转避免应用于所有共享资源RTOS vs 常规 OS关键区别是可预测性 (predictability)。常规 OS如 Linux使用公平策略调度线程不建立实时线程的优先级优势。在内核调用期间优先级信息通常丢失导致不可预测的延迟。QNX 微内核架构直接满足所有五项需求以固定优先级管理线程通过线程置换抢占所有设备驱动和 OS 服务以独立进程存在通过同步消息传递 IPC 访问——接收者继承客户端优先级这使原始实时活动的优先级贯穿所有服务和设备驱动请求 (OSR 5)相关参考文档用途Getting Started进程、线程、消息传递入门Programmer’s Guide编程指南SAT User’s GuideInstrumentation 详细使用io-sock User’s Guide网络栈详情Writing a Resource Manager资源管理器开发QNX Filesystem for SafetyQTD 安全文件系统