Inspiration
When we built our first QNX 8.0 project (qsentry), we kept asking the same sizing questions:
- How long does a
MsgSend()take? - Should this server be a thread or a separate process?
- How accurate is a 2 ms timer?
- What happens to all of this under load?
We could not find one small tool that answers them on any target and produces a report we can paste into a design review. So we wrote one.
What it does
ipcbench runs on the QNX target and measures, with ClockCycles():
MsgSend/MsgReceive/MsgReplyround trips, thread to thread and process to process, with payloads of 4 B, 64 B, 1 KiB, 16 KiB and 64 KiB;MsgSendPulsedelivery latency;- periodic timer interval error at 2 ms and 10 ms, plus the achieved rate and the
overrun count from
timer_getoverrun().
Every test runs three times: idle, with every CPU busy at a lower priority, and with every CPU busy at the same priority. It prints one machine-readable line per result.
ipcreport.py runs on the host (standard library only). It turns the console log
into a Markdown table, an SVG chart and a timer health verdict. The verdict
flags a run whose guest clock was coarse, so emulator artefacts are never
published as RTOS numbers.
run_qemu.cmd does everything on a plain Windows PC: it cross-compiles with SDP
8.0, builds a QNX 8.0 image with mkqnximage, boots it in QEMU, runs the
benchmark, lets the guest shut itself down, and writes the report.
Results (QNX 8.0 x86_64, QEMU TCG, 2 vCPUs, Windows 11 host)
Median latency in run 2, from the raw logs in the repo:
| idle | CPUs busy, lower prio | CPUs busy, same prio | |
|---|---|---|---|
| MsgSend 4 B, thread | 11.7 µs | 19.4 µs | 4.2 ms |
| MsgSend 4 B, other process | 48.4 µs | 70.5 µs | 4.2 ms |
| MsgSend 64 KiB, thread | 52.1 µs | 76.1 µs | 4.0 ms |
| Pulse | 8.9 µs | 10.8 µs | 4.7 ms |
- Priority isolation, measured. Load below the test's priority leaves p99 in the idle range (42 µs vs 51 µs for 4 B). The same load at equal priority costs a 4 ms round-robin timeslice.
- An emulator can lie about time. On the same PC, run 1 showed a 13.1 ms median error on a 2 ms timer, and run 2 showed 0.49 ms. The measurement code was the same in both runs. Our inference is that the host's timer resolution differed. The report now flags such runs as COARSE CLOCK.
These are emulator numbers. The shapes carry over, but the absolute values do not. Run the binary on a board for real figures.
How we built it
- C on the QNX 8.0 APIs:
ChannelCreate,ConnectAttach,MsgSend/MsgReceive/MsgReply,MsgSendPulse,timer_createwithSIGEV_PULSE_INIT,ClockCycles,ClockPeriod, andSCHED_FIFO/SCHED_RRthreads. - Results are buffered and printed only after each load phase, so console I/O never competes with the measurement.
- The statistics code is unit-tested on the host.
- The video is rendered from the raw logs by
tools/make_video.py.
Challenges
Three Windows-host problems cost us runs:
- Git's Unix
findshadowed Windowsfind.exein our wait loop. - A global
taskkill /im qemu-system-x86_64.exekilled a second QEMU instance. core.autocrlfchecked the target shell scripts out with CRLF line endings.
All three fixes are in the scripts and documented. The biggest surprise was the coarse guest clock in run 1, which is why the timer health check exists.
What's next
- Run it on a Raspberry Pi 4 with QNX 8.0 for hardware numbers.
- Add a priority-inheritance test (a low-priority server holding a mutex).
- Compare
MsgSendagainst shared memory plus a semaphore.