目录
虚拟化技术栈 · libvirt

libvirt:统一管道与缓冲区的资源所有权

由命令对象接管管道与缓冲区,统一调用方在成功、失败路径上的资源归属。

virCommandSetSendBuffer: Provide saner semanticsPeter Krempa · 原作者提交说明与修改对照

1问题背景#

启动 swtpm 前要把口令经管道送给子进程。旧调用方既创建管道,又传递写端和缓冲区,还要在多处失败分支关闭两端、清空已转移指针,容易把资源归属判断分散到不同层。

2原有方案#

原来的 TPM 调用方自行创建 pipe、传递写端,并在失败标签关闭两端;成功后还要手动清空 secret,登记读端。资源转移分散在多次调用之间。

3设计方案#

virCommandSetSendBuffer 将管道创建与资源移交封装为消费式 API:入口先清空调用方的缓冲区指针,失败时由局部自动清理负责,成功时将数据和两端 FD 交给 virCommand。

4改进分析#

调用后无论成功失败,调用方都不再拥有缓冲区,返回的读端 FD 仅用于组装子进程参数。调用方无须逐步判断资源是否已转移;原提交中的清理分支也随之减少。

5源码分析#

5.1缓冲区消费与失败清理#

入口即消费 buffer。即使命令对象已有错误或创建 pipe 失败,调用方也不会再次释放它。

src/util/vircommand.cv10.10.0 · L1716–L1737
查看原文件
1716virCommandSetSendBuffer(virCommand *cmd,1717                        unsigned char **buffer,1718                        size_t buflen)1719{1720    g_autofree unsigned char *localbuf = g_steal_pointer(buffer);1721    int pipefd[2] = { -1, -1 };1722    size_t i;17231724    if (virCommandHasError(cmd))1725        return -1;17261727    if (virPipeQuiet(pipefd) < 0) {1728        cmd->has_error = errno;1729        return -1;1730    }17311732    if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) < 0) {1733        cmd->has_error = errno;1734        VIR_FORCE_CLOSE(pipefd[0]);1735        VIR_FORCE_CLOSE(pipefd[1]);1736        return -1;1737    }
virCommandSetSendBuffer

入口通过双指针消费调用方缓冲区,把原指针置空;本地 g_autofree 在早退时负责释放。pipe 或 fcntl 失败写入命令对象的错误状态,fcntl 失败还显式关闭两端 FD。

5.2缓冲区与文件描述符的移交#

数据与写端进入 sendBuffers,读端登记到 pass-fd 列表。局部指针置空后,自动清理不会释放已经移交的缓冲区。

src/util/vircommand.cv10.10.0 · L1739–L1752
查看原文件
1739    i = virCommandGetNumSendBuffers(cmd);1740    VIR_REALLOC_N(cmd->sendBuffers, i + 1);17411742    cmd->sendBuffers[i].fd = pipefd[1];1743    cmd->sendBuffers[i].buffer = g_steal_pointer(&localbuf);1744    cmd->sendBuffers[i].buflen = buflen;1745    cmd->sendBuffers[i].offset = 0;17461747    cmd->numSendBuffers++;17481749    virCommandPassFD(cmd, pipefd[0], VIR_COMMAND_PASS_FD_CLOSE_PARENT);17501751    return pipefd[0];1752}
virCommandSetSendBuffer · ownership transfer

成功后缓冲区与写端转给 sendBuffers,读端通过 virCommandPassFD 登记。第二次 g_steal_pointer 取消本地自动释放,返回的 FD 仅用于组装参数,调用者不再负责关闭它。

5.3TPM 调用方的职责简化#

TPM 准备路径传入 &secret,取得用于子进程参数的 FD,无须自行管理管道清理。命令准备阶段的错误保存在 virCommand 中,最终执行时仍须检查结果。

src/qemu/qemu_tpm.cv10.10.0 · L259–L271
查看原文件
259    conn = virGetConnectSecret();260    if (!conn)261        return -1;262263    memcpy(seclookupdef.u.uuid, secretuuid, sizeof(seclookupdef.u.uuid));264    if (virSecretGetSecretString(conn, &seclookupdef,265                                 VIR_SECRET_USAGE_TYPE_VTPM,266                                 &secret, &secret_len) < 0)267        return -1;268269    *fd = virCommandSetSendBuffer(cmd, &secret, secret_len);270    return 0;271}
qemuTPMSetupEncryption

虚拟 TPM 准备取得口令后,把 &secret 直接交给命令对象。函数前部的 secret 声明使用 g_autofree;转移后被置空。SetSendBuffer 内部错误延迟到 virCommand 执行阶段报告,因此此处返回 0 只表示准备流程继续,并不代表管道已成功写完。

6适用范围与限制#

失败后原缓冲区不再可用;错误可能留到命令执行阶段才报告。自动释放需要支持 cleanup 属性的编译器,且不代替敏感数据擦除。

固定于 libvirt v10.10.0 的非 Windows 实现及 QEMU 虚拟 TPM 调用路径。这是所有权 API 与错误处理写法,不把 GLib 自动清理机制说成 libvirt 的发明。

7参考资料#

virCommandSetSendBuffer: Provide saner semantics

Michal Privoznik:用双指针表达缓冲区消费

GLib g_steal_pointer

对比的是 libvirt 原提交中的真实旧实现。这里评价资源归属与错误分支的可读性,不宣称性能提升。

65 个匹配案例