InfiniBand 和 Linux
经过漫长的酝酿,InfiniBand (IB) 的应用正在兴起,并且正在进行将 IB 支持添加到 Linux 的工作。在物理层面上,IB 类似于 PCI Express。它使用多条高速串行通道传输数据。InfiniBand 规范的第一个版本仅允许每个通道的信令速率与 PCI Express 相同,为 2.5Gb/s。然而,最新版本的规范 (1.2) 增加了对每个通道 5Gb/s 和 10Gb/s 速率的支持。此外,IB 支持 1X、4X、8X 和 12X 的宽度,而 PCI Express 支持 X1、X2、X4、X8、X12、X16 和 X32。今天最常用的 IB 速度是 4X,速率为 2.5Gb/s/通道,总计 10Gb/s。但是,12X 宽度与 10Gb/s/通道速率相结合意味着当前的 IB 规范支持吞吐量惊人的 120Gb/s 的链路。
表 1. 对照表
技术 | 数据速率 | 线缆 |
---|---|---|
USB | 12Mb/s | 5米 |
高速 USB (USB 2.0) | 480Mb/s | 5米 |
IEEE 1394 (火线) | 400Mb/s | 4米 |
千兆以太网 | 1,000Mb/s | 100米(cat5 线缆) |
万兆以太网 | 10,000Mb/s | 10米(铜缆 IB 线缆),1+ 公里(光纤) |
Myrinet | 2,000Mb/s | 10米(铜缆),200米(光纤) |
1X InfiniBand | 2,000Mb/s | 10米(铜缆),1+ 公里(光纤) |
4X InfiniBand | 8,000Mb/s | 10米(铜缆),1+ 公里(光纤) |
12X InfiniBand | 24,000Mb/s | 10米(铜缆),1+ 公里(光纤) |
由于 IB 用于构建网络结构,因此 IB 同时支持铜缆和光纤布线,而 PCI Express 线缆规范仍在开发中。大多数 IB 安装使用铜缆(图 1),铜缆可用于长达约 10 米的距离。IB 还允许各种光纤布线选择,理论上允许链路长达 10 公里。
在过去几年中,IB 被推销为 PCI 的替代品,但现在不再期望如此。相反,IB 适配器应继续作为外围设备,通过 PCI、PCI Express、HyperTransport 或类似的外围总线连接到系统。
用于将系统连接到 IB 网络的网络适配器称为主机通道适配器 (HCA)。除了结构极高的速度外,IB HCA 还提供消息传递接口,允许系统利用 InfiniBand 提供的 10Gb/sec 或更高的吞吐量。为了利用 IB 的速度,支持零拷贝网络至关重要;否则,应用程序将把所有时间都花在复制数据上。
HCA 接口具有三个关键特性,使零拷贝成为可能:高级工作队列抽象、内核旁路和远程直接内存访问 (RDMA)。工作队列抽象意味着应用程序无需逐个构造和处理网络流量数据包,而是将工作请求发布到 HCA 处理的队列。使用单个工作请求发送的消息最长可达 4GB,HCA 负责将消息分解为数据包、等待确认并重新发送丢弃的数据包。由于 HCA 硬件负责传递大型消息,而无需 CPU 的任何参与,因此应用程序可以获得更多 CPU 时间来生成和处理它们发送和接收的数据。
内核旁路允许用户应用程序直接将工作请求发布到 HCA 队列并直接从 HCA 队列收集完成事件,从而消除切换到内核上下文和从内核上下文切换的系统调用开销。内核驱动程序设置队列,并使用标准内存保护来确保每个进程仅访问其自己的资源。但是,所有快速路径操作都纯粹在用户空间中完成。
最后一部分,RDMA,允许消息携带应写入内存的目标地址。指定数据所属的位置对于诸如通过 IB 提供存储之类的应用程序很有用,在这些应用程序中,服务器从磁盘的读取可能会乱序完成。如果没有 RDMA,服务器要么必须在有数据准备好发送时浪费时间等待,要么客户端必须浪费 CPU 功率将数据复制到其最终位置。
尽管远程系统在内存上乱写乱画的想法让一些人感到不安,但 IB 允许应用程序为 RDMA 设置严格的地址范围和权限。如果有什么不同的话,IB RDMA 比让磁盘控制器 DMA 进入内存更安全。
除了高性能之外,IB 还通过提供可以承载网络和存储流量以及集群通信的单个结构,简化了集群的构建和管理。许多组织已经指定了可以在 IB 上运行的各种上层协议,包括
IP-over-InfiniBand (IPoIB):互联网工程任务组 (IETF) 设有一个工作组,正在开发用于通过 IB 发送 IP 流量的标准跟踪草案。这些草案最终应形成 IPoIB 的 RFC 标准。但是,IPoIB 并未充分利用 IB 的性能,因为流量仍然通过 IP 堆栈并逐个数据包发送。IPoIB 提供了一种通过 IB 运行旧应用程序或发送控制流量的简单方法。
套接字直接协议 (SDP):InfiniBand 贸易协会本身已指定一种协议,该协议将标准套接字操作映射到本机 IB RDMA 操作。这允许套接字应用程序在不更改的情况下运行,并且仍然获得几乎所有 IB 的性能优势。
SCSI RDMA 协议 (SRP):负责 SCSI 标准的国际信息技术标准委员会 (INCITS) T10 委员会发布了将 SCSI 协议映射到 IB 的标准。目前正在开发第二代 SRP-2 协议。
许多其他组织也在研究和指定 IB 的使用,包括 DAT Collaborative 和开放组互连软件联盟的 API、用于 NFS 的 RDMA 绑定以及对各种 MPI 包的 IB 支持。
当然,如果没有开源支持,所有这些花哨的硬件功能对于 Linux 世界来说就没那么有趣了。幸运的是,OpenIB Alliance 是一个行业联盟,致力于准确地生产完整开源的 IB 堆栈。OpenIB 目前有 15 家成员公司,包括 IB 硬件供应商、服务器公司、软件公司和研究机构。
OpenIB 软件的工作始于 2004 年 2 月,第一个内核驱动程序于 2004 年 12 月合并到 Linux 树中,就在 2.6.10 版本发布后树为 2.6.11 打开之后。合并到内核的第一批代码是执行一些有用操作的最小 IB 驱动程序集。它包含一个中间层,用于从上层协议抽象低层硬件驱动程序、一个用于 Mellanox HCA 的单个低层驱动程序、一个 IPoIB 上层协议驱动程序以及一个允许子网管理器在用户空间中运行的驱动程序。
来自 IPoIB 驱动程序的一些代码片段应有助于理解如何使用内核的 IB 支持。要查看此代码的上下文,您可以查看完整的 IPoIB 驱动程序,该驱动程序位于 Linux 内核源代码的目录 drivers/infiniband/ulp/ipoib 中。
清单 1 显示了 IPoIB 驱动程序如何分配其所有 IB 资源。首先,它调用 ib_alloc_pd(),该函数分配一个保护域 (PD),这是一个不透明的容器,IB 的每个用户都必须拥有该容器才能容纳其他资源。
清单 1. IPoIB 驱动程序初始化
struct ib_qp_init_attr init_attr = { .cap = { .max_send_wr = IPOIB_TX_RING_SIZE, .max_recv_wr = IPOIB_RX_RING_SIZE, .max_send_sge = 1, .max_recv_sge = 1 }, .sq_sig_type = IB_SIGNAL_ALL_WR, .rq_sig_type = IB_SIGNAL_ALL_WR, .qp_type = IB_QPT_UD }; priv->pd = ib_alloc_pd(priv->ca); priv->cq = ib_create_cq(priv->ca, ipoib_ib_completion, NULL, dev, IPOIB_TX_RING_SIZE + IPOIB_RX_RING_SIZE + 1); if (ib_req_notify_cq(priv->cq, IB_CQ_NEXT_COMP)) goto out_free_cq; priv->mr = ib_get_dma_mr(priv->pd, IB_ACCESS_LOCAL_WRITE); init_attr.send_cq = priv->cq; init_attr.recv_cq = priv->cq, priv->qp = ib_create_qp(priv->pd, &init_attr);
顺便说一句,适当的错误检查已从清单中省略,尽管任何真正的内核代码都必须检查所有函数的返回值以查找失败。所有分配资源并返回指针的 IB 函数都使用标准的 Linux 方法,通过 ERR_PTR() 宏返回错误,这意味着可以使用 IS_ERR() 测试状态。例如,实际内核中对 ib_alloc_pd() 的调用实际上看起来像
priv->pd = ib_alloc_pd(priv->ca); if (IS_ERR(priv->pd)) { printk(KERN_WARNING "%s: failed " "to allocate PD\n", ca->name); return -ENODEV; }
接下来,驱动程序调用 ib_create_cq(),它创建一个完成队列 (CQ)。驱动程序请求在发生完成事件时调用函数 ipoib_ib_completion(),并且 CQ 能够容纳至少 IPOIB_TX_RING_SIZE + IPOIB_RX_RING_SIZE + 1 个工作完成结构。需要此大小来处理极端情况,即驱动程序发布其最大数量的发送和接收,然后直到它们都生成完成才运行。令人困惑的是,CQ 是唯一与 PD 无关的 IB 资源,因此我们不必将 PD 传递给此函数。
创建 CQ 后,驱动程序调用 ib_req_notify_cq() 以请求为添加到 CQ 的下一个工作完成调用完成事件函数。事件函数 ipoib_ib_completion() 处理完成,直到 CQ 为空。然后,它重复调用 ib_req_notify_cq(),以便在有更多完成可用时再次调用它。
然后,驱动程序调用 ib_get_dma_mr() 以设置可与从内核 DMA 映射 API 获取的 DMA 地址一起使用的内存区域 (MR)。转换表在 IB HCA 中设置以处理此问题,并返回一个本地密钥 (L_Key),可以将其传递回 HCA 以引用此 MR。
最后,驱动程序调用 ib_create_qp() 以创建队列对 (QP)。此对象称为队列对,因为它由一对工作队列组成——一个队列用于发送请求,一个队列用于接收请求。创建 QP 需要填写相当大的 ib_qp_init_attr 结构。cap 结构给出了要创建的发送和接收队列的大小。sq_sig_type 和 rq_sig_type 字段设置为 IB_SIGNAL_ALL_WR,以便所有工作请求都生成完成。
qp_type 字段设置为 IB_QPT_UD,以便创建不可靠的数据报 (UD) QP。IB QP 有四种可能的传输方式:可靠连接 (RC)、可靠数据报 (RD)、不可靠连接 (UC) 和不可靠数据报 (UD)。对于可靠的传输方式,IB 硬件保证所有消息要么成功传递,要么在发生不可恢复的错误(例如,电缆被拔掉)时生成错误。对于连接的传输方式,所有消息都发送到在设置 QP 时设置的单个目的地,而数据报传输方式允许每条消息发送到不同的目的地。
一旦 IPoIB 驱动程序创建了其 QP,它就会使用 QP 发送网络堆栈给它的数据包。清单 2 显示了将请求发布到 QP 的发送队列所需的操作。
清单 2. IPoIB 驱动程序发送请求发布
priv->tx_sge.lkey = priv->mr->lkey; priv->tx_sge.addr = addr; priv->tx_sge.length = len; priv->tx_wr.opcode = IB_WR_SEND; priv->tx_wr.sg_list = &priv->tx_sge; priv->tx_wr.num_sge = 1; priv->tx_wr.send_flags = IB_SEND_SIGNALED; priv->tx_wr.wr_id = wr_id; priv->tx_wr.wr.ud.remote_qpn = qpn; priv->tx_wr.wr.ud.ah = address; ib_post_send(priv->qp, &priv->tx_wr, &bad_wr);
首先,驱动程序为发送请求设置收集列表。lkey 字段设置为来自 ib_get_dma_mr() 的 MR 的 L_Key。由于 IPoIB 发送的数据包在一个连续的块中,因此收集列表只有一个条目。驱动程序只需分配数据包的地址和长度。收集列表中的地址是从 dma_map_single() 获取的 DMA 地址,而不是虚拟地址。通常,软件可以使用更长的收集列表来让 HCA 将多个缓冲区收集到单个消息中,以避免必须将数据复制到单个缓冲区中。
然后,驱动程序填写发送工作请求的其余字段。操作码设置为发送,sg_list 和 num_sge 为刚刚填写的收集列表设置,发送标志设置为已发出信号,以便工作请求在完成时生成完成。设置远程 QP 号和地址句柄,wr_id 字段设置为驱动程序的工作请求 ID。
填写完工作请求后,驱动程序调用 ib_post_send(),这实际上将请求添加到发送队列。当 IB 硬件完成请求时,工作完成将添加到驱动程序的 CQ,并最终由 ipoib_ib_completion() 处理。
InfiniBand 可以做很多事情,而 OpenIB Alliance 才刚刚开始编写软件来完成所有这些事情。现在 Linux 已经基本支持 IB,我们将实现更多上层协议,包括 SDP 和存储协议。我们正在解决的另一个主要领域是支持直接用户空间访问 IB——我们之前讨论过的内核旁路功能。在 IB 上有很多有趣的工作要做,OpenIB 项目对所有人开放,所以快来加入乐趣吧。
本文资源: /article/8131。
Roland Dreier 是通过 OpenIB.org 项目维护和领导 Linux InfiniBand 驱动程序的开发者。Roland 获得了加州大学伯克利分校的数学博士学位,并在学术研究和高科技领域担任过各种职位。自 2001 年以来,他一直在 Topspin Communications 工作。