NVIPC 概述#

本节概述 NVIPC 消息传递功能。

NVIPC 消息传输#

为了实现低延迟和高性能,NVIPC 使用无锁内存池和无锁队列来传递消息。消息传输模块架构如下所示。

../../../_images/nvipc_message_transfer.png

NVIPC API 定义#

NVIPC 消息分为两部分

  • MSG:在 CPU 线程中运行的控制逻辑中处理。

  • DATA:使用在 CPU 线程或 GPU 上下文中运行的高性能计算处理。

结构体 nv_ipc_msg_t 被定义为表示通用的 NVIPC 消息,如下所示。

typedef struct

{

    int32_t msg_id;    // IPC message ID

    int32_t cell_id;   // Cell ID

    int32_t msg_len;   // MSG part length

    int32_t data_len;  // DATA part length

    int32_t data_pool; // DATA memory pool ID

    void\*   msg_buf;   // MSG buffer pointer

    void\*   data_buf;  // MSG buffer pointer

} nv_ipc_msg_t;

MSG 部分和 DATA 部分存储在不同的缓冲区中。MSG 部分是强制性的,DATA 部分是可选的。当没有 DATA 部分时,data_buf 为空。

NVIPC 在初始化时创建多个内存池,以管理 MSG 部分和 DATA 部分缓冲区。枚举类型 nv_ipc_mempool_id_t 被定义为内存池指示符。MSG 缓冲区从 CPU 共享内存池分配。DATA 缓冲区可以从 CPU 共享内存池或 CUDA 共享内存池分配。

typedef enum

{

    NV_IPC_MEMPOOL_CPU_MSG   = 0, // CPU SHM pool for MSG part

    NV_IPC_MEMPOOL_CPU_DATA  = 1, // CPU SHM pool for DATA part

    NV_IPC_MEMPOOL_CPU_LARGE = 2, // CPU SHM pool for large DATA part

    NV_IPC_MEMPOOL_CUDA_DATA = 3, // CUDA SHM pool for DATA part

    NV_IPC_MEMPOOL_GPU_DATA  = 4, // CUDA SHM pool which supports GDR copy

    NV_IPC_MEMPOOL_NUM       = 5

} nv_ipc_mempool_id_t;

并且在结构体中定义了一系列 API

struct nv_ipc_t

{

    // De-initiate and destroy the nv_ipc_t instance

    int (\*ipc_destroy)(nv_ipc_t\* ipc);

    // Memory allocate/release for TX side

    int (\*tx_allocate)(nv_ipc_t\* ipc, nv_ipc_msg_t\* msg, uint32_t options);

    int (\*tx_release)(nv_ipc_t\* ipc, nv_ipc_msg_t\* msg);

    // Memory allocate/release for RX side

    int (\*rx_allocate)(nv_ipc_t\* ipc, nv_ipc_msg_t\* msg, uint32_t options);

    int (\*rx_release)(nv_ipc_t\* ipc, nv_ipc_msg_t\* msg);

    // Send a ipc_msg_t message. Return -1 if failed

    int (\*tx_send_msg)(nv_ipc_t\* ipc, nv_ipc_msg_t\* msg);

    // Call tx_tti_sem_post() at the end of a TTI

    int (\*tx_tti_sem_post)(nv_ipc_t\* ipc);

    // Call rx_tti_sem_wait() and then receive all messages in a TTI

    int (\*rx_tti_sem_wait)(nv_ipc_t\* ipc);

    // Get a ipc_msg_t message. Return -1 if no available.

    int (\*rx_recv_msg)(nv_ipc_t\* ipc, nv_ipc_msg_t\* msg);

    // Get SHM event FD or UDP socket FD for epoll

    int (\*get_fd)(nv_ipc_t\* ipc);

    // Write tx_fd to notify the event, Only need for SHM

    int (\*notify)(nv_ipc_t\* ipc, int value);

    // Read rx_fd to clear the event. Only need for SHM

int (\*get_value)(nv_ipc_t\* ipc);

    // More not used

};

无锁数据结构#

NVIPC 中实现了一个名为“数组队列”的无锁队列。数组队列具有以下特性

  • FIFO(先进先出)。

  • 无锁:支持无锁的多生产者和多消费者。

  • 有限大小:最大长度在初始化时定义:N。

  • 有效值是整数:0、 1、 …、 N-1, 可以用作节点索引/指针。

  • 不支持重复值。

基于无锁数组队列,实现了通用内存池和环形队列,它们也是无锁的

  • 内存池:数组队列 + 内存缓冲区数组

  • FIFO 环形队列:数组队列 + 元素节点数组

NVIPC 内存池#

NVIPC 中实现了几个共享内存池。主进程和辅助进程都可以访问它们。每个内存池都是固定大小缓冲区的数组。缓冲区大小和池长度(缓冲区计数)可通过 yaml 文件配置。如果缓冲区大小或池长度配置为 0,则不会创建该内存池。以下是 cuPHY-CP 中使用的默认 NVIPC 内存池配置。

内存池 ID

/dev/shm/ 的 SHM 文件名

注释

NV_IPC_MEMPOOL_CPU_MSG

<prefix>_cpu_msg

用于传输 MSG 部分的 CPU 内存。

NV_IPC_MEMPOOL_CPU_DATA

<prefix>_cpu_data

用于传输 DATA 部分的 CPU 内存。

NV_IPC_MEMPOOL_CPU_LARGE

<prefix>_cpu_large

用于传输大型 DATA 部分的 CPU 内存。

NV_IPC_MEMPOOL_CUDA_DATA

<prefix>_cuda_data

GPU 内存。未使用。

NV_IPC_MEMPOOL_GPU_DATA

<prefix>_gpu_data

具有 GDR 复制的 GPU 内存。未使用。

在 NVIPC 主应用程序初始化后,SHM 文件将出现在 /dev/shm/ 文件夹中。

双向消息队列#

将创建两个环形队列来传递消息缓冲区索引。发送器应用程序中的 TX 环和接收器应用程序中的 RX 环是系统中共享内存中的同一个环。DL 和 UL 环形队列都存储在同一个共享内存文件中。

/dev/shm/ 的 SHM 文件名

内部代码名称

IPC 方向

PHY /主

MAC /辅助

<prefix>_shm

<prefix>_ring_m2s

上行链路

TX

RX

<prefix>_shm

<prefix>_ring_s2m

下行链路

RX

TX

NVIPC 消息通知#

NVIPC 使用 Linux event_fd 进行通知。它支持使用 select /poll/epoll 机制的多个 I/O。消息通知模块架构如下所示。

../../../_images/msg_notification_module_arch.png

每个进程创建 一个 event_fd 文件描述符 efd_rx 用于传入消息通知,并将其与对等进程共享。本地 efd_rx 用于接收,在对等端共享为 efd_tx ``用于发送 。接收器进程可以调用 ``get_fd() 来获取 I/O 描述符,并使用 poll/epoll 获取通知。此外,event_fd 使用 EFD_SEMAPHORE 标志初始化,因此它可以像信号量一样工作。NVIPC 提供事件/选择样式和信号量样式通知 API。

NVIPC 消息流#

典型的消息传输流程如下所示。

../../../_images/message_transfer_flow_example.png

由于内存池和环形队列支持无锁并发,因此通知 API 的使用不是强制性的。如果用户不想使用通知,接收器应通过持续从无锁队列中出队来轮询传入的消息队列。rx_recv_msg() 函数在队列为空时返回 -1