3、多主机拓扑概述:多Root Complex拓扑、多主机通信模型、NTB原理

好,咱们进入第三章。多主机拓扑,这名字听着挺唬人,对吧?其实说白了,就是让多个CPU(也就是多个Root Complex)能通过同一个PCIe Switch互相通信。我最早接触这个,是在一个存储系统的项目里,两个服务器要共享同一块NVMe盘。那时候我就在想,这玩意儿到底怎么搞?

3.1 多Root Complex拓扑

传统的PCIe系统,一个RC管一片。但多主机场景下,一个Switch要连好几个RC。这就有意思了。

常见的拓扑有两种:

  • 对称拓扑:每个RC地位平等,都能访问共享资源。我见过不少高性能计算集群这么干。
  • 非对称拓扑:一个RC是主,其他是从。主节点控制全局,从节点干活。嗯,嵌入式系统里挺常见。

你想想看,如果两个RC同时往一个Endpoint写数据,会怎样?冲突!所以,PCIe Switch内部得做地址隔离和路由控制。我个人习惯,在设计初期就把地址映射表画清楚,不然后期调试能让你怀疑人生。

核心要点:多RC拓扑的关键,在于如何划分和管理每个RC的“势力范围”。

3.2 多主机通信模型

多主机之间怎么通信?总不能靠眼神吧。常见的模型有几种:

  1. 共享内存模型:所有主机都能访问同一块物理内存。简单粗暴,但需要锁机制。我在一个项目中用过,结果死锁了三次才调通。
  2. 消息传递模型:主机之间通过发送消息来通信。类似网络编程里的Socket,但延迟更低。
  3. DMA直接传输:一个主机通过DMA直接把数据写到另一个主机的内存里。效率最高,但实现最复杂。

这里有个坑:共享内存模型虽然快,但缓存一致性是个大问题。我曾经因为没处理好Cache Coherence,导致两个主机读到的数据不一样,查了整整两天。

我的建议:如果对延迟要求不高,优先用消息传递模型。调试起来省心很多。

3.3 NTB(Non-Transparent Bridge)原理

NTB,非透明桥。这名字起得挺玄乎,其实原理不复杂。

普通的PCIe桥(Transparent Bridge)是透明的,RC能看到桥后面的所有设备。但NTB不一样,它会在两个RC之间建立一个“秘密通道”。

NTB的核心机制:

  • 地址转换:NTB会把一个RC的地址空间,映射到另一个RC的地址空间里。说白了,就是“你的内存,我看得见,但你以为我看不见”。
  • 门铃机制(Doorbell):一个RC写一个寄存器,另一个RC就能收到中断。这就像按门铃,通知对方“我有数据给你”。
  • Scratchpad寄存器:用来交换一些控制信息,比如数据准备好了没。

我记得第一次调NTB时,门铃中断死活不触发。后来发现是中断路由配置错了。嗯,这里要注意,NTB的中断路由和普通设备不一样,得单独配。

避坑指南:我曾经因为NTB的地址转换窗口没对齐,导致数据写到一半就崩了。记住,窗口的基地址和大小必须是4KB对齐的。

下面这张图,是我画的一个典型NTB通信流程。你看看就明白了。

Root Complex 1 Root Complex 2 NTB 地址转换 + 门铃 共享内存 写门铃 读数据 DMA传输

流程很简单:RC1通过NTB写一个门铃,NTB收到后,通过DMA把数据从RC1的内存搬到共享内存,然后通知RC2来读。RC2看到数据,就知道RC1在叫它了。

最后,给个代码片段,展示NTB的地址映射配置。这是实际项目中用过的,你参考下。

// NTB地址映射配置示例
// 将RC1的0x1000_0000映射到RC2的0x2000_0000

ntb_config_t ntb_cfg;
ntb_cfg.rc1_base_addr = 0x10000000;  // RC1侧基地址
ntb_cfg.rc2_base_addr = 0x20000000;  // RC2侧基地址
ntb_cfg.window_size   = 0x00100000;  // 1MB窗口
ntb_cfg.doorbell_en   = 1;           // 使能门铃中断

// 配置NTB寄存器
ntb_set_window(0, &ntb_cfg);
ntb_enable_doorbell(0, DOORBELL_VEC_0);

嗯,多主机拓扑和NTB,说白了就是让多个CPU能“和平共处”地共享PCIe资源。我做了这么多年,觉得最难的不是技术本身,而是调试时的耐心。你想想看,两个RC同时跑,出问题了你都不知道是哪个RC的锅。

好了,这一章就到这儿。记住,多主机设计,地址映射和中断路由是命门,别搞错了。


公众号:蓝海资料掘金营,微信deep3321