Tutti Runtime 配置、资源与装配流程
状态:当前实现设计收束
本文基于当前生产代码,描述
TuttiRuntime从读取 YAML 配置,到创建并持有StorageRuntime的完整流程。配置示例以examples/tutti_runtime为准。
1. 范围
本文覆盖:
tutti/config/parser如何将 YAML 翻译为tutti/config/spec定义的TuttiRuntimeSpec;tutti/config/spec如何提供默认值并验证静态配置;TuttiRuntime如何消费已验证的 spec,创建和初始化 Resource;NVMe Resource 如何与 daemon 交互、持有 granted allocation,并分别向 Resolver factory 和 DataPath factory 提供运行期资源视图;
Resolver 和 DataPath 的 factory 接口、输入来源和输出所有权;
TuttiRuntime如何构造RuntimeComponents,调用StorageRuntime::create();TuttiRuntime、StorageRuntime和各组件的所有权、失败回滚及销毁顺序。
本文不覆盖:
daemon 自身的初始化、配置和内部实现;
Resolver 和 DataPath factory 返回实例后的内部算法;
StorageRuntime内部 registry、target、memory、submit、wait、progress 等 I/O 接口和实现;DataPath 的实际 I/O 初始化细节。本文只确认该初始化由谁触发,以及失败时由谁回滚。
2. 总体模型
配置到 Runtime 的主链路如下:
YAML file
|
| parse_tutti_runtime_config(path)
v
TuttiRuntimeSpec(缺省字段已经取得 spec 中定义的默认值)
|
| TuttiRuntimeSpec::validate()
v
静态有效的装配图
|
| Resource factory + Resource::initialize()
v
已授予的运行期 Resource
|
+-------------------------+
| |
| resolver resource view | datapath resource view
v v
Resolver factory DataPath factory
| |
| resolver instance | instance + initialize config
+-------------+-----------+
|
| RuntimeComponents(借用组件指针)
v
StorageRuntime::create()
|
v
RUNNING TuttiRuntime
该模型中有三个必须分开的数据层次:
数据层次 |
典型内容 |
产生者 |
生命周期 |
|---|---|---|---|
期望配置 |
accelerator、resource selection、请求队列数、组件 ID、类型、引用关系、调优参数 |
YAML/parser,或调用方直接构造 spec |
创建期 value object |
运行期事实 |
allocation ID、PCI BDF、字符/块设备路径、namespace、BAR0、MDTS、实际 granted queues |
daemon,经 NVMe Resource 获取和验证 |
Resource lease 生命周期 |
装配结果 |
Resolver/DataPath 实例、路由 scheme、DataPath key、 |
两个 factory 和 |
|
静态 spec 不保存 daemon grant,daemon grant 也不回写 spec。二者只在 Resource 和组件 factory 的边界上汇合。
3. 模块职责总览
模块 |
主要输入 |
主要输出 |
核心职责 |
不负责 |
|---|---|---|---|---|
Config parser |
YAML 路径和 YAML 节点 |
|
YAML 加载、结构检查、字段转换、具体 config variant 构造 |
完整 spec 语义、硬件检查、对象创建 |
Config spec |
C++ spec value |
|
默认值、字段约束、引用和拓扑校验、contract 校验 |
YAML、RPC、硬件探测、运行期对象 |
Resource factory |
|
未初始化的 |
按 config variant 选择 concrete Resource |
初始化顺序和 Runtime 所有权 |
NVMe Resource |
NVMe 请求、runtime accel ID、daemon client |
初始化后的 allocation、两类 Resource view |
daemon 发现/申请、grant 验证、lease 释放、信息投影 |
Resolver/DataPath 构造、StorageRuntime 装配 |
Resolver factory |
|
|
校验关系,消费 resolver view,创建对应 Resolver |
Resolver 的 Runtime 使用、对象长期所有权 |
DataPath factory |
|
|
校验关系,消费 datapath view,创建 DataPath 和初始化配置 |
调用 |
TuttiRuntime |
config path 或 |
完整的 |
编排、环境校验、组件所有权、StorageRuntime 创建和整体销毁 |
重复实现 parser/spec/factory 规则 |
StorageRuntime |
|
|
接收路由绑定并初始化 DataPath;销毁时停止 DataPath |
拥有 Resource、Resolver、DataPath 或理解 concrete backend spec |
BackendSpec 是装配关系,不对应一个运行期 backend 对象。它在创建期把一个
Resource、一个 Resolver 和一个 DataPath 连接起来,并携带三者共享的关系参数,例如
striped backend 的 stripe_unit。
4. Config parser 边界
4.1 公共入口
Parser 对外只提供一个入口:
Result<TuttiRuntimeSpec> parse_tutti_runtime_config(
const std::string& path);
实现位于 tutti/config/parser/tutti_runtime_config_parser.cpp。YAML::Node、yaml-cpp
异常和 YAML 路径错误都留在 parser 私有实现中,不进入 spec、Resource 或 Runtime
接口。
TuttiRuntime::create(config_path) 先调用该入口,成功后将返回的 spec 交给
TuttiRuntime::create(spec)。Parser 自身不调用 spec.validate(),所以其成功语义是
“YAML 已经可以完整翻译为 C++ spec”,而不是“该 spec 已经可以创建 Runtime”。完整
语义验证由 TuttiRuntime::create(spec) 统一触发。
4.2 Parser 负责的检查
Parser 负责所有依赖 YAML 表示的信息:
文件存在且可由 yaml-cpp 加载;
root、
storage、单个声明和config等节点是预期的 mapping/sequence/scalar;required field 存在,unknown field 被拒绝;
YAML 标量可以转换到目标 C++ 整数范围;
selection等字符串可以翻译成 spec enum;根据
type或contract构造正确的std::variant分支;错误消息携带类似
storage.resources[0].allocation的 YAML 路径。
Parser 不判断以下语义:
ID 是否重复、backend 引用是否存在;
Resource、Resolver 和 DataPath 类型能否组成一个合法 backend;
scheme 是否重复;
accelerator 是否真实存在;
daemon 是否能满足资源请求;
factory 或具体组件是否在当前 build 中可用。
4.3 分发边界
顶层 parser 解析公共字段后,按声明类型分发到私有 parser:
YAML 声明 |
分发键 |
目标 config variant |
|---|---|---|
Resource |
|
|
Resource |
|
|
Resolver |
|
|
Resolver |
|
|
Resolver |
|
|
DataPath |
|
|
DataPath |
|
|
DataPath |
|
|
Backend relation |
|
|
Backend relation |
|
|
Backend relation |
|
|
这种分发让 YAML 解析逻辑与 spec 类型一一对应。新增组件类型时,应同时增加其 spec
config、私有 parser、spec validator 和 factory 分支,而不是在 TuttiRuntime 中解析
组件字段。
5. Config spec 边界
5.1 Spec 是唯一静态配置模型
顶层结构为:
struct TuttiRuntimeSpec {
AcceleratorSpec accelerator;
RuntimeSpec runtime;
StorageSpec storage;
Status validate() const;
Result<std::string> to_debug_string() const;
};
StorageSpec 分别保存 ResourceSpec、ResolverSpec、DataPathSpec 和
BackendSpec 数组。前三者是组件声明,BackendSpec 通过字符串 ID 引用它们:
BackendSpec
resource ----> ResourceSpec.id
resolver ----> ResolverSpec.id
datapath ----> DataPathSpec.id
contract ----> 合法的类型、scheme、cardinality 组合
config ----> 关系自身的参数
Spec 是普通 C++ value object。调用方可以绕过 YAML 直接构造,因此所有静态语义必须由
TuttiRuntimeSpec::validate() 独立保证,不能只依赖 parser 的防御性检查。
5.2 默认值归属
默认值定义在 spec header 的成员初始化器中,parser 对 optional field 只在 YAML 出现时 覆盖它。这保证 YAML 和程序化构造共享同一套默认值。
当前主要默认值如下:
字段 |
默认值来源 |
当前默认值 |
|---|---|---|
|
|
编译期 |
|
|
编译期 |
|
NVMe resource spec |
|
|
NVMe DataPath spec |
|
NVMe DataPath 其余 tuning |
NVMe DataPath spec |
|
striped |
striped backend spec |
|
provider.endpoint、queues_per_controller、memory capacity_bytes 等字段没有可用的业务
默认值;parser 将其设为 required,spec validator 也会拒绝空值或零值。
例如 examples/tutti_runtime/tutti_striped.yaml 显式设置 stripe_unit: 65536;如果省略
该 optional field,parser 构造 StripedLocalNvmeBackendConfig 时会保留 spec 中的
512 KiB 默认值。
5.3 validate() 的职责
TuttiRuntimeSpec::validate() 是静态配置语义的权威入口,当前依次保证:
accelerator profile 只能是 HOST、CUDA、MUSA 或 MACA,且与
accel_id的 host/ accelerator 语义一致;每个声明的 ID、type、scheme、variant 和具体字段有效;
同类声明 ID 唯一;
每个 backend 引用的 Resource、Resolver、DataPath 都存在;
backend contract 与 Resource type、Resolver type/scheme、DataPath type 和资源数量匹配;
Resolver scheme 格式有效且全局唯一;
一个 Resource 不会被两个不同 DataPath 消费;
每个声明都可以从某个 backend 到达,不允许未使用声明;
当前产品约束为恰好一个 backend。
“恰好一个 backend”加上“所有声明可达”意味着当前一个 TuttiRuntimeSpec 实际上只会
装配一个 Resource、一个 Resolver 和一个 DataPath。数据结构仍使用 vector,保留了未来
扩展装配图的表达能力,但当前 validator 不允许多 backend Runtime。
当前 contract 矩阵为:
Contract |
Resource |
Resolver |
Scheme |
DataPath |
Resource cardinality |
|---|---|---|---|---|---|
|
|
|
|
|
1 |
|
|
|
|
|
至少 2 |
|
|
|
|
|
1 |
其中 striped contract 还要求 stripe_unit 非零且按 4096 字节对齐。
这些检查只基于配置即可完成。daemon 实际返回几个 slice、每个 slice 的队列数和设备 元数据是否有效,属于 Resource 初始化后的动态校验。
5.4 Debug 输出
to_debug_string() 先再次调用 validate(),再输出包含默认值在内的确定性配置文本。
TuttiRuntimeCreateOptions::spec_debug_logger 可在创建任何 Resource 之前接收这份文本。
因此日志表达的是将被消费的 effective spec,而不是原始 YAML 文本。
6. TuttiRuntime 创建入口与环境校验
公共入口有两种:
static Result<std::unique_ptr<TuttiRuntime>> create(
const std::string& config_path,
TuttiRuntimeCreateOptions options = {});
static Result<std::unique_ptr<TuttiRuntime>> create(
config::TuttiRuntimeSpec spec,
TuttiRuntimeCreateOptions options = {});
两条路径最终都进入同一个 create_with_options_()。创建顺序为:
spec.validate();生成并可选记录 spec debug string;
校验 Runtime 环境;
创建并初始化所有 Resource;
按
BackendSpec解析关系,分别调用 Resolver 和 DataPath factory;将实例注册到
TuttiRuntime的 ownership registry,同时构造RuntimeComponents;调用
StorageRuntime::create(RuntimeConfig, RuntimeComponents);保存
StorageRuntime,将TuttiRuntime状态置为RUNNING。
环境校验不同于 spec 校验。它比较 accelerator.profile 和实际编译 profile,并在
accel_id != -1 时查询当前 accelerator backend 的 device count,拒绝超出范围的
device ID。这些结果依赖当前 build 和机器,不能放入 host-only spec validator。
7. Resource 边界
7.1 通用接口
Resource 抽象提供生命周期、诊断信息和两个面向消费者的窄视图:
class Resource {
public:
virtual const ResourceCapabilities& capabilities() const = 0;
virtual Status initialize() = 0;
virtual Status shutdown() = 0;
virtual ResourceInfo info() const = 0;
virtual Result<std::unique_ptr<const ResourceView>>
get_resolver_view() const = 0;
virtual Result<std::unique_ptr<const ResourceView>>
get_datapath_view() const = 0;
};
通用创建入口为:
Result<std::unique_ptr<Resource>> create_resource(
const config::ResourceSpec& spec,
const ResourceCreateContext& context);
输入来源如下:
输入 |
来源 |
含义 |
|---|---|---|
|
|
Resource ID、type 和具体资源请求 |
|
|
该 Resource 归属的 Runtime accelerator |
resource_factory.cpp 按 ResourceSpec.config 的 variant 分发到 NVMe 或 memory
Resource。factory 只构造 CREATED 状态对象;TuttiRuntime 随后调用
Resource::initialize(),并验证 Resource ID/type/capability/state 与 spec 一致,最后才
接管所有权。
这里的 tutti::Resource 是 TuttiRuntime 装配层的资源抽象,不是
RuntimeComponents::resources 所使用的 ResourceProvider SPI。前者负责兑现 daemon
资源并为两个组件 factory 提供 view;后者是 StorageRuntime 调用
DataPath::initialize() 时传入的通用 provider。当前 TuttiRuntime 不设置
RuntimeComponents::resources,StorageRuntime 会使用其内部默认 provider。换言之,
NVMe daemon grant 在 DataPath factory 构造实例时已经完成消费,不会继续穿透到
StorageRuntime。
7.2 NVMe Resource 的静态请求
以 examples/tutti_runtime/tutti_local_nvme.yaml 为例,静态 NVMe 请求只包含:
daemon endpoint;
selection mode:
allowed、explicit或striped;explicit/striped 模式下期望的 daemon device ID;
每个 controller 请求的 queue 数;
runtime.accel_id,由顶层 Runtime 配置提供。
PCI BDF、设备节点路径、mount/view 路径、namespace、block size、BAR0 size、MDTS 和实际 grant queue 数都不属于 YAML。
7.3 NVMe Resource 初始化与 daemon grant
NVMe Resource 的 concrete factory 使用 provider.endpoint 创建
NvmeResourceClient,但真正的 daemon 交互发生在 NvmeResource::initialize():
list_accelerators()
-> 确认 runtime.accel_id 存在并有 view_root
list_nvme_resources()
-> 确认请求设备存在、可用且 ACL 允许该 accelerator
acquire_nvme_slices(accel_id, selection, device_ids, queues)
-> RuntimeNvmeAllocation { allocation_id, slices[] }
validate_allocation_metadata()
-> ResourceState::INITIALIZED
每个 granted slice 包含:
device_id, accel_id, allowed_accel_ids,
pci_bdf, chrdev_path, block_path,
backing_mount_path, view_path,
namespace_id, logical_block_size,
bar0_size, max_data_size, granted_queues
Resource 会校验 allocation ID、slice 数量/顺序、accelerator 和 ACL、所有必要路径、 namespace、block size、BAR0、MDTS、queue grant,以及多 slice block size 一致性。 如果 grant 已取得但后续验证失败,Resource 立即 release allocation。
daemon client 将 allocation handle 保存在 client 内部;release(allocation_id) 通过销毁
对应 handle 释放 lease。NvmeResource::shutdown() 和析构函数最终都会走该释放路径。
7.4 面向 factory 的两种投影
同一份 daemon grant 不直接暴露给所有消费者。NVMe Resource 投影成两种 view:
View |
提供字段 |
消费者 |
|---|---|---|
|
device ID、PCI BDF、block path、mount/view path、namespace、logical block size |
Resolver factory |
|
device ID、PCI BDF、accel ID、character device path、namespace、logical block size、BAR0、MDTS、granted queues |
DataPath factory |
这两个 view 只能在 Resource 为 INITIALIZED 时取得,并以 value snapshot 的形式返回。
Resolver 无法通过其 view 取得 BAR0 或字符设备,DataPath 也不依赖文件系统 mount
信息。该隔离是 Resource 模块的主要边界,而不仅是类型转换便利。
8. Resolver factory 边界
8.1 接口和输入来源
struct ResolverCreateContext {
const Resource& resource;
const config::BackendSpec& relation;
std::string data_path_key;
};
Result<std::unique_ptr<StorageTargetResolver>> create_resolver(
const config::ResolverSpec& spec,
const ResolverCreateContext& context);
输入 |
来源 |
用途 |
|---|---|---|
|
backend 的 |
选择 Resolver 类型和其静态 config |
|
backend 的 |
获取 resolver resource view |
|
当前装配关系 |
校验 contract,并读取 |
|
被引用的 |
写入 Resolver 产生的 target,使其能路由到同一个 DataPath binding |
Factory 会防御性确认 relation 确实引用传入的 Resolver、Resource 和 DataPath key,再按
ResolverSpec.config variant 分发。它通过通用 Resource::get_resolver_view() 取得
snapshot,并 dynamic_cast 到 concrete view;类型不匹配即创建失败。
8.2 NVMe Resolver factory 消费的数据
local-file 组合要求一个 NVMe slice,构造 Resolver 时消费:
pci_bdf;namespace_id;logical_block_size;block_path;DataPathSpec.id形成的 DataPath key。
striped-file 组合要求至少两个 slice。Factory 为每个 slice 构造 local-file shard,再用:
所有 shard Resolver;
BackendSpec.config.stripe_unit;DataPathSpec.id形成的 DataPath key;
构造 striped Resolver。
Factory 的输出是未被 StorageRuntime 拥有的 unique_ptr。TuttiRuntime 将其注册到
resolvers_ registry,并向 RuntimeComponents 只提供:
ResolverBinding{
.scheme = ResolverSpec.scheme,
.resolver = borrowed_pointer,
};
9. DataPath factory 边界
9.1 接口和输入来源
struct DataPathCreateContext {
const Resource& resource;
const config::BackendSpec& relation;
std::int32_t runtime_accel_id = -1;
};
struct CreatedDataPath {
std::unique_ptr<DataPath> instance;
DataPathConfig initialize_config;
};
Result<CreatedDataPath> create_data_path(
const config::DataPathSpec& spec,
const DataPathCreateContext& context);
输入 |
来源 |
用途 |
|---|---|---|
|
backend 的 |
选择 concrete DataPath 并提供 tuning |
|
已初始化 Resource registry |
获取 datapath resource view |
|
当前装配关系 |
校验 DataPath 与 contract/Resource 的连接 |
|
|
创建 context 中保留 Runtime 身份;当前 factory 尚未直接读取该字段,NVMe concrete binding 来自已验证 slice view |
与 Resolver factory 一样,DataPath factory 先防御性检查 relation,再调用
Resource::get_datapath_view() 并检查 concrete view 类型。
9.2 NVMe DataPath factory 消费的数据
local-nvme 组合要求一个 slice。构造 DataPath 时,factory 合并:
spec tuning:cache capacity、threads per block、in-flight/batch 上限等;
daemon grant:character device path、BAR0 size、accel ID、granted queues、namespace、 logical block size、MDTS、PCI BDF。
striped-local-nvme 组合要求至少两个 slice。Factory 为每个 slice 构造 device descriptor,
并取所有 slice max_data_size 的最小值作为 effective MDTS。threads_per_block 不能超过
任何 slice 的实际 granted queues。
因此,诸如“请求 32 个 queue,但 daemon 只 grant 16 个,而 tuning 要求 32 个线程”的 冲突无法仅靠 spec 判断,应由 DataPath factory 在静态 tuning 与动态 grant 汇合处拒绝。
9.3 Factory 不初始化 DataPath
DataPath factory 返回 CreatedDataPath,其中:
instance由TuttiRuntime持有;initialize_config随DataPathBinding传给StorageRuntime。
Factory 不调用 DataPath::initialize()。该调用发生在
StorageRuntime::create(RuntimeConfig, RuntimeComponents) 内。这条边界保证
StorageRuntime 在注册完整路由后,以统一方式执行 DataPath 生命周期初始化和失败回滚。
10. 装配为 StorageRuntime
10.1 Backend relation 的解析
通过 spec validation 后,TuttiRuntime 可以把每个 BackendSpec 当作静态有效的装配
关系。当前实现按 ID 查找三个声明和已初始化 Resource:
BackendSpec relation
+-- relation.resource -> ResourceSpec + initialized Resource
+-- relation.resolver -> ResolverSpec
+-- relation.datapath -> DataPathSpec
随后先创建 Resolver,再创建 DataPath,并把两者存入各自 registry。这里不创建
BackendInstance;relation 完成 factory 参数传递和 binding key 对齐后即完成使命。
最关键的不变量是:
BackendSpec.datapath
== ResolverCreateContext.data_path_key
== DataPathBinding.key
Resolver 解析 target 后给出的 recommended DataPath key,必然命中同一 relation 所绑定 的 DataPath 实例。
10.2 提交边界
TuttiRuntime 最终构造以下等价数据。实际代码使用 C++17 成员赋值和
push_back():
RuntimeConfig runtime_config;
runtime_config.accel_id = spec.runtime.accel_id;
runtime_config.profile_name = spec.accelerator.profile;
RuntimeComponents components;
components.resolvers.push_back(
{resolver_spec.scheme, resolver_borrowed_pointer});
components.data_paths.push_back(
{data_path_spec.id,
data_path_borrowed_pointer,
created_data_path.initialize_config});
然后调用:
StorageRuntime::create(
std::move(runtime_config),
std::move(components));
RuntimeComponents 是 in-process assembly contract,只包含 SPI 指针和路由信息,不包含
concrete NVMe、filesystem 或 backend spec 类型。其 resources 字段在此保持
nullptr;它不接收也不拥有 tutti::Resource。
RuntimeConfig.max_terminal_results 没有对应的 TuttiRuntime spec 字段,因此此处保留
StorageRuntime 自身的默认值 64。这也说明 TuttiRuntimeSpec 目前只向
StorageRuntime 下传 accelerator 身份,不镜像其所有内部配置。
10.3 StorageRuntime 创建边界
本文只依赖 StorageRuntime::create() 的以下契约:
StorageRuntime不拥有 Resolver 或 DataPath,调用方必须让它们至少存活到StorageRuntime::shutdown()完成;create 先校验
RuntimeConfig和 binding 完整性/唯一性;create 检查 DataPath accelerator binding 与 Runtime 一致;
create 使用每个 binding 的
DataPathConfig调用DataPath::initialize();如果一个 DataPath 初始化失败,create shutdown 已初始化的 DataPath,并且不返回
StorageRuntime实例;create 成功后,Resolver/DataPath 指针由
StorageRuntime借用,实际对象继续由TuttiRuntime持有。
StorageRuntime 如何使用 Resolver 打开 target,以及如何向 DataPath 提交 I/O,不在本文 范围内。
11. 所有权与生命周期
11.1 成功创建后的所有权
application
owns unique_ptr<TuttiRuntime>
|
+-- owns unique_ptr<StorageRuntime>
+-- owns Resource registry
+-- owns Resolver registry
+-- owns DataPath registry
StorageRuntime
borrows Resolver* and DataPath*
Resolver/DataPath instances
were constructed from Resource view snapshots
Resource
owns daemon client and active allocation lease
TuttiRuntime::storage_runtime() 返回 borrowed pointer。调用方不能单独销毁它,也不能让
它超出 TuttiRuntime 生命周期。
11.2 创建失败回滚
创建过程具有事务语义:任何阶段失败都不向调用方返回半初始化 TuttiRuntime。
Resource factory 或 initialize 失败:当前 Resource 被 shutdown;此前已注册 Resource 由临时
TuttiRuntime析构逆序 shutdown;NVMe allocation 已取得但 grant 校验失败:NVMe Resource 当场 release;
Resolver/DataPath factory 或 registry 失败:已创建实例由
unique_ptr或 registry 释放,已初始化 Resource 随 Runtime 回滚;StorageRuntime::create()中 DataPath 初始化失败:StorageRuntime 先 shutdown 已初始化 DataPath,随后临时TuttiRuntime销毁组件和 Resource;Resource、Resolver、DataPath 和 StorageRuntime factory 调用抛出的异常都会在
TuttiRuntime创建边界转换为Status。
11.3 正常 shutdown 和销毁顺序
TuttiRuntime::~TuttiRuntime() 调用幂等的 shutdown()。当前顺序为:
1. StorageRuntime::shutdown(0)
2. destroy StorageRuntime
3. destroy Resolver instances(逆注册顺序)
4. destroy DataPath instances(逆注册顺序)
5. Resource::shutdown()(逆初始化顺序)
6. destroy Resource;NVMe lease/client 在此之前已经释放
7. TuttiRuntime state = STOPPED
即使某一步返回错误,TuttiRuntime 也记录第一个错误并继续后续清理,避免 Resource
lease 因前序清理失败而无人释放。必须先销毁 StorageRuntime,因为它借用 Resolver 和
DataPath;必须最后 shutdown Resource,因为两个 concrete 组件都是由该 Resource 的
grant 构造出来的。
StorageRuntime::shutdown(0) 是非阻塞 drain:仍有 in-flight I/O 时可能返回 TIMEOUT。
当前 TuttiRuntime::shutdown() 即使收到 TIMEOUT,也会继续销毁 StorageRuntime、组件并
释放 Resource lease。因此“调用 shutdown”本身并不等于“已经完成 drain”;应用必须在
销毁 TuttiRuntime 前结束并回收全部 I/O,再显式检查 TuttiRuntime::shutdown() 的
返回值。否则借用组件和底层设备资源可能在未完成操作仍存在时被释放。这是当前创建/
销毁边界的前置条件,本文不扩展其 I/O drain 策略。
12. 两个 NVMe 示例的端到端数据流
12.1 Local NVMe
examples/tutti_runtime/tutti_local_nvme.yaml 声明:
Resource: example-nvme / nvme / explicit device 0 / request 32 queues
Resolver: example-file-resolver / local-file / scheme=file
DataPath: example-local-nvme-datapath / local-nvme / threads=32
Backend: ext4-local-nvme,引用以上三个 ID
创建时的数据流为:
runtime.accel_id = 0
+ ResourceSpec request
-> daemon acquire
-> one granted NVMe slice
| resolver view: BDF, block path, NSID, block size
| -> LocalFileResolver(data_path_key =
| "example-local-nvme-datapath")
|
| datapath view: chrdev, BAR0, accel, queues, NSID, block size, MDTS, BDF
| + DataPathSpec tuning
| -> LocalNvmeDataPath + DataPathConfig{"local_nvme"}
v
RuntimeComponents:
resolver["file"] -> resolver instance
datapath["example-local-nvme-datapath"] -> datapath instance
12.2 Striped Local NVMe
examples/tutti_runtime/tutti_striped.yaml 声明两个 device、striped Resolver/DataPath 和
stripe_unit: 65536。它与 local 流程的差异只在 factory 内:
NVMe Resource 要求 daemon 按请求顺序返回两个 slice;
Resolver factory 为两个 slice 构造 file shard,并用 backend 的 stripe unit 构造
StripedResolver;DataPath factory 为两个 slice 构造 device descriptor,以最小 MDTS 作为共同上限, 构造
StripedDataPath;binding key 使用 YAML 中的 DataPath ID,scheme 使用
striped。
TuttiRuntime 的编排、所有权、StorageRuntime 创建和销毁流程与 local 模式完全相同。
13. 边界结论
当前实现可以收束为以下规则:
Parser 只负责 YAML 到 spec 的忠实、类型安全翻译;
Spec 定义所有静态字段和默认值,并独立保证整个装配图合法;
Resource 将静态资源请求兑现为动态 grant,并独占外部 lease 生命周期;
Resource 分别向 Resolver 和 DataPath factory 暴露最小必要的运行期视图;
Resolver/DataPath factory 是静态 spec 与动态 grant 的汇合点,只负责实例化;
BackendSpec是关系,不是运行期对象;TuttiRuntime是唯一编排者和所有者,负责把 owned components 转换为StorageRuntime借用的 bindings;StorageRuntime的边界从create(RuntimeConfig, RuntimeComponents)开始,在shutdown()和对象销毁后结束;销毁严格遵循 StorageRuntime、组件、Resource/lease 的依赖顺序。
14. 代码索引
主题 |
代码位置 |
|---|---|
TuttiRuntime 公共 API |
|
创建和装配编排 |
|
shutdown 和 ownership registry |
|
Parser 公共 API |
|
Parser 顶层分发 |
|
Spec 顶层结构 |
|
Spec 验证 |
|
Resource SPI 和 factory |
|
NVMe Resource 和 daemon adapter |
|
Resolver factory |
|
DataPath factory |
|
StorageRuntime 创建/销毁契约 |
|
配置示例 |
|