简介《QNX实时操作系统开发手册中文》是一份面向嵌入式开发初学者与实时控制系统工程师的入门文档围绕QNX的多任务、微内核、优先级抢占调度、进程间通信、文件命令及应用程序开发展开帮助读者快速建立QNX开发环境并理解其编程模型。压缩包内共1个doc文件约2.2MB内容以中文手册形式组织包含QNX概述、系统结构、常用系统与文件操作命令以及开发过程实例适合按章节查阅和对照练习。目前已有3074人学习下载说明其在QNX入门资料中具有一定参考价值。手册对任务、消息、资源、节点等抽象概念有系统说明并结合C/C开发环境、编译器与库函数介绍能帮助读者掌握任务创建、任务管理和进程间通信等关键环节文中实例也可作为搭建实时控制、通信与多媒体处理应用的起步参考。1. 翻开一份二十年前的中文手册翻到一份 QNX 实时操作系统开发手册中文版版本号 1.1前言没有几句客套讲完背景直接进系统结构第二章一条一条贴命令实例第三章给应用程序开发流程。这类中文资料在这个系统上算稀缺品QNX 是 1982 年就推出的分布式实时操作系统微内核加一组共操作的管理器进程内核把网络集成进来也只有 148KB 量级任务按优先级抢占调度落地场景集中在实时控制、通信、多媒体这些对时间较真的地方。手册适合两类人手里有一台老控制设备、需要把程序重新编起来跑起来的维护开发者以及想弄清微内核加消息传递这套设计到底怎么落地的人。下面按“长什么样、怎么跑、怎么改”的顺序拆。2. 微内核、管理器与节点系统结构怎么落到目录上手册第 1.2 节把系统结构讲得很抽象任务、消息、资源、节点。真坐到一台设备前面这些概念对应的是/boot/sys下那几个文件、ps输出里的那几行、以及路径前面多出来的那个节点号。想把 QNX 上的程序改明白先得把这一层映射关系建立起来否则看到代码里Send来Send去完全没有实感改一个参数都得靠猜。2.1 一个内核加一群管理器量产机上为什么这么设计QNX 的微内核只负责调度、上下文切换、消息传递和中断这几件事文件系统、设备驱动、网络协议栈各自是独立的进程手册里叫“共操作的管理器”它们之间以及它们跟内核之间全部通过消息对话。手册那张结构图想表达的是“小组型”而不是“层次型”内核是召集人各个管理器是平级的组员没有谁必须经过谁才能干活。这种结构在实时场景里的直接好处是新加一个管理器不用碰内核内核体积和切换开销基本不变硬实时任务的最坏响应时间更容易估出来。代价也很明显一次文件读要穿过好几轮消息吞吐量拼不过把文件系统塞进内核的设计。手册里那句“内核小、上下文切换快在同样硬件条件下给实时应用留出更大余地”说的就是拿吞吐换确定性这笔账。拿ps的输出对照着看最直观下面这段是手册例 2.7 的原始输出# ps PID PGRP SID PRI STATE BLK SIZE COMMAND 1 1 0 30f READY 262066K /boot/sys/Proc32 -l 1 2 2 0 10r RECV 0 108K /boot/sys/Slib32 7083 7083 6 10o WAIT -1 36K /bin/sh 7090 7083 6 10o REPLY 1 24K ps这里每一列都能当诊断线索用。PRI是优先级数字后面跟的字母是调度策略标记f一般对应 FIFO 也就是同优先级不轮转r对应时间片轮转o是另一类非实时调度。STATE里的RECV表示这个任务正阻塞在接收消息上REPLY表示它发出去的消息还没被应答WAIT是等子进程或等某个事件。BLK说明它阻塞在哪个节点或哪个对象上-1这类值在排查挂死问题时比什么都好用。SIZE是当前占用的内存PID 1 那个 262066K 的数字别被吓到那是带单位换算的地址空间统计不是真的用了 256MB。所以每次接手一台不熟悉的设备我第一件事是ps看一眼:哪些进程常驻、优先级怎么排、有没有进程长期停在REPLY。停在REPLY的老进程十有八九是对面收消息的任务没写Reply后面第 5 章会专门说这个坑。2.2 任务、文件、设备、节点四个抽象各自的边界任务是可调度的执行单元等于一段执行代码加一块只能自己访问的私有数据。调度规则手册写得很干脆优先级驱动急者优先抢占同优先级的任务按时间片轮转低优先级任务必须等高优先级任务的 I/O 或其它操作挂起之后才能轮到如果高优先级任务一直就绪低优先级任务就一直没机会。这条规则带来的直接后果是写给 QNX 的后台任务必须自己让出 CPU不然界面或者通信任务会被饿死。文件这一层比一般系统丰富手册列了六种类型前五种由文件系统管理器 Fsys 管文件类型归谁管典型用途常规文件Fsys配置、日志、可执行文件目录文件Fsys目录树组织记录名字和权限符号链文件Fsys一个文件多个路径入口管道 / FIFOFsys共操作进程之间的 I/O 通道块特殊文件Fsys磁盘、磁带、分区字符特殊文件Dev控制台、串口等按字符读写的设备块特殊文件和字符特殊文件的区分值得记一下前者按块随机存取后者按字节流顺序读写应用程序通过文件接口访问设备不需要知道硬件细节这一层抽象和 UNIX 是一脉相承的。设备除了硬盘之外的外设都归这一类设备管理器 Dev 是进程和设备之间的接口。系统里硬盘和节点是编号的设备是命名的I/O 名字空间里统一挂/dev前缀比如控制台就是/dev/con1。写程序时不要硬编码设备名读配置传进来换一台机器设备节点很可能就不叫这个了。节点是一台独立的工作站。多个节点用局域网连起来共享打印机、硬盘这些资源从程序员角度看整个系统就是一台虚拟机所有任务都像在本机跑所有文件都像本机可访问。手册反复强调这一点因为它是后面所有跨机通信的地基。2.3 名字空间路径前面那个节点号是分布式的入口QNX 的资源透明存取落在一个很土但极其好用的约定上——资源名前加节点号。同一节点内的路径是普通的/300e/bin跨节点就是//2/300e/config中间那个2是节点号。程序里打开文件、读写设备、发消息写法不变转发由内核通过消息完成。# pwd /300e/bin # ls /dev # 本机设备 # ls //2/300e/config # 节点 2 上的配置目录 # cat //2/300e/config/comn1.sys # 直接读另一台机器的配置文件在一次现场调试里这条特性省掉了整个临时传文件的环节采集程序在节点 1 上跑配置放在节点 2 的/300e/config下改配置只要在节点 1 上cat过去或者用cp拷过去不用登录两台机器来回折腾。代价是网络断了之后这类路径会返回错误跨节点访问的代码必须把错误码判掉不能当本机文件用。对应的目录布局大致是这样手册例 2.5 里的ls就能对上路径放什么/boot/sysProc32、Slib32等系统进程和共享库管理器/bin用户命令与可执行程序/lib共享库/config系统与业务配置示例里是comn1.sys、qcdt.sys这类/src源码目录/dev设备名字空间这套布局不是强制的但手册全程按/300e这个前缀走跟着它建目录后面贴的命令和路径基本能直接抄。3. 进程间通信五条通道的选型与一段能跑的消息收发手册第 1.3 节那句话分量很重QNX 所有的系统服务、设备驱动和应用都依赖 IPC。也就是说你写文件、读设备、查进程底层全是消息在跑。真正要写业务代码时手册给了五条通道——内部任务消息、代理、信号、共享内存、消息队列选错了不是性能问题是会挂死。3.1 Send / Receive / Reply同步三原语写错一句就卡死内部任务消息是主力。发送方调Send把一段数据从自己的地址空间拷到接收方然后发送任务被挂起一直等到接收方Reply才醒。消息内容对系统没有任何意义系统不解释只搬运所以发什么结构由收发双方自己约定。这就是ps里REPLY状态的来源。先看客户端这边/* 显示任务向采集任务请求一个通道的当前值 */ #include stdio.h #include string.h #include sys/kernel.h typedef struct { int type; /* 请求类型服务端靠它分发别用裸数字散落在代码里 */ int ch; /* 通道号 */ long value; /* 应答里回填的采样值 */ } msg_t; int main(void) { pid_t srv; msg_t req, rep; int n; /* 名字服务服务端启动时把 sampler 注册进去客户端按名字找 pid */ srv name_locate(sampler); if (srv 0) { fprintf(stderr, sampler not running\n); return 1; } memset(req, 0, sizeof(req)); req.type 1; /* 1 读通道 */ req.ch 3; /* 参数依次是目标 pid、发送缓冲、发送字节数、应答缓冲、应答字节数 返回应答字节数Send 期间本任务一直阻塞直到对方 Reply 或出错 */ n Send(srv, req, sizeof(req), rep, sizeof(rep)); if (n 0) { perror(Send); return 1; } printf(ch%d %ld\n, rep.ch, rep.value); return 0; }服务端是这套机制的关键因为发送方能不能醒过来完全取决于它/* 采集任务收一条、读一次硬件、回一条 */ #include stdio.h #include string.h #include sys/kernel.h typedef struct { int type; int ch; long value; } msg_t; static long read_channel(int ch); /* 真正的硬件读取按板卡自己实现 */ int main(void) { pid_t pid; msg_t req, rep; int rcvid; pid name_attach(NULL, sampler, 0); /* 注册名字失败一般是重名 */ if (pid 0) { perror(name_attach); return 1; } for (;;) { rcvid Receive(0, req, sizeof(req)); /* 0 表示接收任何人的消息 */ if (rcvid 0) { perror(Receive); continue; } memset(rep, 0, sizeof(rep)); rep.ch req.ch; rep.value read_channel(req.ch); /* 这一句是给发送方的“后悔药”漏掉对方就永远停在 REPLY 状态 */ Reply(rcvid, rep, sizeof(rep)); } }参数说明几个容易搞混的Receive的第一个参数传 0 表示不限发送者传具体 pid 就只收那一个任务的消息做点对点链路时很有用返回值rcvid是本次接收的标识必须原样交给Reply它不是你自己的 pidSend的应答缓冲长度可以和发送长度不一样服务端回多少字节由Reply的第三个参数决定。消息体建议控制在几百字节以内大数据块走共享内存这是当年硬件条件下的通行做法放到今天依然能省掉不少拷贝开销。还有一种变体叫Creceive带超时的接收服务端在等待期间还要干别的事时用它比开一个专门的线程划算。3.2 代理和信号不想被阻塞时用什么通知代理proxy是一种非阻塞的单向消息。发送方触发一下就走不等待应答代理的唯一作用就是给拥有它的进程投递一条固定内容的消息。典型用法是中断处理程序或者定时器到点之后通知主任务主任务收到消息再去做具体的事。手册里那句“一个进程或一个中断处理程序可以在不被阻塞和不必等待回答的情况下向另一个进程发送消息”说的就是这个场景。信号是另一条路POSIX 兼容的信号集加上部分传统 UNIX 信号和系统特有信号。它的优点是异步打断能力强缺点是几乎带不了信息只能表达“发生了某件事”。所以常见分工是需要传数据用消息需要打断等待用信号需要高频轻量通知用代理。3.3 共享内存与消息队列快和稳的取舍共享内存是所有通道里最快的指针直接读写零拷贝。但手册特意加了一句警告共享内存只在同一物理节点上成立而且不加同步措施非常容易出错两个任务同时改一条记录而没协调其中一个修改就丢了。所以共享内存的完整用法一定是“共享内存 同步原语”成对出现写方拿锁、写、放锁读方拿锁、读、放锁跨节点直接放弃这条路改用消息。消息队列是另一套思路中间站着一个任务别人Send进来它立刻Reply把数据挂到自己内部的链表上真正的接收方直接向队列要数据。对发送方来说它是非阻塞的生产快、消费慢的场合特别合适比如告警上报、操作日志。代价是多了一次拷贝和一次常驻任务的调度开销队列长度也得自己控制不然内存会被撑爆。3.4 五条通道怎么选通道发送方是否阻塞能否跨节点适合场景主要坑内部任务消息阻塞到 Reply能请求/应答、远程调用漏 Reply 直接挂死代理不阻塞能中断通知、事件告警单向收不到回复信号不阻塞本机为主异常打断、进程控制带不了数据共享内存不阻塞不能大块数据、高频采样必须自己加锁消息队列不阻塞能日志、告警、削峰队列长度要封顶实际项目里通常是组合拳采集任务和界面任务之间用消息采样原始数据放共享内存中断通知走代理日志走队列。这套组合在手册的例子之外但从它的功能描述里能直接推出来。4. 从命令行到第一个可执行程序常用命令与开发流程手册第二章整整一章讲命令篇幅比 IPC 还多原因很实在设备上没有图形界面所有诊断都靠终端。这些命令跟 UNIX 高度相似但细节上有出入照搬 Linux 的习惯容易翻车。4.1 系统状态先搞清楚现在跑着什么pwd显示当前目录输出里以/开头的是绝对路径判断自己在文件系统哪个位置全靠它。who am i查自己的登录名和终端线路finger不带参数列本地登录用户带用户名查某个用户的详细信息不管对方在不在线都能查。date不带参数显示当前时间带格式串可以定制输出# date DATE: %m/%d/%y%nTIME: %H:%M:%S DATE: 04/30/01 TIME: 15:09:19这个格式串在写日志时很好用%n是换行把日期和时间拆成两行塞进日志头比一行长串好读。ps前面已经拆过列含义这里补一句实操排查“程序没反应”时先ps看目标任务停在哪个STATE再看BLK列。停在RECV是正常的等服务停在REPLY就是等别人回话停在WAIT要看它在等谁。sin是系统信息视图输出比ps多CODE和DATA两列看内存分布和代码段大小时用它# sin SID PID PROGRAM PRI STATE BLK CODE DATA -- -- Microkernel --- ----- --- 104480 0 1 1 /boot/sys/Proc32 30f READY --- 118k 1392k 2 2 /boot/sys/Slib32 10r RECV 0 53k 4096use是查命令用法的use use能查到它自己的说明use -a 文件名可以从可执行模块里把用法信息原样抽出来适合在没有联机文档的机器上现查。这条命令当年是救命稻草现在很多人不知道它存在。4.2 文本处理四件套配置文件比对全靠它们grep按模式搜文件模式是正则表达式加-i忽略大小写# grep -i protocol channel1.sys channel2.sys channel1.sys: Protocol: U4F channel2.sys: Protocol: CDT输出带文件名前缀多文件搜索时直接能看出是哪份配置不一致。diff找两个文件的差异输出里的是第一个文件的行是第二个文件的行# diff comn1.sys comn2.sys 6,7c6,7 rtu_codes(H): 2 yk_lock_check: NO --- rtu_codes(H): 1 yk_lock_check: YES第一行6,7c6,7的意思是把第一个文件的第 6 到 7 行改成第二个文件的第 6 到 7 行c是 change。现场比对两台设备的配置差异这一条命令比人工翻文件快十倍也避免了肉眼漏看。sort的排序规则要记一下跟直觉不完全一样数字开头的行排在字母开头之前大写字母开头排在小写之前*、%、这类符号按 ASCII 码排。wc统计行数、词数、字符数-l行、-w词、-c字符-h加表头-t不显示合计# wc -h dbms.cfg ser.cfg lines words bytes 291 595 5333 dbms.cfg 7 39 256 ser.cfg 298 634 5589 Total检查日志有没有被写爆、配置有没有被截断wc -l一眼就能判断。4.3 文件和目录几个不能想当然的地方cp拷贝文件目标已存在会被覆盖而且手册明确写了它不能复制目录。要整目录搬得配合别的手段或者用系统提供的目录拷贝选项具体看版本。这也是从别的系统迁过来最容易踩的一脚习惯性cp -r结果命令不认。mv同时管改名和移动两件事可以一起做# mv qcdt.sys channel1.sys # 同目录改名 # mv /300e/config/qcdt.sys /300e1/config/channel1.sys # 移动并改名rm删除文件-r递归删目录-f不提示直接删-i交互确认且优先级高于-f。手册特别标注了两点.和..禁止删除如果被删的是符号链接只删链接本身指向的目标文件还在。第二点在某些场景下是救命的也容易误判成“文件没删干净”。mkdir建目录-m指定权限-p父目录不存在时一起建。rmdir删目录目录非空会直接报Directory not empty必须先清空。-p可以连着把空的父目录一起删掉。4.4 权限位ls -l那一串字符怎么读# ls -l drwxr-xr-- 7 root root 4096 Apr 09 09:13 . drwxrwxr-x 18 root root 4096 Apr 28 16:49 .. drwxr-xr-- 2 root root 4096 May 14 13:25 bind开头是目录后面九个字符分三组依次是属主、同组、其它用户。r读、w写、x执行短横线表示没有该权限。上面bin这个目录属主可读写执行同组可读可执行其他人只可读。chmod改权限有两种写法符号法和八进制法。符号法是chmod whopermission fileswho取u属主、g同组、o其它、a全部授予-收回。注意who、、permission之间不能有空格这是手册专门提醒的加了空格命令就不认。八进制法更省事八进制权限0无1执行2写3写 执行4读5读 执行6读 写7读 写 执行# chmod 777 mkrtu # 所有用户可读可写可执行现场调试期用 777 图省事可以理解但上线前一定要收回成 755 或更严尤其是可执行文件放在别的用户可写的目录里。4.5 编译、运行与开机自启动代码写完之后就是落地。老系统的编译工具是命令行形式的 C 编译器cc一类命令跟现在的用法差别不大# cc -o sampler sampler.c # 编出可执行文件 # cp sampler /300e/bin/ # 放到目标目录 # /300e/bin/sampler # 手动拉起来试跑 # ps | grep sampler # 确认进程在自启动的做法是把启动命令写进系统初始化脚本位置随版本不同常见的是 Fsys 挂载完文件系统之后执行的那段脚本。写进去的时候注意三件事路径用绝对路径别依赖当前目录输出重定向到日志文件现场没人盯着屏幕拉起顺序放在设备管理器启动之后否则一开机就去打开设备会失败。5. 常见问题排查五条踩坑记录这些坑基本都不是语法问题是运行时行为理解不到位。每一条我都按当时的现象、定位到的原因、最后的解法记下来照着对号入座能省掉不少通宵。5.1 换一台机器程序就起不来库没跟着走现象同一份可执行文件在开发机上跑得好好的拷到现场设备上执行直接报找不到符号或者启动失败ps里根本看不到进程。原因动态链接的共享库没有同步到目标机。这类系统的程序依赖Slib32这类共享库管理器库要么在/lib下要么在程序指定的搜索路径里编译时链接的东西不等于运行时能找到的东西。解决把依赖库一起打包拷过去确认目标机上的库管理器进程已经起来再ls /lib对一遍文件名。稳妥的做法是编译时尽量静态链接掉那些不常变的基础库把运行时的依赖面压到最小。从那以后我每次部署都先在一台干净机器上冷启动验证一遍不在开发机上“应该没问题”。5.2 界面卡住不动对面没 Reply现象采集任务都正常唯独显示任务十几秒不动一次ps一看显示任务长期停在REPLY状态本文还有配套的精品资源点击获取