# vlibmemory 设计分析文档 ## 1. 概述 vlibmemory 是 VPP (Vector Packet Processing) 框架中负责 **Binary API 通信** 的核心模块。它为外部控制平面应用程序(如 Python/Go/Java 客户端)与 VPP 数据平面之间提供了双向通信机制。 ### 设计目标 - **高性能本地通信**:通过共享内存 (SVM - Shared Virtual Memory) 和环形队列实现零拷贝消息传递 - **远程通信支持**:通过 Unix Domain Socket 实现跨进程/跨主机通信 - **无锁设计**:消息缓冲区采用环分配器 (ring allocator) 方案,避免锁竞争 - **多客户端支持**:支持多个控制平面应用同时连接 - **消息追踪**:内置 API 消息追踪 (trace) 功能,用于调试和回放 - **自动垃圾回收**:消息缓冲区具有基于时间戳的垃圾回收机制 - **保活检测**:自动检测并清理死客户端连接 ### 两种通信方式 | 特性 | 共享内存 (SHMEM) | Socket | |------|------------------|--------| | 性能 | 极高(零拷贝) | 中等 | | 适用场景 | 本地通信 | 跨进程/跨主机 | | 传输机制 | SVM + 环形队列 | Unix Domain Socket / TCP | | 消息格式 | msgbuf_t + 消息体 | msgbuf_t + 消息体(网络字节序) | --- ## 2. 架构设计 ### 2.1 整体架构 vlibmemory 采用 **服务端-客户端 (Server-Client)** 架构: - **服务端 (VPP/VLIB 侧)**:运行在 VPP 主进程中,负责创建共享内存区域、管理客户端注册、处理 API 请求 - **客户端 (控制平面)**:外部应用程序,通过链接 vpp-api-client 库连接 VPP ``` ┌─────────────────────────────────────────────────────────────────┐ │ VPP 主进程 (服务端) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ memory_api │ │ socket_api │ │ API 消息处理器 │ │ │ │ (共享内存) │ │ (Socket) │ │ (api_shared.c) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │ │ │ │ │ │ │ ┌──────┴─────────────────┴──────────────────────┴──────────┐ │ │ │ api_main_t (API 主控制结构) │ │ │ │ - msg_data[] (消息处理器向量) │ │ │ │ - vl_clients[] (客户端注册表) │ │ │ │ - rx_trace / tx_trace (消息追踪) │ │ │ └──────────────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────┴─────────────────────────────────┐ │ │ │ 共享内存区域 (SVM) │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ vl_shmem_hdr_t (共享内存头部) │ │ │ │ │ │ - vl_input_queue (客户端→VLIB 消息队列) │ │ │ │ │ │ - vl_rings[] (VLIB 消息缓冲区环) │ │ │ │ │ │ - client_rings[] (客户端消息缓冲区环) │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ 共享内存 (SVM) │ Socket │ │ ┌────────┴─────────┐ ┌────────┴──────────┐ │ 客户端进程 │ │ 客户端进程 │ │ (本地) │ │ (本地/远程) │ │ │ │ │ │ memory_client │ │ socket_client │ │ first_msg_id_reply│ │ client_index │ └───────────────────┘ └────────────────────┘ ``` ### 2.2 模块划分 | 模块 | 文件 | 职责 | |------|------|------| | 共享内存服务端 | `memory_api.c/h` | VPP 侧共享内存 API 处理 | | 共享内存客户端 | `memory_client.c/h` | 客户端共享内存连接管理 | | Socket 服务端 | `socket_api.c/h` | VPP 侧 Socket API 处理 | | Socket 客户端 | `socket_client.c/h` | 客户端 Socket 连接管理 | | 公共 API | `api_shared.c` | 消息分发、追踪、内存分配 | | 共享内存初始化 | `memory_shared.c` | SVM 区域创建、环分配器初始化 | | IDL 定义 | `memclnt.api` | API 消息定义 | --- ## 3. 核心数据结构详解 ### 3.1 vl_shmem_hdr_t — 共享内存头部 定义位置:[`memory_shared.h:63`](src/vlibapi/memory_shared.h:63) ```c typedef struct vl_shmem_hdr_ { int version; /* 共享内存版本号 (VL_SHM_VERSION = 2) */ volatile int vl_pid; /* VLIB 进程的 PID */ /* 客户端→VLIB 的消息队列 */ svm_queue_t *vl_input_queue; /* VLIB 分配缓冲区发送给客户端 */ ring_alloc_t *vl_rings; /* 客户端分配缓冲区发送给 VLIB */ ring_alloc_t *client_rings; /* 应用重启计数 (用于 epoch 检测) */ u32 application_restarts; /* 应用重启期间回收的消息数 */ u32 restart_reclaims; /* 垃圾回收的消息数 */ u32 garbage_collects; /* Socket 传递的文件描述符索引 */ u32 clib_file_index; } vl_shmem_hdr_t; ``` **作用**:共享内存区域的入口点,所有客户端和服务端通过此结构访问共享资源。 ### 3.2 ring_alloc_t — 环分配器 定义位置:[`memory_shared.h:22`](src/vlibapi/memory_shared.h:22) ```c typedef struct ring_alloc_ { svm_queue_t *rp; /* 环形队列指针 */ u16 size; /* 单个项的大小 */ u16 nitems; /* 项的数量 */ u32 hits; /* 分配命中统计 */ u32 misses; /* 分配未命中统计 */ } ring_alloc_t; ``` **作用**:为不同大小的消息提供无锁分配。系统预定义了多组环: ```c /* VLIB 侧环 (发送给客户端) */ #define foreach_vl_aring_size _(64+sizeof(ring_alloc_t), 1024) /* 64字节 × 1024个 */ _(256+sizeof(ring_alloc_t), 128) /* 256字节 × 128个 */ _(1024+sizeof(ring_alloc_t), 64) /* 1024字节 × 64个 */ /* 客户端侧环 (发送给 VLIB) */ #define foreach_clnt_aring_size _(1024+sizeof(ring_alloc_t), 1024) /* 1024字节 × 1024个 */ _(2048+sizeof(ring_alloc_t), 128) /* 2048字节 × 128个 */ _(4096+sizeof(ring_alloc_t), 8) /* 4096字节 × 8个 */ ``` ### 3.3 msgbuf_t — 消息缓冲区 定义位置:[`api_common.h:127`](src/vlibapi/api_common.h:127) ```c typedef struct msgbuf_ { svm_queue_t *q; /* 消息分配来源的环 */ u32 data_len; /* 消息体长度 (不含本头部) */ u32 gc_mark_timestamp; /* 垃圾回收标记时间戳 */ u8 data[0]; /* 实际消息数据 (柔性数组) */ } msgbuf_t; ``` **作用**:每个 API 消息前面都有一个 msgbuf_t 头部,用于内存管理和垃圾回收。 **内存布局**: ``` |← msgbuf_t →|← 消息体 (data_len 字节) →| +------------+-------------------------+ | q |len|gc | _vl_msg_id | ...数据... | +------------+-------------------------+ ``` ### 3.4 vl_api_registration_t — API 客户端注册 定义位置:[`api_common.h:37`](src/vlibapi/api_common.h:37) ```c typedef struct vl_api_registration_ { vl_registration_type_t registration_type; /* SHMEM / SOCKET_LISTEN / SOCKET_SERVER / SOCKET_CLIENT */ u32 vl_api_registration_pool_index; /* 在注册池中的索引 */ u8 *name; /* 客户端名称 */ /* 僵尸检测 */ f64 last_heard; /* 最后收到消息的时间 */ int last_queue_head; /* 上次队列头位置 */ int unanswered_pings; /* 未响应的 ping 数 */ int is_being_removed; /* 正在被移除 */ /* 共享内存专用 */ svm_queue_t *vl_input_queue; /* 客户端输入队列 */ svm_region_t *vlib_rp; /* SVM 区域 */ void *shmem_hdr; /* 共享内存头部 */ /* Socket 专用 */ u32 clib_file_index; /* 文件描述符索引 */ i8 *unprocessed_input; /* 待处理的输入数据 */ u32 unprocessed_msg_length; /* 未处理消息长度 */ u8 *output_vector; /* 输出缓冲区 */ int *additional_fds_to_close; /* 需要关闭的额外 FD */ /* Socket 客户端专用 */ u32 server_handle; /* 服务端句柄 */ u32 server_index; /* 服务端索引 */ bool keepalive; /* 是否启用保活 */ } vl_api_registration_t; ``` **作用**:记录每个已注册 API 客户端的完整状态。 ### 3.5 api_main_t — API 主控制结构 定义位置:[`api_common.h:246`](src/vlibapi/api_common.h:246) ```c typedef struct api_main_t { vl_api_msg_data_t *msg_data; /* 消息处理器向量 */ uword *msg_id_by_name; /* 消息名→ID 哈希表 */ struct ring_alloc_ *arings; /* 分配器环向量 */ u32 ring_misses; /* 环分配失败次数 */ u32 garbage_collects; /* 垃圾回收次数 */ u32 missing_clients; /* 丢失客户端次数 */ vl_api_trace_t *rx_trace; /* 接收消息追踪 */ vl_api_trace_t *tx_trace; /* 发送消息追踪 */ int msg_print_flag; /* 打印接收消息 */ int our_pid; /* 当前进程 PID */ svm_region_t *vlib_rp; /* 当前 SVM 区域 */ svm_region_t *vlib_primary_rp; /* 主 SVM 区域 */ svm_region_t **vlib_private_rps; /* 私有 SVM 区域向量 */ svm_region_t **mapped_shmem_regions; /* 所有映射的区域 */ struct vl_shmem_hdr_ *shmem_hdr; /* 共享内存头部 */ vl_api_registration_t **vl_clients; /* 客户端注册向量 (VPP 侧) */ u8 *serialized_message_table_in_shmem; /* 序列化消息表 */ u16 first_available_msg_id; /* 下一个可用消息 ID */ uword *msg_range_by_name; /* 消息范围哈希 */ vl_api_msg_range_t *msg_ranges; /* 消息范围向量 */ svm_queue_t *vl_input_queue; /* 对等输入队列 */ int my_client_index; /* 当前客户端索引 */ vl_api_registration_t *my_registration;/* 当前注册 */ /* ... 更多字段 ... */ } api_main_t; ``` **作用**:全局 API 系统的核心控制结构,通过线程局部变量 `my_api_main` 访问。 ### 3.6 vl_api_msg_data_t — 消息数据 定义位置:[`api_common.h:199`](src/vlibapi/api_common.h:199) ```c typedef struct { void (*handler)(void *); /* 消息处理函数 */ void (*cleanup_handler)(void *); /* 清理函数 */ const char *name; /* 消息名称 */ format_function_t *format_fn; /* 格式化函数 */ cJSON *(*tojson_handler)(void *); /* 二进制→JSON */ void *(*fromjson_handler)(cJSON *, int *); /* JSON→二进制 */ void (*endian_handler)(void *, bool); /* 字节序转换 */ uword (*calc_size_func)(void *); /* 计算大小 */ int trace_size; /* 追踪大小 */ u8 bounce : 1; /* 不自动释放 */ u8 is_mp_safe : 1; /* 多线程安全 */ u8 is_autoendian : 1; /* 自动字节序转换 */ u8 trace_enable : 1; /* 启用追踪 */ u8 replay_allowed : 1; /* 允许回放 */ } vl_api_msg_data_t; ``` ### 3.7 memory_client_main_t / socket_client_main_t **memory_client_main_t** ([`memory_client.h:14`](src/vlibmemory/memory_client.h:14)): ```c typedef struct { u8 rx_thread_jmpbuf_valid; /* 接收线程跳转缓冲区有效 */ u8 connected_to_vlib; /* 已连接到 VLIB */ jmp_buf rx_thread_jmpbuf; /* 接收线程跳转缓冲区 */ pthread_t rx_thread_handle; /* 接收线程句柄 */ volatile u8 first_msg_id_reply_ready; /* 插件消息 ID 回复就绪 */ u16 first_msg_id_reply; /* 插件消息 ID 回复 */ } memory_client_main_t; ``` **socket_client_main_t** ([`socket_client.h:12`](src/vlibmemory/socket_client.h:12)): ```c typedef struct { int socket_fd; /* Socket 文件描述符 */ int socket_enable; /* Socket 使能 */ u32 client_index; /* VPP 分配的客户端索引 */ clib_socket_t client_socket; /* Socket 对象 */ u32 socket_buffer_size; /* 缓冲区大小 */ u8 *socket_tx_buffer; /* 发送缓冲区 */ u8 *socket_rx_buffer; /* 接收缓冲区 */ int control_pings_outstanding; /* 未完成的控制 ping */ u8 *name; /* 客户端名称 */ clib_time_t clib_time; /* 时间对象 */ ssvm_private_t memfd_segment; /* 共享内存段 (memfd) */ int want_shm_pthread; /* 需要共享内存线程 */ } socket_client_main_t; ``` --- ## 4. 共享内存通信机制 ### 4.1 共享内存区域创建流程 ``` VPP 启动 客户端启动 │ │ ├─ vl_map_shmem(region_name, is_vlib=1) │ │ ├─ svm_region_find_or_create() │ │ │ └─ 创建 SVM 区域 │ │ ├─ vl_init_shmem() │ │ │ ├─ 创建 vl_shmem_hdr_t │ │ │ ├─ 创建 vl_input_queue │ │ │ ├─ 创建 vl_rings[] (VLIB 环) │ │ │ └─ 创建 client_rings[] (客户端环) │ │ └─ vlib_rp->user_ctx = shmem_hdr ──────┤ │ │ │ ├─ vl_map_shmem(region_name, is_vlib=0) │ │ ├─ 等待 shmem_hdr 就绪 │ │ └─ am->shmem_hdr = vlib_rp->user_ctx │ │ │ ├─ vl_client_connect() │ │ ├─ 创建客户端输入队列 │ │ └─ 发送 VL_API_MEMCLNT_CREATE │ │ │ │ VL_API_MEMCLNT_CREATE ◄──────────┤ │ ├─ 创建 vl_api_registration_t │ │ └─ 发送 VL_API_MEMCLNT_CREATE_REPLY │ │ │ VL_API_MEMCLNT_CREATE_REPLY ─────┤ │ │ └─ 记录 client_index, handle │ │ ``` ### 4.2 消息发送流程 (共享内存) ``` 发送方 接收方 │ │ │ 1. vl_msg_api_alloc(nbytes) │ │ ├─ 遍历环分配器 │ │ ├─ 检查 head 元素是否空闲 (q==0) │ │ ├─ 若空闲: 标记 q, 返回 data 指针 │ │ └─ 若全忙: 回退到 SVM malloc │ │ │ │ 2. 填充消息体 │ │ mp->_vl_msg_id = ntohs(MSG_ID) │ │ mp->context = ... │ │ │ │ 3. vl_msg_api_send_shmem(q, &mp) ──────┤ │ svm_queue_add(q, &mp, 0) │ │ │ 4. svm_queue_sub() 收到消息 │ │ ├─ vl_msg_api_handler() │ │ │ ├─ 提取 msg_id │ │ │ ├─ 查找 handler │ │ │ ├─ 字节序转换 │ │ │ └─ 调用 handler │ │ └─ vl_msg_api_free() │ │ └─ msgbuf->q = 0 (释放) ``` ### 4.3 消息缓冲区分配策略 分配函数 [`vl_msg_api_alloc_internal()`](src/vlibapi/memory_shared.c:35) 采用以下策略: 1. **环分配器优先**:根据消息大小选择合适的环,尝试从环的 head 位置获取空闲缓冲区 2. **空闲检测**:检查 `msgbuf_t.q` 字段,为 NULL 表示空闲 3. **垃圾回收**:如果缓冲区被占用超过 10 秒,强制回收(设置 `gc_mark_timestamp`) 4. **回退分配**:如果所有兼容环都忙,使用 SVM 的通用内存分配器 释放操作极其简单:将 `msgbuf_t.q` 设为 0,无需任何锁。 --- ## 5. Socket 通信机制 ### 5.1 Socket 服务端架构 ``` ┌─────────────────────────────────────────────┐ │ socket_main_t │ │ ┌───────────────────────────────────────┐ │ │ │ socket_name: "api.sock" │ │ │ │ bind_address: localhost │ │ │ │ registration_pool[] │ │ │ │ ├─ REGISTRATION_TYPE_SOCKET_LISTEN │ │ │ │ ├─ REGISTRATION_TYPE_SOCKET_SERVER │ │ │ │ │ ├─ vl_input_queue (N/A) │ │ │ │ │ ├─ clib_file_index │ │ │ │ │ ├─ unprocessed_input │ │ │ │ │ └─ output_vector │ │ │ │ └─ ... │ │ │ │ input_buffer[4096] │ │ │ │ socksvr_listen_socket │ │ │ └───────────────────────────────────────┘ │ └─────────────────────────────────────────────┘ ``` ### 5.2 Socket 连接流程 ``` VPP (服务端) 客户端 │ │ ├─ vl_sock_api_init() │ │ ├─ 创建监听 Socket │ │ └─ 注册文件事件回调 │ │ │ │ ├─ vl_socket_client_connect() │ │ ├─ socket() + connect() │ │ └─ 发送 VL_API_SOCKCLNT_CREATE │ VL_API_SOCKCLNT_CREATE ◄────────┤ │ ├─ 创建 registration │ │ └─ 发送 VL_API_SOCKCLNT_CREATE_REPLY │ │ │ VL_API_SOCKCLNT_CREATE_REPLY ────┤ │ │ └─ 记录 client_index │ │ ┌─ 可选: 建立共享内存加速通道 ─────────┐ │ │ 发送 VL_API_SOCK_INIT_SHM │ │ │ ├─ 创建 memfd 共享内存 │ │ │ └─ 通过 SCM_RIGHTS 传递 FD ────────┤ │ │ │ ├─ 接收 memfd │ │ │ └─ 映射共享内存 │ └────────────────────────────────────┘ ``` ### 5.3 Socket 消息格式 Socket 传输的消息与共享内存使用相同的 `msgbuf_t` 格式: ``` |← msgbuf_t 头部 →|← 消息体 (data_len 字节) →| +-----------------+--------------------------+ | q (8) |len(4)|gc(4)| _vl_msg_id(2) | ... | +-----------------+--------------------------+ ``` - `q` 字段在 Socket 传输中设为 0(不使用环分配器) - `data_len` 使用网络字节序 (htonl) - 所有消息字段使用网络字节序 ### 5.4 Socket 读取处理 [`vl_socket_read_ready()`](src/vlibmemory/socket_api.c:257) 处理 Socket 数据读取: 1. 从 Socket 读取数据到 `input_buffer` 2. 处理不完整消息(保存到 `unprocessed_input`) 3. 对每个完整消息,发送 `SOCKET_READ_EVENT` 事件 4. [`vl_socket_process_api_msg()`](src/vlibmemory/socket_api.c:188) 调用 `vl_msg_api_socket_handler()` 处理 --- ## 6. 消息处理流程 ### 6.1 完整消息处理链路 ``` 客户端侧 VPP 侧 │ │ 发送消息: │ │ ┌────────────────┐ │ ┌──────────────────────────────────┤ │vl_msg_api_alloc│ │ │ vl_msg_api_alloc() │ │ 环分配器 │ │ │ 或 SVM malloc │ └───────┬────────┘ │ └──────────────────────────────────┘ │ │ ┌───────┴────────┐ │ │填充消息体 │ │ │mp->_vl_msg_id │ │ └───────┬────────┘ │ │ │ ┌───────┴────────┐ │ ┌──────────────────────────────────┐ │vl_msg_api_ │ │ │vl_mem_api_handle_msg_main() │ │send_shmem() │ │ │ 从 vl_input_queue 取消息 │ │svm_queue_add() │──┼──│ svm_queue_sub() │ └────────────────┘ │ └──────────┬───────────────────────┘ │ │ │ ┌────────┴────────────────────────┐ │ │vl_msg_api_handler() │ │ │ 1. 提取 msg_id (前2字节) │ │ │ 2. 查找 msg_data[msg_id] │ │ │ 3. TX 追踪 (如果启用) │ │ │ 4. 字节序转换 (is_autoendian) │ │ │ 5. 调用 handler() │ │ │ 6. 释放消息 (bounce==0) │ │ └─────────────────────────────────┘ │ 接收消息: │ ┌────────────────┐ │ │rx_thread_fn() │ │ │ 循环: │ │ │ svm_queue_sub()│ │ │ vl_msg_api_ │ │ │ handler() │ │ └────────────────┘ │ │ ``` ### 6.2 消息分发核心函数 [`vl_msg_api_handler()`](src/vlibapi/api_shared.c) 是消息分发的核心: ```c void vl_msg_api_handler (void *the_msg, uword msg_len) { api_main_t *am = vlibapi_get_main(); u16 id = clib_net_to_host_u16 (*((u16 *) the_msg)); vl_api_msg_data_t *m = vl_api_get_msg_data(am, id); // 1. 追踪 if (am->rx_trace && am->rx_trace->enabled) vl_msg_api_trace(am, am->rx_trace, the_msg); // 2. 字节序转换 if (m && m->is_autoendian) m->endian_handler(the_msg, 0 /* to host */); // 3. 调用处理器 if (m && m->handler) m->handler(the_msg); // 4. 释放消息 (除非 bounce==1) if (!m->bounce) vl_msg_api_free(the_msg); } ``` ### 6.3 VPP 侧消息处理节点 VPP 使用一个专门的 process 节点 `vl_mem_api_handle_msg_main` 处理 API 消息: ```c int vl_mem_api_handle_msg_main (vlib_main_t *vm, vlib_node_runtime_t *node) { while (1) { // 等待事件 (队列信号 / Socket 读取 / RPC) vlib_process_wait_for_event_or_clock(vlib_main, ...); switch (event_type) { case QUEUE_SIGNAL_EVENT: // 从共享内存队列取消息处理 while (svm_queue_sub(q, &msg, SVM_Q_NOWAIT, 0) == 0) { vl_msg_api_handler(msg, ...); } break; case SOCKET_READ_EVENT: // 处理 Socket 消息 vl_socket_process_api_msg(rp, data); break; case CLOCK_EVENT: // 保活扫描 vl_mem_api_dead_client_scan(am, shm, now); break; } } } ``` --- ## 7. 内存管理 ### 7.1 环分配器工作原理 环分配器是 vlibmemory 内存管理的核心创新,实现了**无锁分配/释放**: ``` 环分配器 (ring_alloc_t) │ └─ svm_queue_t (环形队列) │ ├─ data[] (固定大小的缓冲区数组) ├─ head (下一个可分配位置) ├─ maxsize (总容量) └─ cursize (当前已分配数量) 分配流程: 1. 读取 q->head 位置的缓冲区 2. 检查 msgbuf_t.q 是否为 NULL ├─ NULL → 空闲,分配成功 │ ├─ 设置 msgbuf_t.q = 当前环 │ ├─ q->head++ │ └─ 返回 data 指针 └─ 非 NULL → 正在使用中 ├─ 检查 gc_mark_timestamp │ ├─ 首次标记: 设置时间戳 │ └─ 超过10秒: 强制回收 (垃圾回收) └─ 尝试下一个更大的环 3. 所有环都失败 → 使用 SVM malloc ``` ### 7.2 释放机制 释放操作极其简单高效: ```c void vl_msg_api_free (void *a) { msgbuf_t *rv = (msgbuf_t *) (((u8 *) a) - offsetof(msgbuf_t, data)); if (rv->q) { // 来自环分配器: 只需清零 q 字段 rv->q = 0; rv->gc_mark_timestamp = 0; return; } // 来自 SVM malloc: 需要真正释放 void *oldheap = vl_msg_push_heap(); clib_mem_free(rv); vl_msg_pop_heap(oldheap); } ``` **关键设计**:由于消息缓冲区在同一时刻只属于一个所有者(发送方或接收方),释放时不需要任何锁。 ### 7.3 垃圾回收机制 当环分配器中的所有缓冲区都被占用时,系统会尝试回收"卡住"的缓冲区: 1. **首次检测**:设置 `gc_mark_timestamp = 当前时间` 2. **再次检测**:如果 `当前时间 - gc_mark_timestamp > 10秒`,强制回收 3. **统计**:`shmem_hdr->garbage_collects` 记录回收次数 --- ## 8. 客户端注册与生命周期 ### 8.1 客户端注册类型 ```c typedef enum { REGISTRATION_TYPE_FREE = 0, /* 空闲 */ REGISTRATION_TYPE_SHMEM, /* 共享内存连接 */ REGISTRATION_TYPE_SOCKET_LISTEN, /* Socket 监听 */ REGISTRATION_TYPE_SOCKET_SERVER, /* Socket 服务端 */ REGISTRATION_TYPE_SOCKET_CLIENT, /* Socket 客户端 */ } vl_registration_type_t; ``` ### 8.2 共享内存客户端生命周期 ``` 客户端 VPP │ │ 1. 连接 │ │ │─ vl_client_api_map(region_name) ───────►│ │ (映射共享内存) │ │ │ 2. 注册 │─ vl_client_connect(name) ──────────────►│ │ 发送 VL_API_MEMCLNT_CREATE │ │ (包含: name, ctx_quota, input_queue) │ │ │ │ │ 处理创建请求 │ │ ├─ pool_get(vl_clients) │ │ ├─ 设置 registration │ │ └─ 发送 VL_API_MEMCLNT_CREATE_REPLY │ │ │◄── VL_API_MEMCLNT_CREATE_REPLY ─────────│ │ (包含: handle, index, message_table) │ │ 记录 client_index, my_registration │ │ │ 3. 正常通信 │─ 发送 API 请求 ─────────────────────────►│ │ │ 处理请求 │◄── 接收 API 回复 ───────────────────────│ │ │ 4. 保活 │ │ │ │ [定时] dead_client_scan() │ │ ├─ 检查 last_heard │ │ ├─ 如果超时: 发送 KEEPALIVE │◄── VL_API_MEMCLNT_KEEPALIVE ───────────│ │─ 发送 KEEPALIVE_REPLY ─────────────────►│ │ │ ├─ 更新 last_heard │ │ └─ 重置 unanswered_pings │ │ 5. 断开 │─ vl_client_disconnect() ───────────────►│ │ 发送 VL_API_MEMCLNT_DELETE │ │ │ 处理删除请求 │ │ ├─ 调用 reaper_functions │ │ ├─ 清理 registration │ │ └─ 发送 VL_API_MEMCLNT_DELETE_REPLY │ │ │◄── VL_API_MEMCLNT_DELETE_REPLY ─────────│ │ 清理本地资源 │ │ svm_queue_free(vl_input_queue) │ │ │ 6. 异常断开 (VPP 检测) │ │ │ [定时] dead_client_scan() │ │ ├─ unanswered_pings == 2 │ │ ├─ kill(q->consumer_pid, 0) │ │ │ ├─ 成功: 懒客户端, 重置 │ │ │ └─ 失败: 客户端已死 │ │ └─ 清理死客户端资源 ``` ### 8.3 保活机制详解 VPP 侧的保活扫描在 `vl_mem_api_dead_client_scan()` 中执行: 1. **快速检测**:如果队列头位置变化,说明客户端在处理消息,直接标记为活跃 2. **第一次 ping**:如果 10 秒未收到消息,发送 `VL_API_MEMCLNT_KEEPALIVE` 3. **第二次 ping**:如果仍未响应,再次发送 4. **进程检查**:如果 2 次 ping 都未响应,使用 `kill(pid, 0)` 检查进程是否存在 - 进程存在:标记为"懒客户端",重置计数器 - 进程不存在:标记为死客户端,清理资源 ### 8.4 Epoch 机制 客户端 handle 中包含 epoch 信息,用于检测应用重启: ```c #define VL_API_EPOCH_MASK 0xFF #define VL_API_EPOCH_SHIFT 8 // handle = (index << 8) | (epoch & 0xFF) u32 vl_msg_api_handle_from_index_and_epoch (u32 index, u32 epoch); // 验证 handle 是否有效 u8 vl_msg_api_handle_is_valid (u32 handle, u32 restarts); ``` 当 VPP 重启时,`application_restarts` 计数器增加,旧的 handle 将失效。 --- ## 9. Mermaid 图表 ### 9.1 整体架构图 ```mermaid graph TB subgraph VPP["VPP 主进程"] subgraph API["API 子系统"] AM[api_main_t
主控制结构] MA[memory_api.c
共享内存服务端] SA[socket_api.c
Socket 服务端] AS[api_shared.c
消息分发/追踪] MS[memory_shared.c
共享内存初始化] end subgraph SVM["共享内存区域 SVM"] SHM[vl_shmem_hdr_t
共享内存头部] VIQ[vl_input_queue
客户端→VLIB 队列] VLR[vl_rings 数组
VLIB 消息环] CLR[client_rings 数组
客户端消息环] end subgraph Node["VLIB 处理节点"] PMN[vl_mem_api_handle_msg_main
API 消息处理节点] end end subgraph Clients["客户端进程"] MC[memory_client.c
共享内存客户端] SC[socket_client.c
Socket 客户端] APP1[控制平面应用 1] APP2[控制平面应用 2] end APP1 --> MC APP2 --> SC MC -->|共享内存| SVM SC -->|Socket| SA SA --> AM MC --> MA MA --> SVM SVM --> SHM SHM --> VIQ SHM --> VLR SHM --> CLR MA --> PMN SA --> PMN PMN --> AS AS --> AM MS --> SVM ``` ### 9.2 共享内存消息发送流程图 ```mermaid flowchart TD Start([客户端发送 API 消息]) --> Alloc[vl_msg_api_alloc nbytes] Alloc --> CheckRing{遍历环分配器} CheckRing -->|找到合适环| CheckHead{检查 head 元素} CheckHead -->|q == 0 空闲| MarkUsed[设置 msgbuf.q = 当前环] CheckHead -->|q != 0 占用| CheckGC{gc_mark_timestamp} CheckGC -->|首次标记| SetTS[设置时间戳] CheckGC -->|超过10秒| ForceGC[强制垃圾回收] SetTS --> TryNext[尝试下一个环] ForceGC --> MarkUsed TryNext --> CheckRing CheckRing -->|无合适环| SVMAlloc[SVM malloc 回退] MarkUsed --> FillMsg[填充消息体] SVMAlloc --> FillMsg FillMsg[设置 _vl_msg_id / context / 数据] --> Send[vl_msg_api_send_shmem] Send --> Trace{TX Trace 启用?} Trace -->|是| RecordTrace[记录到 tx_trace] Trace -->|否| Enqueue[svm_queue_add 到目标队列] RecordTrace --> Enqueue Enqueue --> Notify[信号通知 VPP 处理] Notify --> End([完成]) ``` ### 9.3 消息处理序列图 ```mermaid sequenceDiagram participant C as 客户端应用 participant MC as memory_client
rx_thread participant Q as svm_queue
共享队列 participant V as VPP 进程
API 节点 participant H as 消息处理器
handler participant F as 内存释放 Note over C,F: 发送方向 (客户端 → VPP) C->>MC: vl_msg_api_alloc() MC->>Q: 从 client_rings 获取缓冲区 C->>MC: 填充消息体 MC->>MC: vl_msg_api_send_shmem() MC->>Q: svm_queue_add() Q-->>V: 队列信号通知 V->>Q: svm_queue_sub() 取消息 V->>V: 提取 msg_id (前2字节) V->>V: 查找 msg_data[msg_id] V->>V: 字节序转换 (如需要) V->>H: 调用 handler(msg) H-->>V: 处理完成 V->>F: vl_msg_api_free() F->>Q: msgbuf.q = 0 (归还环) Note over C,F: 回复方向 (VPP → 客户端) H->>V: vl_msg_api_alloc() V->>Q: 从 vl_rings 获取缓冲区 H->>V: 填充回复消息 V->>V: vl_msg_api_send_shmem(客户端队列) V->>Q: svm_queue_add() Q-->>MC: 消息就绪 MC->>Q: svm_queue_sub() 取消息 MC->>MC: vl_msg_api_handler() MC->>MC: 分发到对应 handler MC->>F: vl_msg_api_free() MC-->>C: 应用层收到回复 ``` ### 9.4 数据结构关系图 ```mermaid classDiagram class api_main_t { +vl_api_msg_data_t* msg_data (数组) +uword* msg_id_by_name +vl_api_registration_t** vl_clients +vl_shmem_hdr_t* shmem_hdr +svm_queue_t* vl_input_queue +vl_api_trace_t* rx_trace +vl_api_trace_t* tx_trace +svm_region_t* vlib_rp +int my_client_index +vl_api_registration_t* my_registration } class vl_shmem_hdr_t { +int version +int vl_pid +svm_queue_t* vl_input_queue +ring_alloc_t* vl_rings (数组) +ring_alloc_t* client_rings (数组) +u32 application_restarts +u32 garbage_collects } class ring_alloc_t { +svm_queue_t* rp +u16 size +u16 nitems +u32 hits +u32 misses } class msgbuf_t { +svm_queue_t* q +u32 data_len +u32 gc_mark_timestamp +u8 data[0] } class vl_api_registration_t { +vl_registration_type_t registration_type +u32 vl_api_registration_pool_index +u8* name +f64 last_heard +int unanswered_pings +svm_queue_t* vl_input_queue +svm_region_t* vlib_rp +void* shmem_hdr +bool keepalive } class vl_api_msg_data_t { +void(*handler)() +const char* name +void(*endian_handler)() +format_function_t* format_fn +cJSON*(tojson_handler)() +void*(fromjson_handler)() +u8 bounce +u8 is_mp_safe +u8 trace_enable } class vl_api_trace_t { +u8 enabled +u8 wrapped +u32 nitems +u32 curindex +u8** traces (数组) } api_main_t --> vl_shmem_hdr_t : 指向 api_main_t --> vl_api_registration_t : 管理池 api_main_t --> vl_api_msg_data_t : 消息表 api_main_t --> vl_api_trace_t : RX/TX 追踪 vl_shmem_hdr_t --> ring_alloc_t : vl_rings / client_rings ring_alloc_t --> msgbuf_t : 分配 vl_api_registration_t --> vl_shmem_hdr_t : 引用 ``` ### 9.5 客户端生命周期状态图 ```mermaid stateDiagram-v2 [*] --> 未连接 未连接 --> 映射共享内存: vl_client_api_map() 映射共享内存 --> 发送创建请求: vl_client_connect() state 发送创建请求 { [*] --> 等待回复 等待回复 --> 已注册: 收到 CREATE_REPLY } 发送创建请求 --> 已注册 发送创建请求 --> 连接失败: 超时 state 已注册 { [*] --> 活跃 活跃 --> 保活PING: 10秒无消息 保活PING --> 活跃: 队列头变化 保活PING --> 第二次PING: 未响应 第二次PING --> 活跃: 收到 KEEPALIVE_REPLY 活跃 --> 正常通信: 发送/接收 API 消息 正常通信 --> 活跃 } 已注册 --> 发送删除请求: vl_client_disconnect() state 发送删除请求 { [*] --> 等待删除回复 等待删除回复 --> 资源清理: 收到 DELETE_REPLY } 发送删除请求 --> 资源清理 资源清理 --> 解除映射: vl_client_api_unmap() 解除映射 --> 未连接 已注册 --> 异常断开: VPP 检测到客户端死亡 异常断开 --> 未连接: VPP 清理资源 连接失败 --> 未连接 ``` ### 9.6 Socket 通信流程图 ```mermaid sequenceDiagram participant APP as 客户端应用 participant SC as socket_client participant SOCK as Socket participant SM as socket_main
(VPP 侧) participant VPP as VPP 处理 Note over APP,VPP: 连接建立 APP->>SC: vl_socket_client_connect() SC->>SOCK: socket() + connect() SOCK-->>SM: 连接到达 SM->>SM: 创建 registration SM->>SOCK: 发送 SOCKCLNT_CREATE_REPLY SOCK-->>SC: 接收回复 SC-->>APP: 连接成功 Note over APP,VPP: 可选: 建立共享内存加速 APP->>SC: vl_socket_client_init_shm() SC->>SOCK: 发送 SOCK_INIT_SHM SOCK-->>SM: 接收请求 SM->>SM: 创建 memfd SM->>SOCK: 发送 memfd (SCM_RIGHTS) SOCK-->>SC: 接收 memfd SC->>SC: 映射共享内存 Note over APP,VPP: 消息传输 APP->>SC: 准备 API 消息 SC->>SC: vl_socket_client_msg_alloc() SC->>SOCK: write(msgbuf + 消息体) SOCK-->>SM: 数据到达 SM->>SM: vl_socket_read_ready() SM->>SM: 解析 msgbuf_t SM->>VPP: vl_msg_api_socket_handler() VPP->>VPP: 处理消息 VPP->>SM: 准备回复 SM->>SM: vl_socket_api_send() SM->>SOCK: write(msgbuf + 回复) SOCK-->>SC: 接收回复 SC->>SC: vl_socket_client_read() SC->>SC: vl_msg_api_socket_handler() SC-->>APP: 应用层收到回复 Note over APP,VPP: 断开连接 APP->>SC: vl_socket_client_disconnect() SC->>SOCK: close() SC->>SC: 清理共享内存 (如有) ``` --- ## 10. 关键设计总结 ### 10.1 无锁消息缓冲区设计 vlibmemory 最核心的设计创新是无锁消息缓冲区管理: - **所有权模型**:每个缓冲区同一时刻只有一个所有者 - **释放即标记**:释放只需将 `msgbuf_t.q` 设为 0 - **分配即检查**:分配时检查 `q` 字段判断空闲状态 - **垃圾回收兜底**:长时间未释放的缓冲区强制回收 ### 10.2 双通道通信 - **共享内存通道**:本地高性能场景,零拷贝 - **Socket 通道**:通用场景,支持跨进程/跨主机 - **混合模式**:Socket 连接后可建立 memfd 共享内存加速 ### 10.3 消息处理统一性 无论消息来自共享内存还是 Socket,最终都通过 `vl_msg_api_handler()` 统一分发: ``` 消息来源 → 统一 handler → 字节序转换 → 业务处理器 → 内存释放 ``` ### 10.4 线程安全 - **主线程处理**:API 消息主要在 VPP 主线程处理 - **屏障同步**:非线程安全的消息通过 `vl_msg_api_barrier_sync()` 同步工作线程 - **is_mp_safe 标记**:标记消息是否可以在工作线程直接处理 ### 10.5 可扩展性 - **插件消息 ID 分配**:通过 `vl_msg_api_get_msg_ids()` 分配消息 ID 范围 - **消息表序列化**:创建客户端时传递完整消息表 - **版本协商**:共享内存头部包含版本号 --- ## 附录 A:API 消息 ID 分配 | 消息 ID 范围 | 用途 | |-------------|------| | 0-255 | 基础内存客户端消息 (memclnt_create/delete 等) | | 256+ | VPP 核心 API 消息 | | 动态分配 | 插件消息 (通过 first_available_msg_id) | ## 附录 B:关键文件索引 | 文件 | 路径 | 主要功能 | |------|------|----------| | memory_api.c | src/vlibmemory/memory_api.c | 共享内存服务端 | | memory_client.c | src/vlibmemory/memory_client.c | 共享内存客户端 | | socket_api.c | src/vlibmemory/socket_api.c | Socket 服务端 | | socket_client.c | src/vlibmemory/socket_client.c | Socket 客户端 | | api_shared.c | src/vlibapi/api_shared.c | 消息分发/追踪 | | memory_shared.c | src/vlibapi/memory_shared.c | 共享内存初始化/内存管理 | | api_common.h | src/vlibapi/api_common.h | 公共数据结构 | | memory_shared.h | src/vlibapi/memory_shared.h | 共享内存数据结构 | | memclnt.api | src/vlibmemory/memclnt.api | API 消息 IDL 定义 |