4、双系统通信机制:共享内存、消息队列、Socket通信、VirtIO
双系统协同开发,说白了就是让QNX和Linux两个系统能「说上话」。我刚开始做这个的时候,以为就是简单的网络通信,结果发现坑多得很。今天咱们就把四种主流通信机制掰开揉碎了讲清楚。
4.1 共享内存:最快的路,但也是最险的路
共享内存,顾名思义,就是两个系统共用一块物理内存。QNX和Linux各自映射到自己的虚拟地址空间,读写同一块数据。速度?那是真快,微秒级的延迟。
但问题来了——同步怎么办?我当年在一个车载项目中,QNX写数据,Linux读数据,结果Linux读到一半,QNX把数据改了,读出来的数据不完整。嗯,这就是典型的「竞态条件」。
代码示例(QNX端创建共享内存):
// QNX端
#include <sys/mman.h>
#include <fcntl.h>
int fd = shm_open("/my_shared_mem", O_CREAT | O_RDWR, 0666);
ftruncate(fd, 4096);
void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 写数据
sprintf((char*)ptr, "Hello from QNX");
// Linux端
#include <sys/mman.h>
#include <fcntl.h>
int fd = shm_open("/my_shared_mem", O_RDWR, 0666);
void *ptr = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);
// 读数据
printf("Received: %s\n", (char*)ptr);
4.2 消息队列:可靠但慢一点
消息队列,说白了就是两个系统之间通过一个队列传递消息。QNX的mqueue和Linux的mqueue是兼容的,这点很方便。
我个人习惯用消息队列传递控制指令,比如「启动摄像头」、「切换模式」这类小数据量的消息。为什么?因为消息队列自带同步和缓冲,不用担心数据丢失。
但要注意,消息队列的延迟比共享内存高一个数量级。我测过,QNX到Linux的消息队列延迟大概在几十微秒到几百微秒之间。如果你需要传输大量数据,比如视频流,那还是用共享内存吧。
代码示例(QNX端发送消息):
// QNX端
#include <mqueue.h>
mqd_t mq = mq_open("/my_queue", O_CREAT | O_WRONLY, 0666, NULL);
char msg[] = "Start camera";
mq_send(mq, msg, strlen(msg) + 1, 0);
// Linux端
#include <mqueue.h>
mqd_t mq = mq_open("/my_queue", O_RDONLY);
char buf[256];
unsigned int prio;
mq_receive(mq, buf, sizeof(buf), &prio);
printf("Received: %s\n", buf);
4.3 Socket通信:最通用,但开销最大
Socket通信,说白了就是网络通信。QNX和Linux之间可以用TCP或UDP通信。这种方式最通用,跨平台、跨网络都行。
但代价是什么?延迟高。我测过,QNX到Linux的Socket通信延迟大概在毫秒级别,比共享内存慢了几百倍。所以,Socket通信适合传输控制指令或者状态信息,不适合传输实时数据。
你想想看,如果两个系统在同一台物理机上,用Socket通信其实是在走回环接口(loopback),数据要经过协议栈,开销不小。我曾经在一个项目中,用Socket传输视频流,结果CPU占用率飙升,后来换成共享内存才解决。
代码示例(QNX端TCP服务器):
// QNX端
#include <sys/socket.h>
#include <netinet/in.h>
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8888),
.sin_addr.s_addr = INADDR_ANY
};
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
listen(sock, 5);
int client = accept(sock, NULL, NULL);
char buf[256];
recv(client, buf, sizeof(buf), 0);
printf("Received: %s\n", buf);
// Linux端
#include <sys/socket.h>
#include <netinet/in.h>
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8888),
.sin_addr.s_addr = inet_addr("192.168.1.100")
};
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
char msg[] = "Hello from Linux";
send(sock, msg, strlen(msg) + 1, 0);
4.4 VirtIO:虚拟化环境下的最佳选择
VirtIO,说白了就是虚拟化环境下的通信标准。如果你在QNX和Linux之间跑虚拟机,VirtIO是官方推荐的通信方式。
VirtIO的优势是什么?高效、标准化。它绕过了传统的网络协议栈,直接在虚拟设备和物理设备之间传递数据。延迟比Socket低,吞吐量比共享内存高。
我记得在一个项目中,QNX作为Host,Linux作为Guest,用VirtIO传输传感器数据。延迟稳定在几十微秒,CPU占用率也很低。嗯,这里要注意,VirtIO需要两端都支持,QNX和Linux都有对应的驱动。
代码示例(QNX端VirtIO通信):
// QNX端
#include <virtio/virtio.h>
virtio_device_t *dev = virtio_device_open("virtio-net", 0);
virtio_queue_t *queue = virtio_queue_create(dev, 256);
// 发送数据
char data[] = "Hello from QNX";
virtio_queue_add_outbuf(queue, data, sizeof(data));
virtio_queue_kick(queue);
// Linux端
#include <linux/virtio.h>
struct virtio_device *dev = virtio_device_get("virtio-net");
struct virtqueue *vq = dev->vqs[0];
// 接收数据
char buf[256];
unsigned int len;
virtqueue_get_buf(vq, &buf, &len);
printf("Received: %s\n", buf);
4.5 如何选择?一张表搞定
说了这么多,到底该用哪种?我整理了一张表,你一看就明白:
| 通信方式 | 延迟 | 吞吐量 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 共享内存 | 微秒级 | 高 | 中 | 大量数据、实时性要求高 |
| 消息队列 | 几十微秒 | 中 | 低 | 控制指令、小数据量 |
| Socket通信 | 毫秒级 | 低 | 低 | 跨网络、远程调试 |
| VirtIO | 几十微秒 | 高 | 高 | 虚拟化环境、高性能需求 |
我个人建议:
- 如果两个系统在同一台物理机上,优先用共享内存或消息队列。
- 如果需要跨网络,用Socket。
- 如果跑虚拟机,用VirtIO。
- 如果数据量小且需要可靠传输,用消息队列。
好了,这四种通信机制就讲完了。下一章咱们聊聊双系统下的资源管理,包括CPU、内存和中断的分配。到时候我会分享一些实际项目中的配置经验,敬请期待。