WSL C SDK 存储 API 深度解析:WslcCreateSessionVhdVolume 与 WslcDeleteSessionVhdVolume 的用法、校验逻辑与源码实现
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
本篇围绕 WSL(Windows Subsystem for Linux)仓库中的 C API Storage APIs 参考页 展开,完整覆盖其中登记的两个成员函数WslcCreateSessionVhdVolume与WslcDeleteSessionVhdVolume的签名、参数、取值约束与调用示例,并深入 wslcsdk.cpp 的校验与底层CreateVolume调用链、wslcsdk.h 中的WslcVhdRequirements结构定义以及 WslcSdkTests.cpp 中的端到端测试用例,帮助读者掌握在 WSLC(Windows Subsystem for Linux Containers)会话中创建、挂载并删除命名 VHD 卷的完整实战方案。
1. 存储 API 的定位:会话级命名 VHD 卷管理
Storage APIs是 C API 参考目录 下的一个子章节,当前登记了两个成员函数:
- WslcCreateSessionVhdVolume:在已运行的 WSLC 会话中创建一个以 VHDX 为后端的命名卷;
- WslcDeleteSessionVhdVolume:按名称删除该会话中的命名卷。
这两个函数管理的对象是会话内的命名卷(named session volumes),区别于会话自身的根文件系统 VHD(后者由WslcSetSessionSettingsVhd在建会话前配置)。命名卷创建后,可通过容器配置中的WslcSetContainerSettingsNamedVolumes挂载进容器,实现跨容器的持久化数据共享。
2. WslcCreateSessionVhdVolume:签名、参数与官方示例
2.1 函数签名与参数表
STDAPI WslcCreateSessionVhdVolume( _In_ WslcSession session, _In_ const WslcVhdRequirements* options, _Outptr_opt_result_z_ PWSTR* errorMessage);| Parameter | Type | Direction |
|---|---|---|
session | WslcSession | in |
options | const WslcVhdRequirements* | in |
errorMessage | PWSTR* | out, optional |
返回值:HRESULT,成功时返回S_OK,失败时返回对应的错误码(如E_INVALIDARG、E_POINTER)。
2.2 WslcVhdRequirements 结构:字段语义与生效条件
入参结构体WslcVhdRequirements的完整定义见 wslcsdk.h:
typedef enum WslcVhdType { WSLC_VHD_TYPE_DYNAMIC = 0, // Expanding VHDX (default) WSLC_VHD_TYPE_FIXED = 1 // Fixed-allocation VHDX (only honored by WslcCreateSessionVhdVolume) } WslcVhdType; typedef enum WslcVhdRequirementsFlags { WSLC_VHD_REQ_FLAG_NONE = 0x00000000, // When set, WslcVhdRequirements::uid and gid are honored. When clear, // those fields are ignored and the volume is left owned by root:root. WSLC_VHD_REQ_FLAG_OWNER = 0x00000001, } WslcVhdRequirementsFlags; typedef struct WslcVhdRequirements { // Ignored by WslcSetSessionSettingsVhd _In_z_ PCSTR name; _In_ uint64_t sizeBytes; // Desired size (for create/expand) _In_ WslcVhdType type; // The remaining fields are only honored by WslcCreateSessionVhdVolume. // WslcSetSessionSettingsVhd rejects non-NONE flags with E_INVALIDARG. _In_ WslcVhdRequirementsFlags flags; _In_ uint32_t uid; // honored iff (flags & WSLC_VHD_REQ_FLAG_OWNER) _In_ uint32_t gid; // honored iff (flags & WSLC_VHD_REQ_FLAG_OWNER) } WslcVhdRequirements;各字段在WslcCreateSessionVhdVolume中的约束如下:
| 字段 | 类型 | 约束与语义 |
|---|---|---|
name | PCSTR | 卷名,不能为NULL,否则返回E_INVALIDARG;对应后端文件<会话存储目录>/volumes/<name>.vhdx |
sizeBytes | uint64_t | 期望容量(字节),必须大于 0,否则返回E_INVALIDARG |
type | WslcVhdType | 仅接受WSLC_VHD_TYPE_DYNAMIC(0,动态扩展 VHDX,默认)或WSLC_VHD_TYPE_FIXED(1,固定分配 VHDX);其它取值返回E_INVALIDARG。WSLC_VHD_TYPE_FIXED是仅被本函数认可的取值 |
flags | WslcVhdRequirementsFlags | 目前已知标志位仅有WSLC_VHD_REQ_FLAG_OWNER;出现任何未知标志位会返回E_INVALIDARG(防未来标志位被静默忽略) |
uid/gid | uint32_t | 仅当flags & WSLC_VHD_REQ_FLAG_OWNER时生效,指定卷根 inode 的所有者;未设置该标志时字段被忽略,卷保持root:root属主 |
2.3 官方示例(可直接复现)
参考文档给出的最小示例:
WslcVhdRequirements options = { 0 }; options.name = "cache"; options.sizeBytes = (uint64_t)8 * 1024 * 1024 * 1024; // 8 GiB options.type = WSLC_VHD_TYPE_DYNAMIC; options.flags = WSLC_VHD_REQ_FLAG_OWNER; options.uid = (uint32_t)1000; options.gid = (uint32_t)1000; HRESULT hr = WslcCreateSessionVhdVolume(session, &options, NULL);errorMessage为可选出参:传入非空指针时,失败路径会返回一条以CoTaskMemAlloc分配的、调用方需自行释放(CoTaskMemFree)的宽字符错误消息;不需要消息时可传NULL(测试代码中大量使用NULL)。
3. 源码级实现剖析:从参数校验到底层 CreateVolume
3.1 参数校验顺序与错误码
实现位于 wslcsdk.cpp#L482-L533,校验顺序非常明确,可直接映射为一张错误码表:
auto internalType = CheckAndGetInternalType(session); RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session); RETURN_HR_IF_NULL(E_POINTER, options); RETURN_HR_IF_NULL(E_INVALIDARG, options->name); RETURN_HR_IF(E_INVALIDARG, options->sizeBytes == 0); // Reject unknown flag bits so future additions can't be silently ignored. constexpr WslcVhdRequirementsFlags c_knownFlags = WSLC_VHD_REQ_FLAG_OWNER; RETURN_HR_IF(E_INVALIDARG, (options->flags & ~c_knownFlags) != WSLC_VHD_REQ_FLAG_NONE);| 触发条件 | 返回码 |
|---|---|
| 会话句柄内部状态无效(非活动会话) | HRESULT_FROM_WIN32(ERROR_INVALID_STATE) |
options == NULL | E_POINTER |
options->name == NULL | E_INVALIDARG |
options->sizeBytes == 0 | E_INVALIDARG |
flags含未知标志位(如0x80000000) | E_INVALIDARG |
type既不是DYNAMIC也不是FIXED(如强转值 42) | E_INVALIDARG |
上述每一个错误分支都在 WslcSdkTests.cpp#L2184-L2212 与 L2269-L2277 中有对应的负向用例逐一验证。
3.2 驱动选项组装:SizeBytes / Fixed / Uid / Gid
校验通过后,SDK 把WslcVhdRequirements翻译为一组WSLCCompatDriverOption(键值对)传给底层卷创建接口:
const auto sizeStr = std::to_string(options->sizeBytes); std::vector<WSLCCompatDriverOption> driverOpts; driverOpts.push_back({"SizeBytes", sizeStr.c_str()}); if (options->type == WSLC_VHD_TYPE_FIXED) { driverOpts.push_back({"Fixed", "true"}); } if (WI_IsFlagSet(options->flags, WSLC_VHD_REQ_FLAG_OWNER)) { driverOpts.push_back({"Uid", uidStr.c_str()}); driverOpts.push_back({"Gid", gidStr.c_str()}); } WSLCCompatVolumeOptions volumeOptions{}; volumeOptions.Name = options->name; volumeOptions.Driver = "vhd"; volumeOptions.DriverOpts = driverOpts.data(); ... return errorInfoWrapper.CaptureResult(internalType->session->CreateVolume(&volumeOptions, &volumeInfo));由此可以得到几个实现层面的事实:
- 卷的后端驱动固定为
vhd,即每个命名卷对应一个 VHDX 文件;从测试代码断言的路径看,文件落在<会话存储目录>/volumes/<name>.vhdx(见 WslcSdkTests.cpp#L2128-L2129); WSLC_VHD_TYPE_FIXED通过追加Fixed=true驱动选项实现固定预分配;单元测试进一步验证固定卷落盘后的文件尺寸不小于sizeBytes(L2214-L2231),而动态卷则按需扩展;OWNER标志转换为Uid/Gid两个驱动选项。测试中的注释表明其作用时机:uid/gid 在mkfs时写入卷的根 inode,随后容器内执行stat -c "%u %g" /data得到65534 65534(nobody:nogroup)作为验证(L2233-L2267);- 源码中特意把
uidStr/gidStr提升到函数作用域,注释解释了原因:驱动选项数组中保存的是c_str()指针,必须保持到CreateVolume调用结束都有效(L498-L502)。
3.3 与 WslcSetSessionSettingsVhd 的边界差异
WslcVhdRequirements同时被WslcSetSessionSettingsVhd(配置会话根文件系统 VHD)复用,但两者对字段的认可范围不同,这一点从 wslcsdk.cpp#L548-L572 可见:
WslcSetSessionSettingsVhd忽略name(根文件系统卷不需要名字);- 只接受
WSLC_VHD_TYPE_DYNAMIC,FIXED会返回E_NOTIMPL—— 这也正是头文件中“WSLC_VHD_TYPE_FIXEDis only honored byWslcCreateSessionVhdVolume”备注的由来; - 要求
flags必须为WSLC_VHD_REQ_FLAG_NONE,否则E_INVALIDARG,防止调用者误以为 owner 设置作用在了根文件系统 VHD 上; - 传
NULL时重置为默认值(sizeBytes = s_DefaultStorageSize)。
换言之:WslcCreateSessionVhdVolume才是该结构体全部字段(含name、type=FIXED、OWNER标志)的“全量消费者”,这是文档头注两条 Header notes 的完整背景。
4. WslcDeleteSessionVhdVolume:签名与实现
4.1 签名与参数
STDAPI WslcDeleteSessionVhdVolume( _In_ WslcSession session, _In_z_ PCSTR name, _Outptr_opt_result_z_ PWSTR* errorMessage);| Parameter | Type | Direction |
|---|---|---|
session | WslcSession | in |
name | PCSTR | in(_In_z_,指向以空字符终止的卷名) |
errorMessage | PWSTR* | out, optional |
返回值:HRESULT。
参考文档示例:
HRESULT hr = WslcDeleteSessionVhdVolume(session, "cache", NULL);4.2 实现与语义
实现非常短,位于 wslcsdk.cpp#L535-L546:校验会话处于有效状态(否则ERROR_INVALID_STATE)、name非空(否则E_POINTER),然后直接转发到底层session->DeleteVolume(name)。
从单元测试(WslcSdkTests.cpp#L2174-L2182)可确认其完整语义:删除成功后,对应的<会话存储目录>/volumes/<name>.vhdx文件必须已从磁盘移除。因此删除是不可逆的——卷内数据随之丢失,生产环境中应在删除前把关键数据拷出或先停止挂载该卷的容器。
5. 端到端实战:创建卷并用容器读写
测试用例(WslcSdkTests.cpp#L2100-L2182)提供了一条完整可参考的调用链,展示了命名卷的典型生命周期:
- 建会话:
WslcInitSessionSettings+WslcCreateSession,测试刻意使用独立会话目录,避免影响共享默认会话; - 建卷:
WslcCreateSessionVhdVolume创建名为c_volumeName的动态卷,并断言后端 VHDX 文件存在; - 挂载写入:通过
WslcSetContainerSettingsNamedVolumes把命名卷挂进容器的/data:
WslcContainerNamedVolume namedVol{}; namedVol.name = c_volumeName; // 与 WslcVhdRequirements.name 一致 namedVol.containerPath = "/data"; // 容器内绝对路径 namedVol.readOnly = FALSE; WslcSetContainerSettingsNamedVolumes(&containerSettings, &namedVol, 1);容器执行echo wslc-vhd-test > /data/marker.txt写入标记文件(WslcContainerNamedVolume定义见 wslcsdk.h#L205-L210); 4.跨容器读取:第二个容器以readOnly = TRUE只读挂载同一卷,cat /data/marker.txt读回wslc-vhd-test\n,验证了命名卷在会话内多个容器之间的数据共享; 5.删卷:WslcDeleteSessionVhdVolume后断言 VHDX 文件不再存在。
6. WinRT 封装对照:VhdOptions 与 Session 接口
同一组 API 在 WinRT 侧有面向托管/WinRT 调用者的封装。VhdOptions的构造函数接收name、size、VhdType,其中size == 0会在构造/赋值阶段即抛出E_INVALIDARG("VHD size cannot be zero",见 VhdOptions.cpp#L21-L28);当设置了Owner(含Uid/Gid的可选引用)时,ToStructPointer()会把它们填入WslcVhdRequirements并自动置位WSLC_VHD_REQ_FLAG_OWNER(VhdOptions.cpp#L95-L114)。会话接口CreateVhdVolume/DeleteVhdVolume则直接桥接到本文讲解的两个 C 函数(Session.cpp#L322-L336)。WinRT 单元测试(WslcSdkWinRTTests.cpp#L1523-L1591)覆盖了动态卷、固定卷与 owner 卷三条路径,行为与 C API 一致。
7. 要点小结
WslcCreateSessionVhdVolume接受WslcVhdRequirements的全部字段:name(卷名)、sizeBytes(必须 > 0)、type(DYNAMIC或仅本函数认可的FIXED)、OWNER标志(控制uid/gid是否生效,作用于卷根 inode 属主);任何未知标志位或非法type都以E_INVALIDARG拒绝。- 卷以
<会话存储目录>/volumes/<name>.vhdx落盘,驱动选项为SizeBytes/Fixed=true/Uid/Gid,创建动作最终转发到底层CreateVolume(wslcsdk.cpp#L524-L531)。 - 创建出的命名卷通过
WslcSetContainerSettingsNamedVolumes挂载进容器,可读写或只读,实现会话内多容器共享持久化数据。 WslcDeleteSessionVhdVolume按名删除卷并移除后端 VHDX 文件,操作不可逆;其参数校验(E_POINTER/ERROR_INVALID_STATE)与创建函数一致。- 与
WslcSetSessionSettingsVhd(根文件系统 VHD 配置)相比,会话命名卷 API 是唯一支持固定分配 VHD 与属主设置的入口,这是本组存储 API 最核心的差异化能力。
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考