1. 核心上下文管理
1.1 vfu_create_ctx
创建 libvfio-user 上下文。这是使用库的第一步。
vfu_ctx_t *
vfu_create_ctx(vfu_trans_t trans, const char *path,
int flags, void *pvt, vfu_dev_type_t dev_type);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
trans |
vfu_trans_t |
传输类型。VFU_TRANS_SOCK(UNIX socket)或 VFU_TRANS_PIPE(管道,仅内部测试用) |
path |
const char * |
socket 文件路径 |
flags |
int |
上下文标志。0(阻塞模式)或 LIBVFIO_USER_FLAG_ATTACH_NB(非阻塞模式) |
pvt |
void * |
用户私有数据指针,可通过 vfu_get_private() 获取 |
dev_type |
vfu_dev_type_t |
设备类型,当前仅支持 VFU_DEV_TYPE_PCI |
返回值:
- 成功:返回
vfu_ctx_t *指针 - 失败:返回
NULL,设置errno
说明:
- 默认会初始化 1 个 ERR IRQ 和 1 个 REQ IRQ,可通过
vfu_setup_device_nr_irqs()覆盖 - 内部会分配
VFU_PCI_DEV_NUM_REGIONS(9) 个 region 信息结构体 - 内部会调用 transport 的
init方法
示例:
vfu_ctx_t *vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "/tmp/vfio-user.sock",
0, &my_private_data, VFU_DEV_TYPE_PCI);
if (vfu_ctx == NULL) {
perror("vfu_create_ctx");
exit(1);
}
1.2 vfu_realize_ctx
完成设备初始化,使设备进入可 attach 状态。必须在 vfu_attach_ctx() 之前调用。
int
vfu_realize_ctx(vfu_ctx_t *vfu_ctx);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 如果未手动设置 config region,则创建默认的 256 字节 PCI config space
- 为各 BAR 区域设置类型标志(MEM/IO/64位/预取)
- 如果配置了 PCI capabilities,设置 status 寄存器的 capability list 位
- 初始化 IRQ 结构体(如果未手动设置)
- 设置
realized = true
示例:
int ret = vfu_realize_ctx(vfu_ctx);
if (ret < 0) {
perror("vfu_realize_ctx");
exit(1);
}
1.3 vfu_attach_ctx
尝试 attach 到 transport。必须在 vfu_run_ctx() 之前调用。
int
vfu_attach_ctx(vfu_ctx_t *vfu_ctx);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errnoEAGAIN/EWOULDBLOCK:transport 尚未就绪(非阻塞模式),需重试- 其他错误:attach 失败
说明:
- 如果上下文是用
LIBVFIO_USER_FLAG_ATTACH_NB创建的,此调用是非阻塞的 - 需要重试直到成功
示例:
int ret;
do {
ret = vfu_attach_ctx(vfu_ctx);
} while (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
if (ret < 0) {
perror("vfu_attach_ctx");
exit(1);
}
1.4 vfu_run_ctx
轮询 vfu_ctx 并处理来自客户端的请求。这是设备模拟的主循环。
int
vfu_run_ctx(vfu_ctx_t *vfu_ctx);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
返回值:
- 阻塞模式:持续处理请求,仅在错误或客户端断开连接时返回
- 非阻塞模式:处理一个请求(如果有),立即返回
- 成功时返回已处理的请求数(0 或更多)
- 失败时返回
-1,设置errno:ENOTCONN:客户端关闭连接,需重新调用vfu_attach_ctx()EBUSY:设备正在静默中EAGAIN:非阻塞模式下无请求
示例:
do {
ret = vfu_run_ctx(vfu_ctx);
} while (ret >= 0 || (ret == -1 && errno == EAGAIN));
if (ret == -1 && errno != ENOTCONN && errno != ESHUTDOWN) {
perror("vfu_run_ctx");
}
1.5 vfu_get_poll_fd
获取可用于 epoll() 或类似机制等待的文件描述符。
int
vfu_get_poll_fd(vfu_ctx_t *vfu_ctx);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
返回值:
- 成功:返回文件描述符
- 失败:返回
-1
说明:
- 文件描述符可能在
vfu_attach_ctx()成功后或vfu_run_ctx()返回ENOTCONN后改变,需要重新调用此函数获取
1.6 vfu_destroy_ctx
销毁 libvfio-user 上下文。调用时设备必须已处于静默状态。
void
vfu_destroy_ctx(vfu_ctx_t *vfu_ctx);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文,可以为 NULL |
说明:
- 会依次释放 config space、transport、DMA controller、sparse mmap areas、regions、migration 结构、IRQs 等资源
1.7 vfu_get_private
获取在 vfu_create_ctx() 中设置的私有数据指针。
void *
vfu_get_private(vfu_ctx_t *vfu_ctx);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
返回值:
- 返回在
vfu_create_ctx()中设置的pvt指针
2. 日志设置
2.1 vfu_setup_log
设置日志函数和日志级别。
int
vfu_setup_log(vfu_ctx_t *vfu_ctx, vfu_log_fn_t *log, int level);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
log |
vfu_log_fn_t * |
日志回调函数 |
level |
int |
日志级别,使用 syslog 级别(LOG_EMERG ~ LOG_DEBUG) |
日志回调签名:
typedef void (vfu_log_fn_t)(vfu_ctx_t *vfu_ctx, int level, const char *msg);
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno(EINVAL:级别无效)
示例:
static void my_log(vfu_ctx_t *vfu_ctx, int level, char const *msg) {
fprintf(stderr, "device: %s\n", msg);
}
vfu_setup_log(vfu_ctx, my_log, LOG_DEBUG);
2.2 vfu_log
使用上下文配置的日志函数记录日志(供内部使用,也可在你自己的回调中使用)。
void
vfu_log(vfu_ctx_t *vfu_ctx, int level, const char *fmt, ...)
__attribute__((format(printf, 3, 4)));
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
level |
int |
日志级别,使用 syslog 级别(LOG_EMERG ~ LOG_DEBUG) |
fmt |
const char * |
printf 风格的格式字符串 |
... |
- | 格式字符串对应的可变参数 |
3. PCI 设备初始化与配置
3.1 vfu_pci_init
初始化 PCI 设备上下文。每个 vfu_ctx 只能调用一次。
int
vfu_pci_init(vfu_ctx_t *vfu_ctx, vfu_pci_type_t pci_type,
int hdr_type, int revision);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
pci_type |
vfu_pci_type_t |
PCI 类型,可选值见下表 |
hdr_type |
int |
PCI 头类型,当前仅支持 PCI_HEADER_TYPE_NORMAL |
revision |
int |
PCI/PCI-X/PCIe 版本号(当前未使用) |
vfu_pci_type_t 枚举:
| 值 | 描述 | Config Space 大小 |
|---|---|---|
VFU_PCI_TYPE_CONVENTIONAL |
传统 PCI | 256 字节 |
VFU_PCI_TYPE_PCI_X_1 |
PCI-X 模式 1 | 256 字节 |
VFU_PCI_TYPE_PCI_X_2 |
PCI-X 模式 2 | 4096 字节 |
VFU_PCI_TYPE_EXPRESS |
PCI Express | 4096 字节 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 自动分配 config space 缓冲区
- 自动设置 config region(
VFU_PCI_DEV_CFG_REGION_IDX)的大小和读写标志
示例:
vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_EXPRESS, PCI_HEADER_TYPE_NORMAL, 0);
3.2 vfu_pci_set_id
设置 PCI 设备标识符(Vendor ID、Device ID、Subsystem Vendor ID、Subsystem ID)。
void
vfu_pci_set_id(vfu_ctx_t *vfu_ctx, uint16_t vid, uint16_t did,
uint16_t ssvid, uint16_t ssid);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
vid |
uint16_t |
Vendor ID |
did |
uint16_t |
Device ID |
ssvid |
uint16_t |
Subsystem Vendor ID |
ssid |
uint16_t |
Subsystem ID |
示例:
vfu_pci_set_id(vfu_ctx, 0x1af4, 0x1001, 0x1af4, 0x0010);
3.3 vfu_pci_set_class
设置 PCI Class Code(基本类、子类、编程接口)。
void
vfu_pci_set_class(vfu_ctx_t *vfu_ctx, uint8_t base, uint8_t sub, uint8_t pi);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
base |
uint8_t |
基本类码(Base Class Code) |
sub |
uint8_t |
子类码(Sub-Class Code) |
pi |
uint8_t |
编程接口(Programming Interface) |
说明:
- 如果不调用此函数,所有字段初始化为 0
示例(NVMe 控制器:Mass Storage / Non-Volatile Memory):
vfu_pci_set_class(vfu_ctx, 0x01, 0x08, 0x02);
3.4 vfu_pci_get_config_space
获取 PCI 配置空间指针。
vfu_pci_config_space_t *
vfu_pci_get_config_space(vfu_ctx_t *vfu_ctx);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
返回值:
- 返回
vfu_pci_config_space_t *,即 PCI 配置空间的指针
说明:
- PCI config space 由 64 字节的
vfu_pci_hdr_t标准头部,后接 capability 区域和设备特定配置区域组成 - 标准 config space 为 256 字节(
PCI_CFG_SPACE_SIZE) - 扩展 config space 为 4096 字节(
PCI_CFG_SPACE_EXP_SIZE) - 可以直接修改返回的 config space 来设置自定义 PCI 寄存器值
示例:
vfu_pci_config_space_t *cfg = vfu_pci_get_config_space(vfu_ctx);
cfg->hdr.intr.ipin = 1; // INTA#
3.5 vfu_pci_add_capability
向 PCI 配置空间添加 capability。
ssize_t
vfu_pci_add_capability(vfu_ctx_t *vfu_ctx, size_t pos, int flags, void *data);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
pos |
size_t |
capability 在 config space 中的偏移。0 表示自动分配位置 |
flags |
int |
capability 标志(见下表) |
data |
void * |
capability 数据(包括头部),会被复制到 config space 中 |
flags 标志:
| 标志 | 描述 |
|---|---|
VFU_CAP_FLAG_EXTENDED |
这是一个扩展 capability(需要 VFU_PCI_TYPE_PCI_X_2 或 VFU_PCI_TYPE_EXPRESS) |
VFU_CAP_FLAG_CALLBACK |
所有对该 capability 的访问委托给 VFU_PCI_DEV_CFG_REGION_IDX 的回调 |
VFU_CAP_FLAG_READONLY |
禁止客户端写入 capability(头部除外) |
| 组合使用 | 可以按位或组合上述标志 |
返回值:
- 成功:返回 capability 在 config space 中的偏移量
- 失败:返回
-1,设置errno
说明:
- 库内部已完全处理的 capability 类型:
PCI_CAP_ID_PM、PCI_CAP_ID_EXP、PCI_CAP_ID_MSI、PCI_CAP_ID_MSIX - 上述 capability 仍需显式在此函数中注册
- 支持的扩展 capability:
PCI_EXT_CAP_ID_DSN(设备序列号)、PCI_EXT_CAP_ID_VNDR(厂商特定) data必须从struct cap_hdr(普通)或struct pcie_ext_cap_hdr(扩展)开始,ID 字段必须已设置- 对于
PCI_CAP_ID_VNDR或PCI_EXT_CAP_ID_VNDR,内嵌的 size 字段也必须设置 - 最多支持
VFU_MAX_CAPS(128) 个普通 capability 和 128 个扩展 capability
示例(添加 MSI-X capability):
struct msixcap msix = { 0 };
msix.hdr.id = PCI_CAP_ID_MSIX;
msix.mxc.ts = 16; // Table size: 16 vectors
msix.mxc.mxe = 0;
msix.mxc.fm = 0;
// mtab 和 mpba 的 BIR 和 offset 根据实际 BAR 配置设置
msix.mtab.to = 0; // MSI-X Table offset in BAR
msix.mtab.tbir = 4; // BAR index for table
msix.mpba.pbao = 0x1000; // PBA offset in BAR
msix.mpba.pbir = 4; // BAR index for PBA
ssize_t offset = vfu_pci_add_capability(vfu_ctx, 0, 0, &msix);
if (offset < 0) {
perror("add MSI-X capability");
}
示例(添加 PCI Express capability):
struct pxcap px = { 0 };
px.hdr.id = PCI_CAP_ID_EXP;
px.pxcaps.ver = 2; // PCIe Gen2
px.pxcaps.dpt = 0; // Endpoint
px.pxdcap.mps = 0; // 128 bytes max payload
px.pxdcap.flrc = 1; // Support FLR
// ... 设置其他必要的字段
vfu_pci_add_capability(vfu_ctx, 0, 0, &px);
示例(添加 PM capability):
struct pmcap pm = { 0 };
pm.hdr.id = PCI_CAP_ID_PM;
pm.pc.vs = 2; // PCI PM v1.1
pm.pc.dsi = 1; // No device specific init
pm.pc.auxc = 0; // 0 mA auxiliary current
vfu_pci_add_capability(vfu_ctx, 0, 0, &pm);
3.6 vfu_pci_find_capability
查找指定 capability 在 config space 中的偏移。
size_t
vfu_pci_find_capability(vfu_ctx_t *vfu_ctx, bool extended, int cap_id);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
extended |
bool |
true 查找扩展 capability,false 查找普通 capability |
cap_id |
int |
capability ID(PCI_CAP_ID_* 或 PCI_EXT_CAP_ID_*) |
返回值:
- 成功:返回 capability 的偏移量
- 未找到:返回
0,设置errno(ENOENT)
3.7 vfu_pci_find_next_capability
从指定位置开始查找下一个匹配的 capability(用于遍历多个相同 ID 的 capability)。
size_t
vfu_pci_find_next_capability(vfu_ctx_t *vfu_ctx, bool extended,
size_t pos, int cap_id);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
extended |
bool |
是否查找扩展 capability |
pos |
size_t |
起始偏移(必须是现有 capability 的有效偏移) |
cap_id |
int |
capability ID |
返回值:
- 成功:返回下一个匹配 capability 的偏移量
- 未找到:返回
0,设置errno
4. 设备区域(Region)设置
4.1 vfu_setup_region
设置设备区域。区域是设备内存的可访问范围,客户端可通过 VFIO_USER_REGION_READ/WRITE 或通过 mmap 直接映射访问。
int
vfu_setup_region(vfu_ctx_t *vfu_ctx, int region_idx, size_t size,
vfu_region_access_cb_t *region_access, int flags,
struct iovec *mmap_areas, uint32_t nr_mmap_areas,
int fd, uint64_t offset);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
region_idx |
int |
区域索引(见下表) |
size |
size_t |
区域大小(字节) |
region_access |
vfu_region_access_cb_t * |
区域访问回调函数(可为 NULL) |
flags |
int |
区域标志(见下表) |
mmap_areas |
struct iovec * |
可 mmap 的子区域数组;可为 NULL |
nr_mmap_areas |
uint32_t |
mmap_areas 数组的元素数量 |
fd |
int |
支持该区域的文件描述符;不可 mmap 则为 -1 |
offset |
uint64_t |
区域在 fd 中的偏移量 |
区域索引(region_idx):
| 常量 | 值 | 描述 |
|---|---|---|
VFU_PCI_DEV_BAR0_REGION_IDX |
0 | BAR0 |
VFU_PCI_DEV_BAR1_REGION_IDX |
1 | BAR1 |
VFU_PCI_DEV_BAR2_REGION_IDX |
2 | BAR2 |
VFU_PCI_DEV_BAR3_REGION_IDX |
3 | BAR3 |
VFU_PCI_DEV_BAR4_REGION_IDX |
4 | BAR4 |
VFU_PCI_DEV_BAR5_REGION_IDX |
5 | BAR5 |
VFU_PCI_DEV_ROM_REGION_IDX |
6 | ROM |
VFU_PCI_DEV_CFG_REGION_IDX |
7 | 配置空间 |
VFU_PCI_DEV_VGA_REGION_IDX |
8 | VGA |
区域标志(flags):
| 标志 | 描述 |
|---|---|
VFU_REGION_FLAG_READ |
区域可读 |
VFU_REGION_FLAG_WRITE |
区域可写 |
VFU_REGION_FLAG_RW |
区域可读写(READ | WRITE) |
VFU_REGION_FLAG_MEM |
该区域为内存区域(未设置则为 IO 区域),影响 BAR 类型 |
VFU_REGION_FLAG_ALWAYS_CB |
始终使用回调(对 config region 特别有用) |
VFU_REGION_FLAG_64_BITS |
64 位 BAR |
VFU_REGION_FLAG_PREFETCH |
预取 BAR(通常需同时设置 64_BITS) |
区域访问回调签名:
typedef ssize_t (vfu_region_access_cb_t)(vfu_ctx_t *vfu_ctx, char *buf,
size_t count, loff_t offset,
bool is_write);
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- config region (
VFU_PCI_DEV_CFG_REGION_IDX) 有特殊处理:标准 PCI 头部和已知 capability 的访问由库处理,其他区域通过回调或 memcpy 处理 - 如果提供了
fd但mmap_areas为 NULL,则整个区域可 mmap - 客户端可以 mmap 文件描述符的任意部分,即使
mmap_areas不允许 - 64 位 BAR 需要在相邻的高位 BAR 区域(如 BAR0+1、BAR2+3、BAR4+5)不设置任何回调
示例(简单的非映射 BAR):
static ssize_t bar0_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
loff_t offset, bool is_write) {
struct my_data *data = vfu_get_private(vfu_ctx);
if (is_write) {
memcpy(data->bar0 + offset, buf, count);
} else {
memcpy(buf, data->bar0 + offset, count);
}
return count;
}
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR0_REGION_IDX, 0x1000,
bar0_access, VFU_REGION_FLAG_RW, NULL, 0, -1, 0);
示例(可 mmap 的 BAR,带 sparse mmap):
int tmpfd = mkstemp(template);
ftruncate(tmpfd, 0x3000);
void *bar1_mem = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED, tmpfd, 0);
struct iovec bar1_mmap_areas[] = {
{ .iov_base = (void*)0, .iov_len = 0x1000 }, // 第 0 页可 mmap
{ .iov_base = (void*)0x2000, .iov_len = 0x1000 }, // 第 2 页可 mmap
// 第 1 页不可 mmap
};
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR1_REGION_IDX, 0x3000,
bar1_access, VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM,
bar1_mmap_areas, 2, tmpfd, 0);
示例(64 位 BAR):
// BAR2+BAR3 组合为 64 位 BAR
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR2_REGION_IDX, 0x1000000,
bar2_access,
VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM | VFU_REGION_FLAG_64_BITS,
NULL, 0, -1, 0);
5. 中断(IRQ)设置
5.1 vfu_setup_device_nr_irqs
设置设备的各类型 IRQ 数量。
int
vfu_setup_device_nr_irqs(vfu_ctx_t *vfu_ctx, enum vfu_dev_irq_type type,
uint32_t count);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
type |
enum vfu_dev_irq_type |
IRQ 类型 |
count |
uint32_t |
IRQ 数量 |
IRQ 类型:
| 枚举值 | 描述 |
|---|---|
VFU_DEV_INTX_IRQ |
INTx 中断(通常设置为 1) |
VFU_DEV_MSI_IRQ |
MSI 中断(1, 2, 4, 8, 16, 32) |
VFU_DEV_MSIX_IRQ |
MSI-X 中断(最多 2048) |
VFU_DEV_ERR_IRQ |
错误中断(默认 1) |
VFU_DEV_REQ_IRQ |
请求中断(默认 1) |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 默认已初始化 1 个 ERR IRQ 和 1 个 REQ IRQ
- 如果 INTx 数量不为 0,则自动设置 config space 的
intr.ipin = 1(INTA#)
示例:
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_INTX_IRQ, 1);
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSI_IRQ, 8);
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSIX_IRQ, 16);
5.2 vfu_setup_irq_state_callback
设置 IRQ 状态变化回调(当客户端 mask/unmask IRQ 时触发)。
int
vfu_setup_irq_state_callback(vfu_ctx_t *vfu_ctx, enum vfu_dev_irq_type type,
vfu_dev_irq_state_cb_t *cb);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
type |
enum vfu_dev_irq_type |
IRQ 类型 |
cb |
vfu_dev_irq_state_cb_t * |
IRQ 状态变化回调 |
回调签名:
typedef void (vfu_dev_irq_state_cb_t)(vfu_ctx_t *vfu_ctx, uint32_t start,
uint32_t count, bool mask);
// mask == true: IRQ 被 mask
// mask == false: IRQ 被 unmask
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
5.3 vfu_irq_trigger
触发一个中断。
int
vfu_irq_trigger(vfu_ctx_t *vfu_ctx, uint32_t subindex);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
subindex |
uint32_t |
IRQ 子索引(向量编号) |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- libvfio-user 自动选择合适的 IRQ 类型(INTx/MSI/MSI-X),调用者只需指定子索引
- 如果 IRQ 对应的 eventfd 未设置(
-1),返回ENOENT错误
示例:
// 触发第 0 号中断向量
vfu_irq_trigger(vfu_ctx, 0);
6. DMA 设置与操作
6.1 vfu_setup_device_dma
设置设备 DMA 注册/注销回调。
int
vfu_setup_device_dma(vfu_ctx_t *vfu_ctx, vfu_dma_register_cb_t *dma_register,
vfu_dma_unregister_cb_t *dma_unregister);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
dma_register |
vfu_dma_register_cb_t * |
DMA 区域注册回调(可选) |
dma_unregister |
vfu_dma_unregister_cb_t * |
DMA 区域注销回调(可选) |
回调签名:
typedef void (vfu_dma_register_cb_t)(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info);
typedef void (vfu_dma_unregister_cb_t)(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info);
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 如果不调用此函数,则无法通过
vfu_addr_to_sgl()访问客户端的 DMA 内存 - 要使用
vfu_sgl_get()直接映射访问,至少需要提供dma_unregister回调 - 内部创建 DMA controller,最多支持
MAX_DMA_REGIONS(64) 个区域,每个最大MAX_DMA_SIZE(x86_64 上为 8TB)
示例:
static void dma_register(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) {
struct my_data *data = vfu_get_private(vfu_ctx);
// 记录 DMA 区域信息
data->dma_iova = info->iova;
}
static void dma_unregister(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) {
struct my_data *data = vfu_get_private(vfu_ctx);
// 释放所有对该区域的引用
memset(&data->dma_iova, 0, sizeof(data->dma_iova));
}
vfu_setup_device_dma(vfu_ctx, dma_register, dma_unregister);
6.2 vfu_addr_to_sgl
将客户端物理地址范围转换为 scatter/gather 列表。
int
vfu_addr_to_sgl(vfu_ctx_t *vfu_ctx, vfu_dma_addr_t dma_addr, size_t len,
dma_sg_t *sgl, size_t max_nr_sgs, int prot);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
dma_addr |
vfu_dma_addr_t |
客户端物理地址 |
len |
size_t |
要映射的内存大小 |
sgl |
dma_sg_t * |
接收 scatter/gather 条目的数组 |
max_nr_sgs |
size_t |
数组的最大元素数 |
prot |
int |
访问保护(PROT_READ / PROT_WRITE,定义在 ``) |
返回值:
- 成功:返回创建的 SG 条目数
- 失败:
-1:地址范围无效(errno=ENOENT)或保护违规(errno=EACCES)(-x - 1):max_nr_sgs太小,x 为实际需要的条目数(errno=0)
说明:
- 必须先调用
vfu_setup_device_dma() dma_sg_t结构体大小需通过dma_sg_size()获取,不能使用sizeof(dma_sg_t)
示例:
dma_sg_t *sg = alloca(dma_sg_size());
int ret = vfu_addr_to_sgl(vfu_ctx, (vfu_dma_addr_t)0x1000, 4096, sg, 1, PROT_READ);
if (ret < 0) {
perror("vfu_addr_to_sgl");
}
6.3 vfu_sgl_get
将 scatter/gather 列表映射到本地进程的虚拟地址空间(iovec 数组)。
int
vfu_sgl_get(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, struct iovec *iov, size_t cnt,
int flags);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
sgl |
dma_sg_t * |
从 vfu_addr_to_sgl() 获取的 SG 数组 |
iov |
struct iovec * |
输出 iovec 数组 |
cnt |
size_t |
SG 条目数 |
flags |
int |
必须为 0 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 仅在提供
dma_unregister回调时才支持(即直接 mmap 方式) - 使用完毕后必须调用
vfu_sgl_put()释放 - 直接映射访问方式下,服务端负责跟踪脏页
6.4 vfu_sgl_put
释放由 vfu_sgl_get() 获取的 iovec 映射。
void
vfu_sgl_put(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, struct iovec *iov, size_t cnt);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
sgl |
dma_sg_t * |
SG 数组 |
iov |
struct iovec * |
对应的 iovec 数组 |
cnt |
size_t |
条目数 |
说明:
- 如果 SG 条目标记为可写,会自动标记脏页
- 如果已调用
vfu_sgl_mark_dirty(),脏页只会被标记一次(原子操作)
6.5 vfu_sgl_mark_dirty
标记 SG 条目为脏(已写入)。仅在需要标记脏页但不释放映射时使用。
void
vfu_sgl_mark_dirty(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, size_t cnt);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
sgl |
dma_sg_t * |
SG 数组 |
cnt |
size_t |
条目数 |
6.6 vfu_sgl_read
从客户端 DMA 区域读取数据(基于消息的方式)。
int
vfu_sgl_read(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, size_t cnt, void *data);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
sg |
dma_sg_t * |
从 vfu_addr_to_sgl() 获取的 SG 条目 |
cnt |
size_t |
条目数(当前仅支持 1) |
data |
void * |
读入缓冲区 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 通过
VFIO_USER_DMA_READ消息实现,在区域不可直接 mmap 或未设置 DMA 回调时使用 - 不需要
dma_unregister回调
示例:
char buf[4096];
vfu_sgl_read(vfu_ctx, sg, 1, buf);
6.7 vfu_sgl_write
向客户端 DMA 区域写入数据(基于消息的方式)。
int
vfu_sgl_write(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, size_t cnt, void *data);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
sg |
dma_sg_t * |
从 vfu_addr_to_sgl() 获取的 SG 条目 |
cnt |
size_t |
条目数(当前仅支持 1) |
data |
void * |
要写入的数据 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 通过
VFIO_USER_DMA_WRITE消息实现 - 热迁移期间,此调用不会标记脏页(客户端负责跟踪)
6.8 vfu_sg_is_mappable
检查 SG 条目是否可映射。
bool
vfu_sg_is_mappable(vfu_ctx_t *vfu_ctx, dma_sg_t *sg);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
sg |
dma_sg_t * |
SG 条目 |
返回值:
true:SG 条目可直接映射访问false:需要消息方式访问
6.9 dma_sg_size
获取 dma_sg_t 的大小。
size_t
dma_sg_size(void);
说明:
- 不能直接使用
sizeof(dma_sg_t),因为dma_sg_t是不透明类型 - 用于配合
alloca()或malloc()分配 SG 数组
7. 设备复位与静默(Quiesce)回调
7.1 vfu_setup_device_reset_cb
设置设备复位回调。
int
vfu_setup_device_reset_cb(vfu_ctx_t *vfu_ctx, vfu_reset_cb_t *reset);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
reset |
vfu_reset_cb_t * |
复位回调函数 |
回调签名:
typedef int (vfu_reset_cb_t)(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type);
复位类型(vfu_reset_type_t):
| 值 | 描述 |
|---|---|
VFU_RESET_DEVICE |
客户端请求设备复位(如虚拟机重启),vfu_ctx 保持有效 |
VFU_RESET_LOST_CONN |
客户端连接断开,attach 上下文被清理,需重新 vfu_attach_ctx() |
VFU_RESET_PCI_FLR |
PCI Function Level Reset |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 复位回调应确保所有正在使用的 IRQ 或客户端内存访问在返回前完成或取消
示例:
static int device_reset(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type) {
struct my_data *data = vfu_get_private(vfu_ctx);
// 重置所有设备状态
memset(data->regs, 0, sizeof(data->regs));
return 0;
}
vfu_setup_device_reset_cb(vfu_ctx, device_reset);
7.2 vfu_setup_device_quiesce_cb
设置设备静默回调。当库需要请求设备暂停操作(如处理 DMA map/unmap 或迁移状态转换)时调用。
void
vfu_setup_device_quiesce_cb(vfu_ctx_t *vfu_ctx,
vfu_device_quiesce_cb_t *quiesce_cb);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
quiesce_cb |
vfu_device_quiesce_cb_t * |
静默回调函数 |
回调签名:
typedef int (vfu_device_quiesce_cb_t)(vfu_ctx_t *vfu_ctx);
返回值:
0:设备已立即静默-1且errno=EBUSY:设备无法立即静默,需异步静默后调用vfu_device_quiesced()
说明:
- 静默状态下设备不能调用
vfu_addr_to_sgl()或vfu_sgl_*(),除非在设备回调中调用 - 静默期间的合法回调:
vfu_dma_register_cb_t、vfu_dma_unregister_cb_t、vfu_reset_cb_t、迁移转换回调
7.3 vfu_device_quiesced
由设备调用以完成待处理的静默操作。
int
vfu_device_quiesced(vfu_ctx_t *vfu_ctx, int quiesce_errno);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
quiesce_errno |
int |
0 表示成功,否则为错误码(会导致操作失败并复位设备) |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 调用返回后,设备不再是静默状态
8. 热迁移(Migration)设置
8.1 vfu_setup_device_migration_callbacks
设置设备热迁移回调。
int
vfu_setup_device_migration_callbacks(vfu_ctx_t *vfu_ctx,
const vfu_migration_callbacks_t *callbacks);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
callbacks |
vfu_migration_callbacks_t * |
迁移回调结构体 |
回调结构体定义:
#define VFU_MIGR_CALLBACKS_VERS 2
typedef struct {
int version; // 必须设为 VFU_MIGR_CALLBACKS_VERS
// 迁移状态转换回调
int (*transition)(vfu_ctx_t *vfu_ctx, vfu_migr_state_t state);
// 读取迁移数据回调
ssize_t (*read_data)(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count);
// 写入迁移数据回调
ssize_t (*write_data)(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count);
} vfu_migration_callbacks_t;
迁移状态(vfu_migr_state_t):
| 状态 | 描述 |
|---|---|
VFU_MIGR_STATE_STOP |
设备已停止 |
VFU_MIGR_STATE_RUNNING |
设备正在运行 |
VFU_MIGR_STATE_STOP_AND_COPY |
停止并拷贝状态 |
VFU_MIGR_STATE_PRE_COPY |
预拷贝阶段 |
VFU_MIGR_STATE_RESUME |
恢复状态 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- 三个回调都是必需的(
transition、read_data、write_data) transition回调返回-1表示错误,需设置errnoread_data回调返回读取的字节数,返回0表示无更多数据write_data回调不支持部分写入,返回非count值视为错误
示例:
static int migr_transition(vfu_ctx_t *vfu_ctx, vfu_migr_state_t state) {
switch (state) {
case VFU_MIGR_STATE_STOP_AND_COPY:
// 停止设备操作,准备迁移
break;
case VFU_MIGR_STATE_RUNNING:
// 恢复设备运行
break;
// ...
}
return 0;
}
static ssize_t migr_read_data(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count) {
struct my_data *data = vfu_get_private(vfu_ctx);
uint64_t to_read = MIN(count, sizeof(data->device_state) - data->bytes_xferred);
memcpy(buf, (char*)&data->device_state + data->bytes_xferred, to_read);
data->bytes_xferred += to_read;
return to_read;
}
static ssize_t migr_write_data(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count) {
struct my_data *data = vfu_get_private(vfu_ctx);
memcpy((char*)&data->device_state + data->bytes_xferred, buf, count);
data->bytes_xferred += count;
return count;
}
const vfu_migration_callbacks_t migr_cbs = {
.version = VFU_MIGR_CALLBACKS_VERS,
.transition = migr_transition,
.read_data = migr_read_data,
.write_data = migr_write_data,
};
vfu_setup_device_migration_callbacks(vfu_ctx, &migr_cbs);
9. ioeventfd 设置
9.1 vfu_create_ioeventfd
在指定区域创建一个 ioeventfd。
int
vfu_create_ioeventfd(vfu_ctx_t *vfu_ctx, uint32_t region_idx, int fd,
size_t gpa_offset, uint32_t size, uint32_t flags,
uint64_t datamatch, int shadow_fd, size_t shadow_offset);
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
vfu_ctx |
vfu_ctx_t * |
libvfio-user 上下文 |
region_idx |
uint32_t |
区域索引 |
fd |
int |
ioeventfd 的文件描述符 |
gpa_offset |
size_t |
区域内的偏移量 |
size |
uint32_t |
ioeventfd 的大小(字节) |
flags |
uint32_t |
ioeventfd 标志 |
datamatch |
uint64_t |
数据匹配值 |
shadow_fd |
int |
shadow ioeventfd 的文件描述符,-1 表示普通 ioeventfd |
shadow_offset |
size_t |
shadow 内存中的写入偏移 |
返回值:
- 成功:返回
0 - 失败:返回
-1,设置errno
说明:
- shadow ioeventfd 需要编译时定义
SHADOW_IOEVENTFD,且内核支持 gpa_offset + size必须在区域大小范围内
10. 完整示例流程
以下是一个典型的 PCI 模拟设备创建流程:
#include "libvfio-user.h"
struct my_device {
// 设备私有数据
char bar0_data[0x1000];
};
// --- 步骤 1: 定义日志回调 ---
static void my_log(vfu_ctx_t *vfu_ctx, int level, const char *msg) {
fprintf(stderr, "mydev: %s\n", msg);
}
// --- 步骤 2: 定义区域访问回调 ---
static ssize_t bar0_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
loff_t offset, bool is_write) {
struct my_device *dev = vfu_get_private(vfu_ctx);
if (is_write)
memcpy(dev->bar0_data + offset, buf, count);
else
memcpy(buf, dev->bar0_data + offset, count);
return count;
}
// --- 步骤 3: 定义复位回调 ---
static int device_reset(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type) {
struct my_device *dev = vfu_get_private(vfu_ctx);
memset(dev->bar0_data, 0, sizeof(dev->bar0_data));
return 0;
}
int main(int argc, char **argv) {
int ret;
struct my_device dev = { 0 };
vfu_ctx_t *vfu_ctx;
// 1. 创建上下文
vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "/tmp/vfio-user.sock",
0, &dev, VFU_DEV_TYPE_PCI);
if (vfu_ctx == NULL) { err(1, "vfu_create_ctx"); }
// 2. 设置日志
vfu_setup_log(vfu_ctx, my_log, LOG_DEBUG);
// 3. 初始化 PCI 设备
vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_EXPRESS, PCI_HEADER_TYPE_NORMAL, 0);
// 4. 设置 PCI ID
vfu_pci_set_id(vfu_ctx, 0x1234, 0x5678, 0x1234, 0x5678);
// 5. 设置 Class Code
vfu_pci_set_class(vfu_ctx, 0x01, 0x08, 0x02); // NVMe
// 6. 设置 BAR 区域
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR0_REGION_IDX, 0x1000,
bar0_access, VFU_REGION_FLAG_RW, NULL, 0, -1, 0);
// 7. (可选) 添加 PCI capabilities
struct msixcap msix = { 0 };
msix.hdr.id = PCI_CAP_ID_MSIX;
msix.mxc.ts = 15; // 16 vectors (0-based)
vfu_pci_add_capability(vfu_ctx, 0, 0, &msix);
struct pxcap px = { 0 };
px.hdr.id = PCI_CAP_ID_EXP;
px.pxcaps.ver = 2;
px.pxcaps.dpt = 0; // Endpoint
px.pxdcap.flrc = 1;
vfu_pci_add_capability(vfu_ctx, 0, 0, &px);
// 8. 设置 IRQ 数量
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_INTX_IRQ, 1);
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSIX_IRQ, 16);
// 9. 设置复位回调
vfu_setup_device_reset_cb(vfu_ctx, device_reset);
// 10. 完成设备初始化
ret = vfu_realize_ctx(vfu_ctx);
if (ret < 0) { err(1, "vfu_realize_ctx"); }
// 11. Attach 到 transport
ret = vfu_attach_ctx(vfu_ctx);
if (ret < 0) { err(1, "vfu_attach_ctx"); }
// 12. 运行主循环
do {
ret = vfu_run_ctx(vfu_ctx);
} while (ret >= 0);
// 13. 清理
vfu_destroy_ctx(vfu_ctx);
return 0;
}
11. 数据结构参考
11.1 PCI 配置空间头部 (vfu_pci_hdr_t)
typedef union {
uint8_t raw[PCI_STD_HEADER_SIZEOF]; // 64 字节
struct {
vfu_pci_hdr_id_t id; // Vendor ID + Device ID (4B)
vfu_pci_hdr_cmd_t cmd; // Command (2B)
vfu_pci_hdr_sts_t sts; // Status (2B)
uint8_t rid; // Revision ID (1B)
vfu_pci_hdr_cc_t cc; // Class Code (3B)
uint8_t cls; // Cache Line Size (1B)
uint8_t mlt; // Master Latency Timer (1B)
vfu_pci_hdr_htype_t htype; // Header Type (1B)
vfu_pci_hdr_bist_t bist; // BIST (1B)
vfu_bar_t bars[6]; // BAR0-BAR5 (4B each)
uint32_t ccptr; // Cardbus CIS Pointer
vfu_pci_hdr_ss_t ss; // Subsystem Vendor/Device ID
uint32_t erom; // Expansion ROM Base Address
uint8_t cap; // Capability Pointer
uint8_t res1[7]; // Reserved
vfu_pci_hdr_intr_t intr; // Interrupt Line + Pin
uint8_t mgnt; // Min Grant
uint8_t mlat; // Max Latency
};
} vfu_pci_hdr_t; // 64 字节
11.2 PCI 配置空间 (vfu_pci_config_space_t)
typedef struct {
union {
uint8_t raw[PCI_CFG_SPACE_SIZE]; // 256 字节标准空间
vfu_pci_hdr_t hdr; // 前 64 字节为头部
};
uint8_t extended[]; // 扩展空间 (到 PCI_CFG_SPACE_EXP_SIZE = 4096)
} vfu_pci_config_space_t;
11.3 DMA 信息 (vfu_dma_info_t)
typedef struct vfu_dma_info {
struct iovec iova; // 客户端的 IOVA 范围
void *vaddr; // 映射到本进程的虚拟地址(可为 NULL)
struct iovec mapping; // 实际 mmap 的范围(可能因大页对齐而不同)
size_t page_size; // 映射的页面大小
uint32_t prot; // 映射的保护属性(PROT_READ/PROT_WRITE)
} vfu_dma_info_t;
11.4 BAR 寄存器 (vfu_bar_t)
typedef union {
uint32_t raw;
union {
struct {
unsigned int region_type:1; // 0=Memory, 1=I/O
unsigned int locatable:2; // 内存类型的定位位
unsigned int prefetchable:1; // 预取
unsigned int base_address:28; // 基地址
} mem;
struct {
unsigned int region_type:1; // 始终为 1
unsigned int reserved:1;
unsigned int base_address:30; // I/O 基地址
} io;
};
} vfu_bar_t;
11.5 dma_sg_t 结构
struct dma_sg {
vfu_dma_addr_t dma_addr; // 所属 DMA 区域的起始地址
int region; // DMA 区域索引
uint64_t length; // 此 SG 条目的长度
uint64_t offset; // 在此 DMA 区域内的偏移
bool writeable; // 是否可写
};
API 调用顺序总结
创建 vfio-user PCI 模拟设备的推荐 API 调用顺序:
1. vfu_create_ctx() -- 创建上下文
2. vfu_setup_log() -- (可选) 设置日志
3. vfu_pci_init() -- 初始化 PCI 设备
4. vfu_pci_set_id() -- 设置 PCI ID
5. vfu_pci_set_class() -- (可选) 设置 Class Code
6. vfu_setup_region() -- 设置各 BAR 区域
7. vfu_pci_add_capability() -- (可选) 添加 PCI capabilities
8. vfu_setup_device_nr_irqs() -- 设置 IRQ 数量
9. vfu_setup_irq_state_callback() -- (可选) 设置 IRQ 状态回调
10. vfu_setup_device_dma() -- (可选) 设置 DMA
11. vfu_setup_device_reset_cb() -- (可选) 设置复位回调
12. vfu_setup_device_quiesce_cb() -- (可选) 设置静默回调
13. vfu_setup_device_migration_ -- (可选) 设置热迁移回调
callbacks()
14. vfu_realize_ctx() -- 完成设备初始化 ★必须
15. vfu_attach_ctx() -- Attach 到 transport ★必须
16. vfu_run_ctx() -- 运行主循环 ★必须
17. vfu_destroy_ctx() -- 销毁上下文
运行时使用的 API:
vfu_get_private()-- 获取私有数据vfu_irq_trigger()-- 触发中断vfu_addr_to_sgl()+vfu_sgl_get()+vfu_sgl_put()-- DMA 直接映射访问vfu_addr_to_sgl()+vfu_sgl_read()/vfu_sgl_write()-- DMA 消息访问vfu_sgl_mark_dirty()-- 标记脏页vfu_pci_get_config_space()-- 获取 PCI config space 指针vfu_pci_find_capability()/vfu_pci_find_next_capability()-- 查找 capabilityvfu_device_quiesced()-- 完成异步静默vfu_get_poll_fd()-- 获取轮询文件描述符(用于 epoll)vfu_create_ioeventfd()-- 创建 ioeventfd