# 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 定义 |