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.lua、object_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*.so、libsml_custom_base*.so、libpd_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_bus与storage/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_sml与src/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.PCIeDevice、bmc.kepler.Systems.Mctp.*、bmc.kepler.Systems.Bios、bmc.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.callbackworker,用于处理 I2C/MCTP 通信。- 当控制器与 RAID 驱动需要通过 hwproxy/BMA 访问实际总线时,由该 worker 协调。
3.2 典型调用路径
场景:获取逻辑盘信息
- 上层通过 Redfish / RPC 调用逻辑盘查询接口(封装在
rpc_service_volume中)。 rpc_service_volume调用storage_app内部的查询函数。storage_app使用c_volume集合(由volume_collection管理)返回当前缓存状态;- 如需刷新,则触发
c_volume:get_volume_info():- 调用
sml.get_ctrl_init_state确认控制器已初始化; - 调用
sml.get_ld_info(...)获取当前逻辑盘详细信息; - 更新
c_volume字段并触发on_update等信号。
- 调用
场景:更新控制器 SlowDisk 检测模式
- 上层通过 RPC/Redfish 调用设置慢盘检测模式接口;
- 进入
c_controller中的模式转换逻辑(handle_slow_disk_mode_setting):- 校验控制器
OOBSupport与is_working状态; - 根据当前模式与目标模式,从
MODE_TRANSITIONS表计算所需底层操作序列; - 通过
sml.ctrl_operation下发到 RAID 控制器;
- 校验控制器
- 根据返回码决定是否更新本地状态并记录操作日志。
3.3 并发与资源管理
- 通过 Skynet 的
queue与worker机制:- 保证对单控制器的关键操作串行执行,避免竞态。
- 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/lualib、src/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动态加载。
- 各厂商 RAID/HBA 驱动所提供的核心动态库,由 SML C 适配层通过
4.2 关键 SO 依赖链
- SML 基础库加载(
l_sml_adapter.cpp)- 决策逻辑:
- 若
TypeId <= PMC_2100_8I_SMART_HBA,优先加载SML_CUSTOM_BASE_LIB_PATH(libsml_custom_base*.so)。 - 对于自研 1880 系列控制器(
HI1880_SP186_M_*),加载SML_BASE_LIB_PATH(libsml_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.so、libsml_pmc.so、libsml_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.c 的 smlib_add_ctrl 流程中:先根据 OOB 注册信息拼出 controller_id 与 vender_index,再调用 ctrl_manage_object_constructor,为每个 SML_CTRL_S 填入对应厂商的函数指针表(并在此过程中 dlopen 各厂商的 SML 封装库)。
调用位置与顺序(adapter.c)
smlib_add_ctrl(SML_CTRL_OOB_INFO_S *ctrl)校验ctrl_index,分配gCtrlList[ctrl_index]。construct_ctrl_id(ctrl, &controller_id, &vender_index):根据i_controller_typeid(及 PMC 场景下的 MCTPeid/phy_addr等)查表,得到vender_index与编码进controller_id的类型位(如 LSI 的 StoreLib 大类、Histore 的 HBA/RAID 类型等)。ctrl_manage_object_constructor(vender_index, gCtrlList[ctrl_index]):按厂商分支dlopen+dlsym,把pfn_add_ctrl、pfn_init_ctrl_over_i2c/pfn_init_ctrl_over_pcie等挂到该控制器对象上。- 若构造成功,再调用
gCtrlList[ctrl_index]->pfn_add_ctrl(controller_id)做厂商侧“加入控制器”的初始化。
construct_ctrl_id 与编译形态
工程中存在两套 construct_ctrl_id 实现(链接时二选一,不会同时进同一个二进制):
vendor_other/vendor_other.c:覆盖 博通 LSI(含g_sml_lsi_ctrl[]类型表)与 PMC(PMC_3152_8I_SMART_RAID、PMC_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_LSI | register_storelib_function | /usr/lib64/libsml_lsi.so(LSI_SML_LIB_PATH) | lsi_*:含 lsi_init_ctrl_manage(I2C 带外初始化入口)等;StoreLib 分库见下文 4.3。 |
VENDER_PMC | register_storagecore_function | /usr/lib64/libsml_pmc.so | pmc_*:初始化走 pfn_init_ctrl_over_pcie(与 PMC StorageCore 交互,见 sc_misc.c 等对 libsyscore.so 的二次加载)。 |
VENDER_HUAWEI | register_histore_function | BUILD_SML_OPEN 时 /usr/lib64/libsml_histore_open.so,否则 /usr/lib64/libsml_histore.so | histore_*:带外/升级等走 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 类型位,二次dlopenlibraidblib.so、libstorelibir*.so等 Broadcom 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_constructor → register_storelib_function 路径中),由 lsi_init_ctrl_manage 再按编码在 controller_id 中的 StoreLib 类型决定加载哪一份 StoreLib .so,并完成 I2C 注入与库内初始化。
与上层衔接
register_storelib_function将lsi_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)
- 从
ctrl_id解析库类型:libType = LSI_STORELIB_TYPE_VALID_BIT(ctrl_id)(同一套ctrl_id编码里携带“应使用哪类 StoreLib”的信息)。 lsi_mutex_init():按库类型加锁,避免多控制器并发初始化竞态。InitCtrlDiagEvent(ctrl_id):初始化背板/硬盘 sense 等诊断上下文。- 按
libType分支:若对应类型的 StoreLib 尚未加载,则调用对应的LoadStorelib*(内部dlopen+dlsym),再InitializeStorelib(libType, &ctrlList)向库下发 Discover/Init;部分分支在初始化成功后还会做 OOB 就绪校验(见下表)。
各类型对应的动态库与关键符号(文件路径均为 BMC 上典型安装位置)
库类型(libType) | 动态库路径 | 加载函数 | 向库注册 I2C | 命令分发符号 |
|---|---|---|---|---|
SL_LIB_TYPE_STORELIB(MegaRAID 等 MR) | /usr/lib64/libraidblib.so | LoadStorelibMR | RegisterI2CFunc(非空则注册 hwproxy 下发的读写) | ProcessLibCommand |
SL_LIB_TYPE_STORELIBIR | /usr/lib64/libstorelibir.so | LoadStorelibIR | 无(IR 路径不注册 I2C) | ProcessLibCommandIR |
SL_LIB_TYPE_STORELIBIR_2 | /usr/lib64/libstorelibir2.so | LoadStorelibIR2 | 无 | ProcessLibCommandIR2 |
SL_LIB_TYPE_STORELIBIR_3 | /usr/lib64/libraidblibir3.so | LoadStorelibIR3 | MPCI_RegisterI2CFunc | ProcessLibCommandIR3,另需 SetInitDone |
SL_LIB_TYPE_STORELIBIT(IT 模式) | /usr/lib64/libraidblibit.so | LoadStorelibIT | RegisterI2CFunc | ProcessLibCommandIT,另需 SetInitDone |
SL_LIB_TYPE_STORELIBCUSTOM | /usr/lib64/libraidbcustom.so | LoadStorelibCustom | RegisterI2CFunc | ProcessLibCommandIT(自定义库复用 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:加载后同样带
reDiscover的InitializeStorelib;初始化成功后通过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_manager和sml完成领域模型装配。
5.2 场景二:创建逻辑盘
目标:在指定控制器与物理盘集合上创建新的逻辑盘。
- 用户通过 WebUI/Redfish 发起创建 Volume 请求(指定控制器、RAID 级别、成员盘列表等)。
- 上层网关将请求转换为对 Storage RPC 的调用(
rpc_service_volume)。 rpc_service_volume在storage_app中校验资源状态(控制器健康、成员盘可用等)。- 通过
sml.ctrl_operation或sml.create_ld_on_new_array / create_ld_on_existed_array下发创建命令:- 由
.smld调用 Vendor SML SO 执行实际创建操作。
- 由
- 创建成功后,触发拓扑刷新:
c_array:get_array_info()更新阵列信息;c_volume:get_volume_info()更新逻辑盘属性;c_drive集合更新RefVolumeList等引用关系。
该场景贯穿:
- 上层接口(Redfish/IPMI/RPC)→ 应用层校验 → SML 适配 → Vendor SO 执行 → 领域对象刷新。
5.3 场景三:硬盘健康检测与告警
目标:周期性评估盘健康状态并上报告警。
metric_collect创建周期性任务,遍历c_drive集合。- 对每块盘,借助:
sml.pd_operation、smlib_get_pd_sas_smart_info等接口采集 SMART/错误统计;libpd_log_parse*.so解析原始日志,识别关键指标。
- 结果回灌到
c_drive对象的健康字段(如prefault_by_*,fault_by_*,basic_diagnose_info等)。 c_drive根据配置表PREDICTIVE_FAILURE_SOURCE_CFG/FAULT_SOURCE_CFG进行多源汇总并防抖:- 通过
mc.debounce避免抖动引起频繁告警; - 调用
add_event/error_engine上报告警与维护日志。
- 通过
- 上层通过 Redfish 或告警系统看到统一的“硬盘健康状态”与“预故障/故障告警”。
该场景体现:
- 逻辑视图:健康信息聚合在
c_drive; - 开发视图:
metric_collect/diagnose等模块组合; - 进程视图:周期任务 + SML 调用;
- 物理视图:依赖 SMART 日志解析 SO 库。
通过以上 4+1 视图,可以从不同角度理解 Storage 模块在 openUBMC 中的角色与设计理念:上承平台接口与运维场景,下接多厂商 RAID/HBA 驱动与硬件拓扑,在逻辑上通过统一领域模型(Controller/Array/Volume/Drive)提供一致的管理能力,在物理上通过 SML 与一系列 SO 库实现对异构硬件的抽象和适配。