Storage 模块设计说明(4+1 视图)
更新时间: 2026/04/29
在Gitcode上查看源码

Storage 模块设计说明(4+1 视图)

Storage 组件是 openUBMC 中负责存储控制器、物理盘、阵列、逻辑盘等资源统一管理的独立子系统。本章节从软件架构 4+1 视图模型出发,对 Storage 模块的层级调用关系、SO 依赖关系以及核心对象之间的关联进行系统性说明。

代码仓地址:https://gitcode.com/openUBMC/storage

一、逻辑视图(领域模型与对象关系)

1.1 领域对象总览

核心领域对象均定义在 src/lualib 下,通过 mc.orm.object 封装为可持久化对象,并由 object_manager 统一管理:

  • Controller 相关
    • controller/controller_object.lua 中的 c_controller
    • 负责 RAID 控制器的发现、基本属性(温度、类型、BDF、SlotId)与健康状态维护,以及控制器级操作(模式切换、SlowDisk 检测开关等)。
  • Drive 相关
    • drive/drive_object.lua 中的 c_drive
    • 描述单块物理盘的拓扑属性(槽位、Enclosure、RefControllerId)、LED 状态、SMART/健康信息、预故障/故障状态等。
  • Array(DiskArray)相关
    • array/array_object.lua 中的 c_array
    • 表示 RAID 阵列,维护阵列容量、空闲空间、成员盘列表(RefPDSlots / RefPDEnclosures)以及关联逻辑盘列表 RefVolumes
  • Volume(逻辑盘)相关
    • volume/volume_object.lua 中的 c_volume
    • 表示逻辑卷,维护 RAID 级别、缓存/访问策略、容量、重构状态、所属阵列等,并通过 RefDiskArrayList / RefDrives 关联阵列和物理盘。
  • Drives 集合与对象管理
    • drives/drives_object.luaobject_manager 系列
    • 将同类对象聚合成集合,为健康统计、告警判断等提供遍历能力。
  • SML 适配与底层访问
    • sml/init.lua 提供 sml.get_array_info / get_ld_info / get_pd_info / ctrl_operation 等高层接口。
    • SML 再经 l_sml_adapter.cpp 动态加载 /usr/lib64/libsml_base*.solibsml_custom_base*.solibpd_log_parse*.so 等 vendor 库。

1.2 关键对象关系类图

下面用简化类图描述 Controller / Drive / Array / Volume 之间的核心依赖关系(仅保留关键字段,便于理解结构):

可以看到:

  • 控制器是核心聚合根:通过 RefDrives / RefControllerId 管理所有物理盘,通过 RefControllerId 管理所有阵列。
  • 阵列 c_array 绑定在固定控制器上,同时维护自己的逻辑盘列表 RefVolumes 与成员盘槽位/Enclosure 列表。
  • 逻辑盘 c_volume 在阵列与物理盘之间搭建桥梁:既记录自身所在阵列,又维护成员盘与 CacheCade 关系。
  • 物理盘 c_drive 既从控制器视角被管理,又从阵列/逻辑盘视角作为成员参与 RAID 关系。

1.3 对象与 SML / 事件系统的协作

  • 对象刷新
    • c_controller / c_array / c_volume / c_drive 均通过 sml.* 接口周期性或按需从 RAID/HBA 卡驱动拉取最新状态,例如:
      • c_array:get_array_info()sml.get_array_info(controller_id, array_id)
      • c_volume:get_volume_info()sml.get_ld_info(...)
      • c_controller 的各种操作 → sml.ctrl_operation(...)
  • 通信异常监控
    • sml 调用异常(SML_ERR_CTRL_STATUS_INVALID 等),会通过 ctrl_commu_loss_monitor 记录控制器通信丢失,供上层健康检查与告警。
  • 信号机制
    • 通过 mc.signal 为关键状态变化定义信号(如 on_update, on_update_ref_drives, on_presence_changed 等),由上层服务或 RPC 层订阅,实现解耦。

二、开发视图(模块与代码结构)

2.1 目录结构与分层

参考 storage_new/README.md 与源码,Storage 模块主要分为三层:

  • 入口与服务层(Service Layer)
    • src/service/main.lua:Skynet 服务入口
      • 注册 .storage 服务,启动 sd_busstorage/service/smld
      • 创建 StorageApp.new() 实例,接收外部命令(如退出)。
    • src/service/smld.lua:SML 后台 worker 服务,处理所有 SML 相关调用。
  • 领域服务与业务组织层(Application / Domain Layer)
    • src/lualib/storage_app.lua:核心应用类 storage_app
      • 负责初始化并持有以下关键对象集合:
        • 资源与任务:storage_db, sync_task, task_service, tasks
        • 领域对象集合:drive_collection, volume_collection, array_collection, battery_collection, c_drives_object
        • 服务组件:diagnose_service, metric_collect, ipmi_service, link_volume_array_drive_service, bus_monitor_service
        • RPC / IPMI 接口:rpc_service_volume, rpc_service_drive, rpc_service_controller, rpc_service_subhealth, rpc_service_nvme_drive
        • 升级相关:drive_upgrade, controller_upgrade, controller_firmware
        • NVMe 相关:nvme.vpd_connector_collection, nvme.replica_drive_collection
      • 初始化过程中,storage_app:init() 会:
        • 构建业务对象与集合;
        • 注册到 mc.orm.object_manage / mc.mdb.object_manage
        • 注册 RPC / IPMI / 导入导出接口;
        • 启动 GC 任务,维护 Lua/C 堆。
  • 底层访问与适配层(Infra / Adapter Layer)
    • src/lualib/sml:SML Lua 封装,使用 Skynet 协程与 .smld 服务进行异步调用。
    • src/lualib-src/l_smlsrc/lualib-src/sml/*:C/C++ 实现的 SML 适配层与历史/日志解析能力。
      • 通过 dlopen 动态加载各类 vendor 库。
    • bma, mctp, ipmi_service 等模块:适配 BMA、MCTP、IPMI 等带内/带外访问通道。

2.2 模块依赖关系图

从开发视角看,主要模块依赖关系如下(从上到下表示调用方向):

2.3 外部依赖声明

mds/service.json 中定义了 Storage 组件构建与运行时的依赖:

  • Conan 包依赖(test/build)
    • libmc4lua, libmgmt_protocol, mctpd 等通用基础库。
    • hwproxy, hwdiscovery, ipmi_core, mdb_interface, persistence 等平台基础能力。
  • Kepler 接口依赖(required)
    • bmc.kepler.Systems.PCIeDevices.PCIeDevicebmc.kepler.Systems.Mctp.*bmc.kepler.Systems.Biosbmc.kepler.Systems.Board.Unit 等硬件抽象接口,用于建立与主板/PCIe/FRU/UpdateService 的资源关系。

三、进程视图(运行时结构与并发)

3.1 关键 Skynet 服务

  • .storage 服务
    • src/service/main.lua 启动,作为 Storage 模块对外的统一服务入口。
    • 负责:
      • 启动 sd_bus.smld
      • 构造并初始化 StorageApp
      • 处理组件生命周期命令(如 exit)。
  • .smld 服务
    • 定义在 src/service/smld.lua,为 SML 提供专门的 worker 线程。
    • 接收来自 sml/init.lua 的调用请求,转发到底层 C 适配器与动态库。
  • 插件 worker
    • sml/init.lua 中通过 worker.core 创建 sml.callback worker,用于处理 I2C/MCTP 通信。
    • 当控制器与 RAID 驱动需要通过 hwproxy/BMA 访问实际总线时,由该 worker 协调。

3.2 典型调用路径

场景:获取逻辑盘信息

  1. 上层通过 Redfish / RPC 调用逻辑盘查询接口(封装在 rpc_service_volume 中)。
  2. rpc_service_volume 调用 storage_app 内部的查询函数。
  3. storage_app 使用 c_volume 集合(由 volume_collection 管理)返回当前缓存状态;
  4. 如需刷新,则触发 c_volume:get_volume_info()
    • 调用 sml.get_ctrl_init_state 确认控制器已初始化;
    • 调用 sml.get_ld_info(...) 获取当前逻辑盘详细信息;
    • 更新 c_volume 字段并触发 on_update 等信号。

场景:更新控制器 SlowDisk 检测模式

  1. 上层通过 RPC/Redfish 调用设置慢盘检测模式接口;
  2. 进入 c_controller 中的模式转换逻辑(handle_slow_disk_mode_setting):
    • 校验控制器 OOBSupportis_working 状态;
    • 根据当前模式与目标模式,从 MODE_TRANSITIONS 表计算所需底层操作序列;
    • 通过 sml.ctrl_operation 下发到 RAID 控制器;
  3. 根据返回码决定是否更新本地状态并记录操作日志。

3.3 并发与资源管理

  • 通过 Skynet 的 queueworker 机制:
    • 保证对单控制器的关键操作串行执行,避免竞态。
    • SML 调用使用 timeout_call 包装,防止调用阻塞导致服务不可用。
  • 垃圾回收:
    • storage_app:start_gc_task() 中创建周期性任务:
      • 调用 collectgarbage('collect') 回收 Lua 堆;
      • 通过 utils_core.malloc_trim(0) 释放 C 堆内存。

四、物理视图(部署与 SO 依赖)

4.1 组件部署

  • BMC 文件系统布局(示意)
    • /usr/lua/storage/*:编译打包后的 Lua 代码(对应 src/lualibsrc/service)。
    • /usr/lib64/*sml*.so:Storage 依赖的 SML 库。
    • /usr/lib64/libpd_log_parse*.so:盘日志解析库。
    • /usr/lib64/raidlib.so, /usr/lib64/libraidblib*.so, /usr/lib64/libsyscore.so 等:
      • 各厂商 RAID/HBA 驱动所提供的核心动态库,由 SML C 适配层通过 dlopen/dlsym 动态加载。

4.2 关键 SO 依赖链

  • SML 基础库加载(l_sml_adapter.cpp
    • 决策逻辑:
      • TypeId <= PMC_2100_8I_SMART_HBA,优先加载 SML_CUSTOM_BASE_LIB_PATHlibsml_custom_base*.so)。
      • 对于自研 1880 系列控制器(HI1880_SP186_M_*),加载 SML_BASE_LIB_PATHlibsml_base*.so)。
    • 成功加载后:
      • 通过函数表 sml_adapter_fun_table 调用 dlsym 绑定所有 SML 接口(控制器/阵列/逻辑盘/物理盘操作、诊断、慢盘检测等)。
  • 盘日志解析库加载
    • 同文件中,通过 SML_PD_LOG_PARSE_PATH 加载 libpd_log_parse*.so
      • 提供 SMART/SAS 日志解析、性能劣化分析等函数;
      • 解析函数指针通过 pd_log_adapter 统一保存,供 Lua 层调用。
  • 厂商专有库
    • sml/sml_base/src/adapter/adapter.c 等文件中,通过宏定义加载:
      • libsml_lsi.solibsml_pmc.solibsml_histore*.so 等;
    • sml_histore/src/hs_misc.c 中通过 HISTORE_LIB = /usr/lib64/raidlib.so 调用厂商 histore 能力库。

4.2.1 ctrl_manage_object_constructor:按厂商加载 SML 适配层 SO

smlib 控制器列表 侧,厂商选择与 SO 绑定发生在 adapter.csmlib_add_ctrl 流程中:先根据 OOB 注册信息拼出 controller_idvender_index,再调用 ctrl_manage_object_constructor,为每个 SML_CTRL_S 填入对应厂商的函数指针表(并在此过程中 dlopen 各厂商的 SML 封装库)。

调用位置与顺序(adapter.c

  1. smlib_add_ctrl(SML_CTRL_OOB_INFO_S *ctrl) 校验 ctrl_index,分配 gCtrlList[ctrl_index]
  2. construct_ctrl_id(ctrl, &controller_id, &vender_index):根据 i_controller_typeid(及 PMC 场景下的 MCTP eid/phy_addr 等)查表,得到 vender_index 与编码进 controller_id 的类型位(如 LSI 的 StoreLib 大类、Histore 的 HBA/RAID 类型等)。
  3. ctrl_manage_object_constructor(vender_index, gCtrlList[ctrl_index]):按厂商分支 dlopen + dlsym,把 pfn_add_ctrlpfn_init_ctrl_over_i2c / pfn_init_ctrl_over_pcie 等挂到该控制器对象上。
  4. 若构造成功,再调用 gCtrlList[ctrl_index]->pfn_add_ctrl(controller_id) 做厂商侧“加入控制器”的初始化。

construct_ctrl_id 与编译形态

工程中存在两套 construct_ctrl_id 实现(链接时二选一,不会同时进同一个二进制):

  • vendor_other/vendor_other.c:覆盖 博通 LSI(含 g_sml_lsi_ctrl[] 类型表)与 PMCPMC_3152_8I_SMART_RAIDPMC_2100_8I_SMART_HBA 等)的 controller_id / vender_index 组装。
  • vendor_histore/vendor_histore.c:覆盖 华为 Histore 系列控制器的 g_sml_histore_ctrl[] 映射。

因此:vender_index 的来源是“控制器 TypeId → 厂商表项”,ctrl_manage_object_constructor 只做“厂商 → 加载哪份 libsml_*.so + 绑哪些符号”。

ctrl_manage_object_constructor 分支逻辑(摘录)

vender_index注册函数动态库(典型路径)符号前缀 / 说明
VENDER_LSIregister_storelib_function/usr/lib64/libsml_lsi.soLSI_SML_LIB_PATHlsi_*:含 lsi_init_ctrl_manage(I2C 带外初始化入口)等;StoreLib 分库见下文 4.3
VENDER_PMCregister_storagecore_function/usr/lib64/libsml_pmc.sopmc_*:初始化走 pfn_init_ctrl_over_pcie(与 PMC StorageCore 交互,见 sc_misc.c 等对 libsyscore.so 的二次加载)。
VENDER_HUAWEIregister_histore_functionBUILD_SML_OPEN/usr/lib64/libsml_histore_open.so,否则 /usr/lib64/libsml_histore.sohistore_*:带外/升级等走 PCIe 侧管理接口集合。
其他返回 SML_ERR_CTRL_PARAM_ILLEGAL

流程图:从添加到控制器对象到厂商 SO

时序图:与 4.3 的层次关系

与 4.3 的关系(两层动态库)

  • 第一层(本节)ctrl_manage_object_constructor 决定加载 libsml_lsi.so / libsml_pmc.so / libsml_histore*.so,完成 SML 与厂商 SDK 之间的适配层绑定。
  • 第二层(4.3):在 VENDER_LSI 路径下,libsml_lsi.so 内的 lsi_init_ctrl_manage 再按 controller_id 中的 StoreLib 类型位,二次 dlopen libraidblib.solibstorelibir*.soBroadcom StoreLib

4.3 博通(LSI)lsi_init_ctrl_manage:不同 StoreLib 分库加载流程

博通 RAID/HBA 在 BMC 侧通过 Broadcom StoreLib 系列动态库 做带外管理;SML 在 adapter.c 里把控制器上的 pfn_init_ctrl_over_i2c 绑定到 sl.c 导出的 lsi_init_ctrl_manage(该绑定发生在 ctrl_manage_object_constructorregister_storelib_function 路径中),由 lsi_init_ctrl_manage 再按编码在 controller_id 中的 StoreLib 类型决定加载哪一份 StoreLib .so,并完成 I2C 注入与库内初始化。

与上层衔接

  • register_storelib_functionlsi_init_ctrl_manage 注册为 pfn_init_ctrl_over_i2c(见 storage_new/src/lualib-src/sml/sml_base/src/adapter/adapter.c)。
  • 在此之前,同一路径已通过 dlopen(LSI_SML_LIB_PATH) 加载 libsml_lsi.so,从中 dlsym 出包括 lsi_init_ctrl_manage 在内的 LSI 适配入口;真正的 StoreLib(MegaRAID / IR / IT 等)则在 lsi_init_ctrl_manage第二次按类型 dlopen

lsi_init_ctrl_manage 主流程(storage_new/src/lualib-src/sml/sml_lsi/src/sl.c

  1. ctrl_id 解析库类型:libType = LSI_STORELIB_TYPE_VALID_BIT(ctrl_id)(同一套 ctrl_id 编码里携带“应使用哪类 StoreLib”的信息)。
  2. lsi_mutex_init():按库类型加锁,避免多控制器并发初始化竞态。
  3. InitCtrlDiagEvent(ctrl_id):初始化背板/硬盘 sense 等诊断上下文。
  4. libType 分支:若对应类型的 StoreLib 尚未加载,则调用对应的 LoadStorelib*(内部 dlopen + dlsym),再 InitializeStorelib(libType, &ctrlList) 向库下发 Discover/Init;部分分支在初始化成功后还会做 OOB 就绪校验(见下表)。

各类型对应的动态库与关键符号(文件路径均为 BMC 上典型安装位置)

库类型(libType动态库路径加载函数向库注册 I2C命令分发符号
SL_LIB_TYPE_STORELIB(MegaRAID 等 MR)/usr/lib64/libraidblib.soLoadStorelibMRRegisterI2CFunc(非空则注册 hwproxy 下发的读写)ProcessLibCommand
SL_LIB_TYPE_STORELIBIR/usr/lib64/libstorelibir.soLoadStorelibIR无(IR 路径不注册 I2C)ProcessLibCommandIR
SL_LIB_TYPE_STORELIBIR_2/usr/lib64/libstorelibir2.soLoadStorelibIR2ProcessLibCommandIR2
SL_LIB_TYPE_STORELIBIR_3/usr/lib64/libraidblibir3.soLoadStorelibIR3MPCI_RegisterI2CFuncProcessLibCommandIR3,另需 SetInitDone
SL_LIB_TYPE_STORELIBIT(IT 模式)/usr/lib64/libraidblibit.soLoadStorelibITRegisterI2CFuncProcessLibCommandIT,另需 SetInitDone
SL_LIB_TYPE_STORELIBCUSTOM/usr/lib64/libraidbcustom.soLoadStorelibCustomRegisterI2CFuncProcessLibCommandIT(自定义库复用 IT 命令入口),另需 SetInitDone

分支内的业务差异(与文档中“带外 I2C 经 hwproxy”一致)

  • MR(libraidblib.so:首次加载成功后调用 InitializeStorelib,并对 tmpCtrlList 设置 oob_cl[0].reDiscover = 1 强制重发现;初始化成功则首次 MR 时 注册 AEN 收集控制器事件日志,并调用 GetOOBStatus 确认固件 OOB 状态;若 Discover 在库内“总是返回 0”,通过 OOB 状态检测把失败反馈给上层重试初始化(代码注释已说明该行为)。
  • IR / IR2:仅首次加载库并 InitializeStorelib注意:当前实现里用 pfnProcessLibCommandMR == NULL 作为“尚未加载”的守卫(与 IR 实际函数指针变量名不一致),阅读代码时需注意这一写法,避免误以为与 MR 共用同一指针。
  • IR3 / IT / Custom:加载后同样带 reDiscoverInitializeStorelib;初始化成功后通过 GetCtrlMFCDefault 再探一轮控制器侧状态——若返回 SL_ERR_INVALID_CMD(例如部分 IT 固件或 MCTP 路径不支持该 DCMD),则视为成功;否则记录 OOB 未就绪类错误,由上层在 INIT_FAILED 时重试。

流程简图

通过上述链路(含 4.2 的 Lua 侧 l_sml_adapter 基础库、4.2.1 的 smlib 厂商适配层 libsml_*.so 选择、4.3 的博通 StoreLib 二次分库),Storage 模块在逻辑上只依赖 sml.* Lua 接口,而物理上可以根据控制器类型装配不同厂商的 SO 库,实现对多种 RAID/HBA 卡的统一管理。

五、场景视图(典型用例)

本节选取三个代表性的业务场景,串联前述视图中的对象、模块、进程与 SO 依赖。

5.1 场景一:系统启动后的存储拓扑发现

目标:建立控制器 → 阵列 → 逻辑盘 → 物理盘的完整拓扑。

说明

  • 该流程体现了:
    • 物理视图:Vendor 库被动态加载;
    • 进程视图.storage.smld 的协作;
    • 逻辑视图:Controller/Array/Volume/Drive 对象及其引用关系初始化;
    • 开发视图storage_app 通过 object_managersml 完成领域模型装配。

5.2 场景二:创建逻辑盘

目标:在指定控制器与物理盘集合上创建新的逻辑盘。

  1. 用户通过 WebUI/Redfish 发起创建 Volume 请求(指定控制器、RAID 级别、成员盘列表等)。
  2. 上层网关将请求转换为对 Storage RPC 的调用(rpc_service_volume)。
  3. rpc_service_volumestorage_app 中校验资源状态(控制器健康、成员盘可用等)。
  4. 通过 sml.ctrl_operationsml.create_ld_on_new_array / create_ld_on_existed_array 下发创建命令:
    • .smld 调用 Vendor SML SO 执行实际创建操作。
  5. 创建成功后,触发拓扑刷新:
    • c_array:get_array_info() 更新阵列信息;
    • c_volume:get_volume_info() 更新逻辑盘属性;
    • c_drive 集合更新 RefVolumeList 等引用关系。

该场景贯穿:

  • 上层接口(Redfish/IPMI/RPC)→ 应用层校验 → SML 适配 → Vendor SO 执行 → 领域对象刷新。

5.3 场景三:硬盘健康检测与告警

目标:周期性评估盘健康状态并上报告警。

  1. metric_collect 创建周期性任务,遍历 c_drive 集合。
  2. 对每块盘,借助:
    • sml.pd_operationsmlib_get_pd_sas_smart_info 等接口采集 SMART/错误统计;
    • libpd_log_parse*.so 解析原始日志,识别关键指标。
  3. 结果回灌到 c_drive 对象的健康字段(如 prefault_by_*, fault_by_*, basic_diagnose_info 等)。
  4. c_drive 根据配置表 PREDICTIVE_FAILURE_SOURCE_CFG / FAULT_SOURCE_CFG 进行多源汇总并防抖:
    • 通过 mc.debounce 避免抖动引起频繁告警;
    • 调用 add_event / error_engine 上报告警与维护日志。
  5. 上层通过 Redfish 或告警系统看到统一的“硬盘健康状态”与“预故障/故障告警”。

该场景体现:

  • 逻辑视图:健康信息聚合在 c_drive
  • 开发视图metric_collect/diagnose 等模块组合;
  • 进程视图:周期任务 + SML 调用;
  • 物理视图:依赖 SMART 日志解析 SO 库。

通过以上 4+1 视图,可以从不同角度理解 Storage 模块在 openUBMC 中的角色与设计理念:上承平台接口与运维场景,下接多厂商 RAID/HBA 驱动与硬件拓扑,在逻辑上通过统一领域模型(Controller/Array/Volume/Drive)提供一致的管理能力,在物理上通过 SML 与一系列 SO 库实现对异构硬件的抽象和适配。