libsomp
版本信息
| 项目 | 内容 |
|---|---|
| 组件版本 | 3.10.15 |
| 首发版本 | openUBMC 26.09 |
| 文档作者 | openUBMC 社区 |
| 最后更新 | 2026-09-15 |
| 许可证 | Mulan PSL v2 |
1. 组件概述
1.1 组件简介
libsomp 是 openUBMC 微组件框架中的共享内存库,mds/service.json 中 type 为 library,description 为 library of shm for micro component framework。编译产物为共享库 libsomp.so(CMake 目标名 somp),安装到 usr/lib64,权限配置见 dist/permissions.ini(usr/lib64/libsomp.so,mode 554,uid/gid 0)。
组件不启动独立服务进程、不注册资源协作接口(无 model.json,mds/service.json 的 required 为空),也不提供 IPMI 命令或 Lua 模块入口。公开能力分为两部分:
- shmlock:跨进程对象级读写锁(C API + C++ RAII 封装)。
- shm / D-Bus 基础设施:MDB 对象树、信号匹配、IPMI 路由表、消息队列等共享内存数据结构,供微组件框架在进程间共享对象元数据。
构建依赖见 mds/service.json:boost/1.87.0.b004@openubmc/stable、huawei_secure_c/[>=1.0.0]@openubmc/stable、liblogger/[>=1.80.5];CMake 还链接 glib-2.0 与 dbus-1。Conan 包对外导出 libs = ["somp"]、libdirs = ["usr/lib64"]。
1.2 解决什么问题
多进程 BMC 服务需要在共享内存中同步对象访问,并共享 D-Bus/MDB 对象树。直接使用 System V 信号量或 Boost.Interprocess 需要自行处理进程崩溃残留锁、PID 重用、服务心跳和对象路径树。libsomp 提供:
- 以
object_id为粒度的读写锁,带所有者跟踪与健康检查。 - 进程指纹(PID + 启动时间 + 随机 token)避免 PID 重用误判。
- C++
LockHandleRAII,避免漏释放。 - 共享内存中的 MDB 对象树、接口/属性/方法/信号、Match 规则和 IPMI 路由。
1.3 核心功能
- 对象级读写锁:阻塞获取、非阻塞尝试、写锁降级为读锁、批量读锁。
- 进程/服务注册与注销;可选心跳,超时未心跳的服务视为死亡。
- 健康监控线程:清理死亡进程残留锁;陈旧锁阈值见
SHM_STALE_LOCK_THRESHOLD_SEC(30 秒)。 - 单例
ShmLockManager:打开或创建段MDBUS_SHM_LOCK_MANAGER(默认 10 MiB,环境变量MDBUS_SHM_LOCK_MANAGER_SIZE,上限 100 MiB)。 - 单例
shm::shared_memory:打开或创建段MDBUS_SHM(默认 90 MiB,测试构建 50 MiB;环境变量MDBUS_SHM_SIZE,上限 200 MiB)。 - D-Bus Match 规则树(进程内
DBus::Match::Matchs与共享内存shm::matchs)。 - 路径分段与忽略大小写比较工具
DBus::utils。
1.4 关键术语表
| 术语 | 解释 |
|---|---|
| object_id | 锁保护的对象标识。GLOBAL_LOCK_OBJECT_ID 为 1,预留为全局对象锁。 |
ServiceId / shm_service_id_t | 逻辑进程 ID、进程内 service_id、incarnation 组成的服务标识。 |
LockHandle / shm_lock_handle_t | 一次加锁结果;C++ 析构时调用 shm_lock_release。 |
| 进程指纹 | pid + start_time + random_token + sequence,用于区分 PID 重用。 |
| incarnation | 服务实例代数,用于识别服务重启。 |
| MDBUS_SHM | Boost.Interprocess 共享内存段名,路径 /dev/shm/MDBUS_SHM。 |
| MDBUS_SHM_LOCK_MANAGER | 锁管理器共享内存段名,路径 /dev/shm/MDBUS_SHM_LOCK_MANAGER。 |
| well-known name | 不以 : 开头的总线名;is_wellknow_name() 按此判断。 |
| RAII | LockHandle 离开作用域自动释放锁。 |
1.5 外部交互边界图
仓库中没有 model.json 或 ipmi.json。以下接口均为进程内 C/C++ API,不能通过 busctl 调用。
2. API 使用说明与示例
头文件安装路径:
| 类别 | 头文件 | 说明 |
|---|---|---|
| C 锁 API | shmlock/shmlock.h、shmlock/shmlock_constants.h | 错误码、容量常量和全部 C 函数。 |
| C++ 锁 API | shmlock/shmlock_manager.h | 转发到 shmlock_manager_core.h,导出 LockManager、ShmLockManager。 |
| C++ 辅助类型 | shmlock/lock_handle.h、service_id.h、lock_stats.h、shmlock_exception.h | RAII 句柄、服务 ID、统计、LockException。 |
| 共享内存对象树 | dbus/shm_tree/*.h | shared_memory、object、tree、message_queue、ipmi_route_table 等。 |
| Match | dbus/match/rule.h、matchs.h、node.h | D-Bus 匹配规则。 |
| 工具 | dbus/utils/utils.h | 路径分段、忽略大小写比较。 |
容量与默认超时(shmlock.h / shmlock_constants.h):
| 宏 | 取值 | 含义 |
|---|---|---|
SHM_MAX_PROCESSES | 64 | 最大进程数。 |
SHM_MAX_SERVICES | 4096 | 全系统最大服务数(64×64)。 |
SHM_MAX_OBJECTS | 10000 | 锁表最大对象数。 |
SHM_LOCK_TIMEOUT_MS | 5000 | C 层注释中的默认锁超时。 |
SHM_TIMEOUT_DEFAULT_MS | 1000 | C++ acquire_*_lock 默认超时。 |
MAX_SERVICE_ID | 63 | ShmLockManager::allocate_service_id 进程内服务 ID 上限(0–63)。 |
2.1 C 初始化与进程/服务注册
功能说明
在调用方提供的共享内存上初始化锁管理器,并注册进程与服务。进程注册会分配逻辑进程 ID 并写入进程指纹。
#include "shmlock/shmlock.h"
shm_lock_manager_t *manager = NULL;
int ret = shm_lock_init(&manager, shm_base, shm_size);
if (ret != SHM_LOCK_OK) {
return ret;
}
shm_service_id_t process_svc;
ret = shm_lock_register_process(manager, &process_svc);
shm_service_id_t service;
ret = shm_lock_register_service(manager, process_svc.logical_process_id, 1, &service);参数说明
| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
shm_lock_init(manager, shm_base, shm_size) | 输出管理器指针、共享内存基址与大小 | shm_lock_error_t | 在 shm_base 上构造 shm_lock_manager_t。 |
shm_lock_destroy(manager) | 管理器 | void | 销毁管理器。 |
shm_lock_register_process(manager, out_service) | 管理器、输出 shm_service_id_t | shm_lock_error_t | 注册当前进程。 |
shm_lock_unregister_process(manager, logical_process_id) | 管理器、逻辑进程 ID | shm_lock_error_t | 注销进程。 |
shm_lock_register_service(manager, logical_process_id, service_id, service) | 逻辑进程 ID、进程内服务 ID、输出服务标识 | shm_lock_error_t | 在已注册进程上注册服务。 |
shm_lock_unregister_service(manager, service) | 服务标识 | shm_lock_error_t | 注销服务。 |
shm_lock_is_process_alive(manager, logical_process_id) | 逻辑进程 ID | bool | 按指纹检查进程是否存活。 |
返回值与异常
C 接口返回 shm_lock_error_t,不抛异常。shm_lock_error_string(error) 将错误码转为英文描述。
| 返回值 | 含义 | 触发条件 | 处理建议 |
|---|---|---|---|
0(SHM_LOCK_OK) | 成功 | 参数合法且操作完成 | 无 |
-1(SHM_LOCK_TIMEOUT) | 超时 | 在超时时间内未能完成锁操作 | 增大 timeout_ms 或检查持锁方 |
-2(SHM_LOCK_INVALID) | 参数非法 | 空指针、未初始化或句柄无效 | 检查管理器、服务 ID 和句柄 |
-3(SHM_LOCK_NOMEM) | 内存不足 | 共享内存或锁表耗尽 | 扩大段大小或减少对象数 |
-4(SHM_LOCK_BUSY) | 锁忙 | 非阻塞获取时锁被占用 | 稍后重试或改用阻塞接口 |
-5(SHM_LOCK_OWNER_DEAD) | 持有者已死 | 锁所有者进程/服务已失效 | 走健康检查/强制解锁路径 |
应用场景
业务进程在使用读写锁前必须先 shm_lock_init(或由 C++ LockManager/ShmLockManager 完成),再注册进程和服务。
限制条件
- 锁表与进程映射位于共享内存,跨进程只能通过相对偏移访问(
mgr_locks_ptr等内联辅助函数)。 SHM_MAX_PROCESSES为 64;服务总数上限 4096。- 进程异常退出时不应再调用
shm_lock_unregister_service;应依赖shm_lock_health_check清理,避免与自动清理重复解锁。
调试示例
C 调试
shm_service_id_t process_svc;
if (shm_lock_register_process(manager, &process_svc) != SHM_LOCK_OK) {
return -1;
}
shm_service_id_t service;
if (shm_lock_register_service(manager, process_svc.logical_process_id, 1, &service) != SHM_LOCK_OK) {
return -1;
}2.2 C 锁操作
功能说明
按 object_id 获取读锁或写锁。读锁可被多个服务同时持有;写锁互斥。object_id == 1 为全局对象,使用 global_reader_refcount 支持同一 service 的读锁重入;其他对象用 reader_bitmap,无重入计数。
参数说明
| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
shm_lock_acquire_read(manager, object_id, service, timeout_ms, handle) | 对象 ID、服务、超时毫秒、输出句柄 | shm_lock_error_t | 阻塞获取读锁。 |
shm_lock_acquire_write(...) | 同上 | shm_lock_error_t | 阻塞获取写锁。 |
shm_lock_try_acquire_read(...) | 无超时参数 | shm_lock_error_t | 非阻塞读锁;忙则 SHM_LOCK_BUSY。 |
shm_lock_try_acquire_write(...) | 无超时参数 | shm_lock_error_t | 非阻塞写锁。 |
shm_lock_downgrade(handle) | 写锁句柄 | shm_lock_error_t | 写锁降级为读锁。 |
shm_lock_release(handle) | 句柄 | shm_lock_error_t | 释放锁。 |
shm_lock_acquire_multi_read(manager, object_ids, count, service, timeout_ms, handles) | 对象 ID 数组与输出句柄数组 | shm_lock_error_t | 批量读锁。 |
shm_lock_acquire_multi_write(...) | 同上 | shm_lock_error_t | 批量写锁。 |
shm_lock_release_multi(handles, count) | 句柄数组 | shm_lock_error_t | 批量释放。 |
shm_lock_force_unlock(manager, object_id) | 对象 ID | shm_lock_error_t | 强制解锁指定对象。 |
返回值与异常
成功为 SHM_LOCK_OK;超时、忙、参数非法见 2.1。shm_lock_type_t:SHM_LOCK_NONE=0、SHM_LOCK_READ=1、SHM_LOCK_WRITE=2。
应用场景
多进程并发读写共享对象;读多写少场景优先读锁。全局配置类对象可使用 object_id == 1。
限制条件
- 同一 Service 的多个线程并行访问同一非全局对象时,上层
reader_bitmap可能漏计数(源码 README 已说明)。 SHM_REFCOUNT_MAX为 255,全局对象上单服务并发读锁不超过该值。- 句柄是本地内存中的引用,不可放入共享内存跨进程传递。
调试示例
shm_lock_handle_t handle;
int ret = shm_lock_acquire_write(manager, 12345, &service, 1000, &handle);
if (ret != SHM_LOCK_OK) {
fprintf(stderr, "%s\n", shm_lock_error_string(ret));
return ret;
}
ret = shm_lock_downgrade(&handle);
ret = shm_lock_release(&handle);2.3 C 健康监控与统计
功能说明
注册服务心跳、启动后台健康检查、查询统计信息或转储锁表状态。
参数说明
| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
shm_lock_register_service_heartbeat(manager, service, heartbeat_interval_ms) | 服务、心跳间隔 | shm_lock_error_t | 注册心跳;超时倍数为 SHM_HEARTBEAT_TIMEOUT_MULTIPLIER(3)。 |
shm_lock_service_heartbeat(service) | 服务 | shm_lock_error_t | 发送一次心跳。 |
shm_lock_check_service_alive(manager, service) | 服务 | shm_lock_error_t | 检查服务是否存活。 |
shm_lock_start_health_monitor(manager) | 管理器 | shm_lock_error_t | 启动后台健康线程。 |
shm_lock_stop_health_monitor(manager) | 管理器 | shm_lock_error_t | 请求停止健康线程。 |
shm_lock_health_check(manager) | 管理器 | shm_lock_error_t | 立即执行一次健康检查。 |
shm_lock_cleanup_dead_readers(manager) | 管理器 | shm_lock_error_t | 清理死亡读者。 |
shm_lock_get_stats(manager, stats) | 输出 shm_lock_stats_t | shm_lock_error_t | 统计加解锁、超时、活跃读写锁、竞争次数。 |
shm_lock_dump_state(manager, buffer, size) | 输出缓冲区 | shm_lock_error_t | 最多转储 SHM_DUMP_MAX_LOCKS(20)个锁。 |
shm_lock_stats_t 字段:total_locks、total_unlocks、total_timeouts、active_read_locks、active_write_locks、lock_contentions。头文件注明死锁检测已移除。
返回值与异常
同 2.1。健康检查成功返回 SHM_LOCK_OK。
应用场景
守护进程创建锁管理器后调用 shm_lock_start_health_monitor;业务服务按注册间隔调用 shm_lock_service_heartbeat。
限制条件
- 心跳超时为间隔的 3 倍;超时未心跳的服务会被判定死亡并释放其锁。
vos_get_tick/vos_get_tick_timespec用于规避单调时钟/真实时钟回跳;时钟跳跃阈值见SHM_CLOCK_JUMP_THRESHOLD_SEC(7200 秒)。DEBUG构建才导出shm_lock_debug_get_semid_for_object。
调试示例
shm_lock_register_service_heartbeat(manager, &service, 5000);
shm_lock_start_health_monitor(manager);
shm_lock_service_heartbeat(&service);
shm_lock_stats_t stats;
shm_lock_get_stats(manager, &stats);
char buf[8192];
shm_lock_dump_state(manager, buf, sizeof(buf));2.4 C++ LockManager / LockHandle / ServiceId
功能说明
namespace shmlock 对 C API 的封装。失败时抛 LockException(what() 为描述,error_code() 为 shm_lock_error_t)。LockHandle 禁止拷贝、支持移动;析构或 release() 时释放锁。
包含头文件:
#include "shmlock/shmlock_manager.h"构造方式:
| 构造函数 | 说明 |
|---|---|
LockManager(managed_shared_memory& shm, size_t size = 0) | 从 Boost 段中分配并初始化。 |
LockManager(void* shm_base, size_t size) | 使用已有内存基址。 |
LockManager(managed_shared_memory& shm, const char* block_name, size_t size_bytes) | 命名块:size_bytes > 0 时创建并初始化;size_bytes == 0 时仅附着已有 meta。附着方析构不会销毁底层结构。 |
参数说明
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
register_process() | 无 | ServiceId | 注册进程。 |
unregister_process(logical_process_id) | uint16_t | void | 失败抛 LockException。 |
register_service(logical_process_id, service_id) | 逻辑进程 ID、服务 ID | ServiceId | 注册服务。 |
unregister_service(service) | ServiceId | void | 正常退出前调用。 |
is_process_alive(logical_process_id) | uint16_t | bool | 进程存活检查。 |
acquire_read_lock(object_id, service, timeout_ms) | 默认超时 SHM_TIMEOUT_DEFAULT_MS | LockHandle | 阻塞读锁。 |
acquire_write_lock(...) | 同上 | LockHandle | 阻塞写锁。 |
try_acquire_read_lock / try_acquire_write_lock | 无超时 | LockHandle | 失败抛 LockException(含 SHM_LOCK_BUSY)。 |
acquire_multi_read_locks(object_ids, service, timeout_ms) | vector<uint64_t> | vector<LockHandle> | 批量读锁。C++ 层未封装批量写锁。 |
force_unlock(object_id) | 对象 ID | void | 强制解锁。 |
get_stats() | 无 | LockStats | total_deadlocks 固定为 0。 |
health_check() | 无 | bool | shm_lock_health_check == SHM_LOCK_OK。 |
register_service_heartbeat / service_heartbeat / check_service_alive | 服务与间隔 | bool | 心跳相关。 |
start_health_monitor() | 无 | bool | 等价于 start_health_monitoring()。 |
stop_health_monitoring() | 无 | bool | 停止健康线程。 |
dump_state() | 无 | std::string | 状态转储。 |
LockHandle 方法:release()、is_valid()、type()、downgrade()(仅写锁可降级)。
ServiceId 方法:logical_process_id()、service_id()、incarnation()、get() / get_internal_id()。
返回值与异常
未初始化时抛 LockException(SHM_LOCK_INVALID, "Lock manager not initialized")。锁操作失败时消息为 Failed to ...: + shm_lock_error_string。附着命名块但找不到 block_name.meta 时抛 Attach failed: meta not found。
应用场景
测试与自定义共享内存段使用 LockManager;openUBMC 运行时推荐 2.5 节单例。
限制条件
- 拷贝
LockHandle已删除,只能移动。 - 仅进程正常退出前调用
unregister_service;异常退出依赖健康检查。 - C++ 未封装
shm_lock_acquire_multi_write/shm_lock_release_multi/shm_lock_cleanup_dead_readers。
调试示例
C++ 调试(创建 + 附着)
#include "shmlock/shmlock_manager.h"
#include <boost/interprocess/managed_shared_memory.hpp>
constexpr const char* kSegmentName = "ubmc_shmlock";
constexpr const char* kManagerBlock = "ubmc_lock_manager_block";
constexpr std::size_t kSegmentSize = 16 * 1024 * 1024;
boost::interprocess::managed_shared_memory shm(
boost::interprocess::create_only, kSegmentName, kSegmentSize);
shmlock::LockManager manager(shm, kManagerBlock, kSegmentSize);
manager.start_health_monitor();
auto process = manager.register_process();
auto service = manager.register_service(process.logical_process_id(), 1);
manager.register_service_heartbeat(service, 5000);
{
auto lock = manager.acquire_write_lock(12345, service);
(void)lock;
}
auto read_lock = manager.acquire_read_lock(12345, service, 1000);
if (read_lock.is_valid()) {
read_lock.release();
}客户端以 open_only 打开段,并用 LockManager(shm, kManagerBlock, 0) 附着。test_package/test_init_attach_gtest.cpp 验证附着方析构不会销毁底层锁表。
2.5 C++ ShmLockManager 单例
功能说明
面向 BMC 运行时的 Facade:open_or_create 段 MDBUS_SHM_LOCK_MANAGER,内部块名 "lock_manager",并把文件属主改为 uid 101(comm_user)、gid 103(apps),权限 0660。
shmlock::ShmLockManager& mgr = shmlock::ShmLockManager::get_instance();
uint16_t sid = shmlock::ShmLockManager::allocate_service_id();
mgr.register_service(sid);
auto lock = mgr.acquire_read_lock(object_id, sid);参数说明
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
get_instance() | 无 | ShmLockManager& | 进程内单例;构造失败抛 std::runtime_error("open shm failed")。 |
destroy() | 无 | bool | 删除单例,并 semctl(IPC_RMID) 清理 key 0x12340000 的 System V 信号量集,再 shared_memory_object::remove("MDBUS_SHM_LOCK_MANAGER")。 |
allocate_service_id() | 无 | uint16_t | 进程内递增,最大返回 63。 |
allocate_object_id(path) | 对象路径 | uint32_t | g_str_hash(path);若结果为 1 则改为 2,避免占用全局锁 ID。 |
register_service(service_id) | 进程内服务 ID | ServiceId | 在本进程已注册的 logical process 上注册,并缓存到内部 map。 |
service_heartbeat(service_id) | 服务 ID | bool | map 中无该 ID 时会先 register_service。 |
acquire_read_lock / acquire_write_lock | object_id、service_id、可选超时 | LockHandle | 未缓存的 service_id 会先注册。 |
get_logical_process_id() | 无 | uint16_t | 本进程逻辑 ID。 |
get_base() | 无 | LockManager& | 底层管理器;未初始化抛 std::runtime_error。 |
get_stats() / dump_state() | 无 | 统计 / 字符串 | 转发到 LockManager。 |
环境变量 MDBUS_SHM_LOCK_MANAGER_SIZE:十进制字节数;小于 0 回退默认 10 MiB,大于 100 MiB 截断为上限,0 也回退默认值。
返回值与异常
单例打开失败为 std::runtime_error("open shm failed")。锁失败仍为 LockException。
应用场景
微组件框架在各业务进程中获取同一锁管理器段,按对象路径分配 object_id 后加锁。
限制条件
allocate_object_id使用 32 位哈希,不同路径可能碰撞。allocate_service_id到达 63 后不再递增,后续调用重复返回 63。destroy()会删除整段共享内存并移除信号量,仅应在明确的框架销毁路径调用。
调试示例
auto& mgr = shmlock::ShmLockManager::get_instance();
uint16_t service_id = shmlock::ShmLockManager::allocate_service_id();
uint32_t object_id = shmlock::ShmLockManager::allocate_object_id("/bmc/kepler/Example");
mgr.register_service(service_id);
try {
auto handle = mgr.acquire_write_lock(object_id, service_id, 1000);
(void)handle;
} catch (const shmlock::LockException& e) {
// e.error_code() 为 shm_lock_error_t
}2.6 shm::shared_memory 与对象树
功能说明
shm::shared_memory::get_instance() 打开或创建段 MDBUS_SHM(/dev/shm/MDBUS_SHM),在段内 find_or_construct<shared_memory_base>("shared_memory_base")。默认大小 90 MiB(ENABLE_TEST 时 50 MiB)。环境变量 MDBUS_SHM_SIZE 为十进制字节;非法或非正回退默认,超过 200 MiB 截断;当 /proc/meminfo 的 MemTotal ≤ 2 GiB 时,自定义大小不会超过默认值。文件属主同样设为 uid 101、gid 103,权限 0660。
对象路径必须以 / 开头,按段插入 tree_node。object 上可注册 interface,再添加 method / property / signal。
参数说明
shm::shared_memory 主要方法:
| 方法 | 说明 |
|---|---|
get_instance() | 单例;失败抛 std::runtime_error("open shm failed")。 |
get_base() | 返回 shared_memory_base&。 |
get_segment_manager() | Boost 段管理器。 |
get_tree(wellknow_name) | 按 well-known name 取或创建 object_tree;名称非法抛 invalid wellknow name。 |
remove_tree(wellknow_name) | 删除树上 MDB 对象、订阅规则,并擦除树。 |
get_wellknow_names() / get_unique_names() | 名称到 object_tree 的 map。 |
add_mdb_object / remove_mdb_object / find_mdb_object | 维护 path+interface 视图。 |
query_interface_view(iface_name, filter) | 按接口名查询。 |
set_harbor_name / del_harbor_name / get_harbor_name | unique name 与 harbor 映射。 |
lock / unlock / lock_shared / unlock_shared | 当前实现中 shared 变体与独占锁均转发到 get_base().lock()/unlock()。 |
is_exist() | 检查 /dev/shm/MDBUS_SHM 是否存在。 |
destory() | 源码头文件拼写为 destory(不是 destroy):移除共享内存段。 |
ipmi_match(net_fn, cmd, payload) | 查询 IPMI 路由,返回 ipmi_match_ret_t。 |
add_ipmi_cmd / remove_ipmi_cmd | 维护 IPMI 命令表。 |
new_mutex(name) | 在共享内存中创建/获取命名互斥量。 |
shm::object / interface / property / method / signal 用于在树上注册 D-Bus 风格成员(add_m / add_p / add_s、find_m / find_p / find_s)。tree_base::register_object / find_object / unregister_object / travel 操作路径树。
object_tree::create_message_queue(shm, size) 创建进程间消息队列;push_back(data, timeout_ms) / pop_front(cb, timeout_ms, max_read_count, read_buf)。单条消息长度不能超过 uint16_t 最大值。
shm::ipmi_handler 保存 net_fn、cmd、priority、privilege、filter、path 等;ipmi_route_node 按 payload 分段组织处理器。
返回值与异常
路径不以 / 开头时 tree 抛 std::runtime_error("tree: invalid path")。打开段失败抛 open shm failed。get_tree 在非 well-known 名称时抛 invalid wellknow name。
应用场景
微组件框架把各服务的 D-Bus 对象、接口和 IPMI 命令放入共享内存,供其他进程按路径或 well-known name 查找。上层业务一般不直接操作这些类,而是通过框架封装。
限制条件
- 对象路径最长相关约束:源码中
MAX_DBUS_PATH为 512。 lock_shared与lock当前实现相同,不能当作真正的读写锁语义使用。- 销毁 API 符号名为
destory。 - 仓库
test_package对对象树没有与 shmlock 同级别的用例覆盖;调用约定以头文件为准。
调试示例
#include "dbus/shm_tree/shared_memory.h"
auto& shm = shm::shared_memory::get_instance();
shm::object_tree* tree = shm.get_tree("bmc.kepler.example");
shm::object& obj = tree->register_object(shm, "/bmc/kepler/Example/1");
auto iface = obj.register_interface(shm, false, "bmc.kepler.Example");
iface->add_m(shm, "Ping", "", "s");2.7 DBus::Match 匹配规则
功能说明
进程内匹配引擎,按 D-Bus match rule 字段过滤消息。Rule 为链式配置;Matchs::add_rule 注册规则与回调;run / test_match 对 Context 求值。
参数说明
DBus::Match::Rule 可配置字段:
| 方法 | 含义 |
|---|---|
type(MessageType) | signal / method_call / method_return / error / invalid。 |
sender / destination / interface / member | 字符串匹配字段。 |
path / path_namespace | 对象路径;namespace 允许前缀匹配,段可为 *。 |
property_interface / properties | 属性变更信号过滤。 |
set_eavesdrop(bool) | eavesdrop 标志。 |
as_string() | 序列化为 match 字符串。 |
disconnect() | 调用已绑定 slot_t::remove。 |
路径含 * 时,仅整段为 * 合法,否则抛 D-Bus error: invalid fuzzy object path;最终路径须通过 dbus_validate_path。
Matchs:
| 方法 | 说明 |
|---|---|
add_rule(RulePtr&, match_cb_t&&) | 正在 run 时延迟到本次匹配结束再插入。 |
run(Context&) | 按规则树匹配并执行回调。 |
test_match(Context&) | 只测试是否匹配。 |
共享内存侧对应 shm::matchs::add_rule / remove_rules / run / test_match,订阅者是 object_tree*。
返回值与异常
非法对象路径抛 std::runtime_error。run / test_match 返回 bool。
应用场景
D-Bus 信号订阅与属性变更(org.freedesktop.DBus.Properties.PropertiesChanged,签名 sa{sv}as)过滤。Context::extract_properties_change_info 从该信号提取接口名和属性表。
限制条件
源码未覆盖独立的 Lua/命令行调试接口;应在链接 libsomp 的宿主进程内调用。
调试示例
#include "dbus/match/matchs.h"
#include "dbus/match/rule.h"
auto rule = std::make_shared<DBus::Match::Rule>();
rule->type(DBus::Match::MessageType::signal)
.interface("org.freedesktop.DBus.Properties")
.member("PropertiesChanged")
.path_namespace("/bmc/kepler");
DBus::Match::Matchs matchs;
matchs.add_rule(rule, [](DBus::Match::Context& ctx) {
(void)ctx;
});2.8 DBus::utils
功能说明
路径分段与忽略大小写字符串比较,供对象树和 Match 使用。test_package/DBusUtilsTest.cpp 覆盖 str_less_i。
参数说明
| 接口 | 说明 |
|---|---|
get_segment(path) | 返回可重复调用的 segment_t,每次给出 (当前段, 剩余路径)。 |
str_less_i(a, b) | 忽略大小写的小于比较。 |
string_view_ignore_case | 用于 map 查找的忽略大小写 string_view。 |
std_string_less | transparent comparator,支持 string 与 string_view。 |
返回值与异常
比较函数返回 bool。路径分段不抛异常;空路径得到空段。
应用场景
将 /bmc/kepler/Foo/1 拆成 bmc、kepler、Foo、1 以插入对象树。
限制条件
str_less_i 按忽略大小写比较,"Apple" 与 "apple" 互不小于。
调试示例
#include "dbus/utils/utils.h"
std::string_view path = "/bmc/kepler/Foo";
auto next = DBus::utils::get_segment(path);
auto first = next(); // first.first == "bmc"3. 组件扩展案例
3.1 扩展能力概述
libsomp 不提供插件机制、配置 DSL 或运行时扩展点。mds/service.json 的 required 为空,仓库没有 model.json 或 ipmi.json。扩展方式是代码级二次开发:
- 上层组件增加对
libsomp(Conan 库名somp)的链接依赖后调用现有 C/C++ API。 - 在
src/lualib-src/dbus/中新增实现,并保证include/与install(FILES ...)同步导出头文件。
3.2 扩展点说明
| 扩展位置 | 作用 | 触发时机 |
|---|---|---|
include/shmlock/shmlock.h 与 src/lualib-src/dbus/shmlock/shmlock.c | 新增 C 锁 API | 链接 libsomp 的进程调用时。 |
include/shmlock/shmlock_manager_core.h 与 shmlock_manager_core.cpp | 新增 C++ 封装 | 调用 LockManager / ShmLockManager 时。 |
src/lualib-src/dbus/shm_tree/ | 扩展对象树、IPMI 路由、消息队列 | shm::shared_memory::get_instance() 之后。 |
src/lualib-src/dbus/match/ | 扩展 Match 字段或匹配算法 | Matchs::add_rule / run 时。 |
src/lualib-src/dbus/CMakeLists.txt | 将新 .c/.cpp 编入 somp 目标 | 重新构建时。 |
3.3 二次开发指导
步骤一:实现 C 或 C++ 入口
锁相关新接口应同时更新 shmlock.h 与 shmlock.c,错误码复用 shm_lock_error_t,并通过 shm_lock_error_string 提供描述。C++ 封装失败时抛 LockException。
步骤二:导出头文件
include/shmlock/ 由 CMake install(DIRECTORY include/shmlock) 安装;shm_tree / match / utils 头文件需在 src/lualib-src/CMakeLists.txt 中逐文件 install(FILES ...)。
示例代码
上层组件使用运行时单例:
#include "shmlock/shmlock_manager.h"
auto& mgr = shmlock::ShmLockManager::get_instance();
uint16_t sid = shmlock::ShmLockManager::allocate_service_id();
mgr.register_service(sid);
auto lock = mgr.acquire_read_lock(
shmlock::ShmLockManager::allocate_object_id("/bmc/kepler/Example"), sid);验证方法
- 使用项目构建环境编译,确认生成
libsomp.so并安装到usr/lib64。 - 在
test_package中按test_base.hpp创建独立 Boost 段与LockManager,覆盖成功、超时、非法参数。 - 多进程场景参考
test_init_attach_gtest.cpp:一方size_bytes > 0创建,另一方size_bytes == 0附着。 - 预期成功返回
SHM_LOCK_OK或有效LockHandle;失败返回文档化错误码或LockException。
注意事项
- 保持 C 函数名、错误码和 C++ 类名向后兼容。
- 修改清理路径时必须同步 System V 层计数与 shmlock 层位图,避免重复解锁。
- 同一 Service 多线程访问同一对象时评估
reader_bitmap漏计数风险。 debug_log当前实现为空函数(见 4.1),新增日志前需先接通实际日志后端。- 若新增 API,须同步更新本文档的 API、错误码、日志和 FAQ 章节。
4. 日志说明
4.1 一键日志收集
libsomp 是动态库,不创建独立日志文件,也没有仓库内声明的 on_dump 回调。include/dbus/logging.h 定义 debug_log 宏,调用 shm_debug_log_func。当前 src/lualib-src/dbus/logging.cpp 中该函数体为空(注释为「暂时不打印日志」),get_log_module_name() 返回空字符串。因此一键日志收集默认不会得到 libsomp 自己的日志行。
| 文件路径 | 内容说明 |
|---|---|
| 不适用(实现为空) | 源码预留了 DLOG_ERROR / DLOG_WARN / DLOG_NOTICE / DLOG_INFO 等级别调用点,但当前不会输出。 |
/dev/shm/MDBUS_SHM | D-Bus/MDB 对象树共享内存段,不是文本日志。 |
/dev/shm/MDBUS_SHM_LOCK_MANAGER | 锁管理器共享内存段。 |
排障应使用 dump_state()、get_stats() 以及 System V 信号量/共享内存是否存在,而不是检索组件私有 log 文件。
4.2 关键日志信息
下列字符串存在于源码 debug_log(...) 调用中;在日志后端接通之前,它们不会出现在宿主日志里。接通后可按关键字过滤。
| 日志片段 | 级别 | 含义解读 | 建议处理动作 |
|---|---|---|---|
create shared memory lock manager | INFO | ShmLockManager 构造成功。 | 无。 |
open shm failed | 异常 | 打开/创建 MDBUS_SHM 或 MDBUS_SHM_LOCK_MANAGER 失败。 | 检查 /dev/shm 空间、权限和段是否被损坏。 |
LockManager: register_process failed, not initialized | ERROR | 管理器未初始化就注册进程。 | 先完成 LockManager 构造或 shm_lock_init。 |
LockManager: create path block=... free=... | NOTICE | 正在创建命名锁管理块。 | 确认 free 足够;WSL 上需保留 headroom。 |
chown lock_manager failed / chmod lock_manager failed | INFO | 无法将锁段改为 uid 101 / gid 103 或 0660。 | 检查进程权限;非 root 时常见。 |
chown shared_memory failed / chmod shared_memory failed | ERROR | MDBUS_SHM 属主或权限设置失败。 | 同上。 |
ShmLockManager: removed stale System V semaphore | INFO | destroy() 删除了残留信号量集。 | 确认没有其它进程仍在使用该 semid。 |
ShmLockManager: remove MDBUS_SHM_LOCK_MANAGER failed | WARN | shared_memory_object::remove 失败,改为 std::remove 文件。 | 检查段是否仍被映射。 |
invalid shared memory size | INFO | MDBUS_SHM_SIZE 非法,已回退默认或上限。 | 修正环境变量。 |
5. 问题定界指南
5.1 典型问题定界
| 问题描述 | 是否为本组件问题 | 判断依据 | 关键证据收集方法 |
|---|---|---|---|
链接不到 libsomp.so / somp | 可能是 | 未安装到 usr/lib64 或 Conan 未声明依赖。 | ldd 查看宿主进程;确认包中存在 usr/lib64/libsomp.so。 |
get_instance() 抛 open shm failed | 可能是 | Boost 段创建/打开失败。 | 检查 /dev/shm 容量、MDBUS_SHM* 文件和进程对 /dev/shm 的写权限。 |
acquire_*_lock 抛 Lock timeout | 可能是持锁方未释放,或本组件健康检查未清残留 | 错误码 -1。 | dump_state()、get_stats().total_timeouts;检查对端进程是否存活。 |
try_acquire_* 失败 Lock busy | 通常是调用方预期内 | 非阻塞接口在锁被占用时返回 -4。 | 改用带超时的阻塞接口,或确认写锁持有者。 |
附着 LockManager(..., 0) 抛 meta not found | 是调用顺序问题 | 创建方尚未写入 block_name.meta。 | 确认守护进程已用 size_bytes > 0 构造。 |
busctl 找不到 libsomp 服务 | 不是 | 组件不是 D-Bus 服务。 | 不要用 busctl;应在宿主进程内调 C/C++ API。 |
| 管理站/IPMI 客户端行为异常 | 通常不是 | 本组件只提供路由表存储,不实现 IPMI 协议栈。 | 到 ipmi_core / rmcpd 等组件定界。 |
5.2 错误码速查表
| 错误码 | 宏 / 异常 | 含义 | 排查建议 |
|---|---|---|---|
0 | SHM_LOCK_OK / "Success" | 成功 | 继续检查业务数据。 |
-1 | SHM_LOCK_TIMEOUT / "Lock timeout" | 锁等待超时 | 增大超时、检查写锁持有者、执行 health_check。 |
-2 | SHM_LOCK_INVALID / "Invalid parameter" | 参数或句柄非法 | 检查是否已 init、服务是否已注册、是否对非写锁 downgrade。 |
-3 | SHM_LOCK_NOMEM / "No memory available" | 内存不足 | 扩大 MDBUS_SHM_LOCK_MANAGER_SIZE 或减少对象。 |
-4 | SHM_LOCK_BUSY / "Lock busy" | 非阻塞获取时锁被占用 | 重试或改阻塞接口。 |
-5 | SHM_LOCK_OWNER_DEAD / "Lock owner dead" | 持有者已死 | 健康检查清理后重试。 |
| 其他 | "Unknown error" | 未识别错误码 | 记录原始整型值。 |
std::runtime_error("open shm failed") | 单例 | 共享内存段打开失败 | 见 5.1。 |
C++ LockStats.total_deadlocks 恒为 0(C 统计结构已移除死锁计数)。不要再按已删除的 SHM_LOCK_DEADLOCK 编写分支。
5.3 调试方法
开启调试日志
当前 shm_debug_log_func 为空,没有组件级日志开关。排障以 API 返回值、dump_state() 和系统 IPC 状态为准。
复现问题方法
- 确认
usr/lib64/libsomp.so可被宿主进程加载。 - 用
test_package中ShmLockTestBase在独立段上复现加解锁,排除 BMC 全局段干扰。 - 多进程问题:一方创建命名块,另一方
open_only+size_bytes == 0附着。 - 检查
/dev/shm/MDBUS_SHM、/dev/shm/MDBUS_SHM_LOCK_MANAGER以及ipcs -s中与 key0x12340000相关的信号量。 - 调用
get_stats()/dump_state()保存锁表快照。
libsomp 没有 D-Bus 对象,因此不存在 busctl 命令行调试方式。
5.4 错误对象解读
C API 返回 int(shm_lock_error_t)。C++ 锁 API 抛 shmlock::LockException,用 error_code() 对齐上表。单例与对象树部分抛 std::runtime_error,没有 D-Bus 结构化错误对象。
6. 常见问题解答
Q1:为什么链接或运行时报找不到 libsomp.so?
- 问题描述:动态链接失败,或
dlopen找不到库。 - 一句话答案:通过 Conan/组件包安装
usr/lib64/libsomp.so,并在链接时指定somp。 - 根因说明:CMake
install(TARGETS somp DESTINATION usr/lib64),Conancpp_info.libs = ["somp"]。 - 解决方案:确认产物路径、
RPATH/LD_LIBRARY_PATH和依赖(glib、libdbus、boost、liblogger、securec)。 - 规避方案:不要只拷贝单个
.so而忽略依赖与头文件。 - 适用版本:3.10.15。
Q2:get_instance() 抛出 open shm failed 怎么办?
- 问题描述:
ShmLockManager或shm::shared_memory单例构造失败。 - 一句话答案:检查
/dev/shm是否可写、段大小环境变量是否合法、旧段是否损坏。 - 根因说明:
open_or_create在权限不足、空间不足或段元数据损坏时会失败。 - 解决方案:查看
/dev/shm/MDBUS_SHM*;必要时在无业务进程占用时调用ShmLockManager::destroy()/shared_memory::destory()后重建。 - 规避方案:用环境变量把段大小限制在默认与上限之间。
- 适用版本:3.10.15。
Q3:加锁总是超时,如何区分死锁残留和正常竞争?
- 问题描述:
acquire_write_lock/shm_lock_acquire_*返回SHM_LOCK_TIMEOUT。 - 一句话答案:先看对端进程是否存活和是否在发心跳,再用
dump_state看持锁者。 - 根因说明:写锁互斥;进程崩溃可能留下 System V 层计数。健康检查按秒级(进程)到约 30 秒(陈旧锁)恢复。
- 解决方案:对仍存活的持有者检查业务逻辑;对死亡进程调用
health_check或等待监控线程。不要在异常退出路径再unregister_service。 - 规避方案:使用
LockHandleRAII,并为服务注册心跳。 - 适用版本:3.10.15。
Q4:客户端附着锁管理器失败,提示 meta not found?
- 问题描述:
LockManager(shm, block_name, 0)抛Attach failed: meta not found。 - 一句话答案:必须先有创建方用非 0 的
size_bytes初始化同名块。 - 根因说明:附着路径只查找
block_name + ".meta",不负责初始化。 - 解决方案:调整进程启动顺序,或改用
ShmLockManager::get_instance()走框架固定段名。 - 规避方案:测试中先构造 daemon
LockManager,再构造 attach-only 实例。 - 适用版本:3.10.15。
Q5:能否用 busctl 或 require() 调用 libsomp?
- 问题描述:按业务组件方式查找 D-Bus 服务或 Lua 模块失败。
- 一句话答案:不能。libsomp 只导出 C/C++ 共享库。
- 根因说明:无
luaopen_*、无model.json、无组件 D-Bus 服务名。 - 解决方案:在 C++/C 宿主中
#include对应头文件并链接somp。 - 规避方案:通过微组件框架间接使用对象树和锁,而不是把 libsomp 当成独立服务。
- 适用版本:3.10.15。