4、双系统通信机制:共享内存、消息队列、Socket通信、VirtIO

双系统协同开发,说白了就是让QNX和Linux两个系统能「说上话」。我刚开始做这个的时候,以为就是简单的网络通信,结果发现坑多得很。今天咱们就把四种主流通信机制掰开揉碎了讲清楚。

4.1 共享内存:最快的路,但也是最险的路

共享内存,顾名思义,就是两个系统共用一块物理内存。QNX和Linux各自映射到自己的虚拟地址空间,读写同一块数据。速度?那是真快,微秒级的延迟。

但问题来了——同步怎么办?我当年在一个车载项目中,QNX写数据,Linux读数据,结果Linux读到一半,QNX把数据改了,读出来的数据不完整。嗯,这就是典型的「竞态条件」。

避坑指南:共享内存必须配合同步机制使用。QNX端用pthread_mutex,Linux端用pthread_mutex或者futex。我曾经因为忘记加锁,导致数据错乱,排查了整整两天。

代码示例(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占用率飙升,后来换成共享内存才解决。

经验之谈:Socket通信的优点是灵活,可以跨网络。如果你需要远程调试或者监控,Socket是首选。但如果是本地通信,我建议优先考虑共享内存或消息队列。

代码示例(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都有对应的驱动。

注意事项:VirtIO的配置稍微复杂一些。你需要确保QNX和Linux的VirtIO驱动版本匹配。我曾经因为驱动版本不匹配,导致通信失败,排查了半天才发现是驱动问题。

代码示例(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、内存和中断的分配。到时候我会分享一些实际项目中的配置经验,敬请期待。