当前位置: 首页 > article >正文

OpenBMC sdbusplus接口实战:从服务注册到多接口管理

1. 初识sdbusplus你的BMC服务开发起点如果你正在为OpenBMC开发一个新的管理功能比如监控机箱温度、控制风扇转速或者实现一个自定义的硬件健康检查服务那么你迟早要和D-Bus打交道。在OpenBMC的世界里sdbusplus就是那个帮你搞定D-Bus通信的“瑞士军刀”。它不是一个新的D-Bus实现而是对底层sd-bus库的一个C封装层把那些繁琐的、容易出错的底层API调用包装成了更符合C开发者习惯的、面向对象的接口。我刚开始接触OpenBMC时看到D-Bus相关的代码也是一头雾水。什么总线、服务、对象路径、接口、方法、信号、属性……一堆概念扑面而来。但后来我发现其实可以把它想象成一个公司内部的电话系统。总线Bus就是公司的内部电话网络服务Service就像是公司的某个部门比如“硬件维护部”对象路径Object Path是这个部门里的具体工位比如“/硬件维护部/服务器A区”接口Interface就是这个工位能提供的服务合同规定了你能打电话来问什么方法、工位会主动广播什么消息信号、以及工位上有什么状态牌可以查看或设置属性。sdbusplus的作用就是帮你快速地在电话系统里注册一个部门、布置好工位、挂上服务合同和状态牌并且接听和处理打进来的电话。你不用自己去操心电话线怎么接、信号协议怎么定这些脏活累活它都帮你封装好了。我们这篇文章就是要手把手带你走完这个完整的流程从在总线上挂上你部门的牌子服务注册到在工位上摆出你能提供的服务清单添加接口与属性再到监听其他部门的重要广播并做出反应事件匹配最后扩展到管理一个拥有多个工位和复杂服务的大部门多接口管理。我会用大量我实际踩过的坑和调试过的代码来举例目标是让你看完就能动手把自己的BMC服务跑起来。2. 服务注册让你的模块在总线上“挂牌营业”万事开头难但服务注册这一步sdbusplus让它变得异常简单。我们先来看一个最最基础的、能让你的进程在D-Bus系统总线上“现身”的代码骨架。这个例子虽然不干任何实事但它是所有后续功能的基石。#include sdbusplus/asio/connection.hpp #include sdbusplus/asio/object_server.hpp #include boost/asio/io_context.hpp #include iostream static const std::string busServiceName xyz.openbmc_project.MyDemoService; int main() { // 1. 创建ASIO I/O上下文这是处理异步事件的核心 boost::asio::io_context io; // 2. 创建并获取一个到系统D-Bus的连接 auto conn std::make_sharedsdbusplus::asio::connection(io); // 3. 申请独占这个服务名相当于“挂牌” conn-request_name(busServiceName.c_str()); // 4. 启动事件循环开始等待和处理消息 io.run(); return 0; }就这么几行代码一个D-Bus服务进程的框架就搭好了。我们来拆解一下每一步背后发生了什么这对后续调试和理解至关重要。首先boost::asio::io_context io;这一行创建了一个异步I/O调度器。D-Bus通信本质上是异步的客户端发来一个方法调用请求服务端需要去处理处理完了再异步地回复。io_context就是管理所有这些异步任务比如等待网络消息、定时器、回调函数的“大脑”。你必须保证它在整个程序生命周期内都存在。第二行std::make_sharedsdbusplus::asio::connection(io)是重头戏。它做了两件关键事一是调用底层的sd_bus_default_system()或sd_bus_default_user()来连接到系统或用户D-Bus总线二是将这个D-Bus连接的文件描述符fd注册到刚才的io_context中并设置好读回调。这样当总线上有消息发给我们时ASIO就能自动唤醒并处理。你可以在sdbusplus/asio/connection.hpp的构造函数里看到它最后调用了read_immediate()这个函数就是用来开始监听总线消息的。第三行conn-request_name是真正的“注册”动作。它向D-Bus守护进程dbus-daemon发送一个请求“我要使用xyz.openbmc_project.MyDemoService这个名字请把它分配给我”。这里用的标志位SD_BUS_NAME_ALLOW_REPLACEMENT | SD_BUS_NAME_REPLACE_EXISTING很实用它允许其他服务稍后替换我们ALLOW_REPLACEMENT并且如果这个名字已经被占用了我们会尝试替换掉它REPLACE_EXISTING。这在开发调试时非常方便你不需要手动去杀掉旧进程。最后io.run()是启动事件循环。程序会阻塞在这里不断地处理D-Bus消息、定时器回调等直到你主动调用io.stop()或者所有异步操作都完成。编译运行这个程序后你可以在BMC的shell里用busctl list命令查看应该就能找到xyz.openbmc_project.MyDemoService这个服务名了。不过现在这个服务还是个“空壳”它没有任何对象、接口所以还无法提供任何功能。这就好比公司里挂了个部门牌子但里面既没工位也没人电话打进来也没人接。2.1 深入request_name名字争夺战与唯一标识你可能会有疑问如果两个程序同时请求同一个服务名谁会赢D-Bus有一套名字管理机制。request_name是一个同步调用它会一直等待直到名字分配结果确定。如果名字当前无人使用申请者直接获得。如果名字已被占用且原持有者没有设置SD_BUS_NAME_ALLOW_REPLACEMENT标志那么新的请求会失败。如果原持有者允许替换那么当原持有者释放名字比如进程退出或主动放弃时等待队列中的第一个请求者将获得该名字。每个成功连接到总线的连接还会自动获得一个“唯一名”Unique Name格式像:1.65这样。这个名称由总线守护进程分配在整个总线生命周期内是唯一且不可变的即使你的服务名被替换了这个唯一名也不会变。它主要用于消息的sender字段标识消息的真正来源。在调试时通过busctl monitor看到的sender后面跟的就是这个唯一名。理解这一点对后续分析事件匹配和消息流向非常重要。3. 添加接口与属性赋予服务真正的能力服务名注册好了接下来就要布置“工位”和定义“服务合同”了。在D-Bus中一个服务下可以有多个对象路径Object Path每个路径下可以有多个接口Interface每个接口里则包含了具体的方法Method、信号Signal和属性Property。我们接下来要做的就是在我们的服务下创建一个对象并为其添加一个功能接口。假设我们要创建一个简单的“计算器”服务它提供一个对象路径/xyz/openbmc_project/calculator在这个对象上提供一个接口xyz.openbmc_project.Calculator该接口有一个属性LastResult记录上次计算结果以及一个方法Add执行加法。#include sdbusplus/asio/connection.hpp #include sdbusplus/asio/object_server.hpp #include sdbusplus/asio/property.hpp #include boost/asio/io_context.hpp #include iostream static const std::string busServiceName xyz.openbmc_project.CalculatorService; static const std::string objectPath /xyz/openbmc_project/calculator; static const std::string interfaceName xyz.openbmc_project.Calculator; int main() { boost::asio::io_context io; auto conn std::make_sharedsdbusplus::asio::connection(io); conn-request_name(busServiceName.c_str()); // 关键步骤1创建对象服务器 auto objServer std::make_sharedsdbusplus::asio::object_server(conn); // 关键步骤2在指定路径上添加一个接口 auto calculatorIface objServer-add_interface(objectPath, interfaceName); // 关键步骤3为接口注册一个属性 int lastResult 0; calculatorIface-register_property(LastResult, lastResult, sdbusplus::asio::PropertyPermission::readWrite); // 关键步骤4为接口注册一个方法 calculatorIface-register_method(Add, [lastResult](int a, int b){ lastResult a b; std::cout Add called: a b lastResult std::endl; return lastResult; }); // 关键步骤5初始化接口将其真正发布到总线上 calculatorIface-initialize(); std::cout Calculator service is running... std::endl; io.run(); return 0; }现在我们来深入看看这几个关键步骤。sdbusplus::asio::object_server是一个辅助类它帮你管理多个对象和接口。它的构造函数默认会调用add_manager(/)。这个ObjectManager接口是D-Bus的一个标准接口用于动态管理对象。当你的服务添加或删除对象时它会自动发送InterfacesAdded和InterfacesRemoved信号方便其他客户端感知服务状态变化。在OpenBMC中很多上层管理工具都依赖这个机制来发现服务所以通常保留这个默认行为是好的。add_interface是核心。它创建了一个dbus_interface对象并将其保存在object_server内部的一个向量中。此时这个接口对象还只是一个“草稿”它记录了路径名、接口名以及后续你通过register_property和register_method添加的各种回调函数但总线和其他客户端还感知不到它的存在。register_property和register_method这两个函数用起来非常直观。它们将属性名/方法名、对应的C变量/函数绑定起来。注意注册属性时指定的PropertyPermission::readWrite这表示该属性可读可写。你还可以设为readOnly。对于属性sdbusplus会自动生成Get和Set方法。对于方法你注册的lambda函数或普通函数就是其实现。lambda捕获[lastResult]让我们可以修改外部的lastResult变量这样属性值就能随着方法调用而更新。最最重要的一步是initialize()。我见过不少新手掉进这个坑注册了一堆属性和方法但忘记调用initialize或者调用的时机不对。这个方法做了什么呢它会把之前“草稿”阶段收集的所有信息属性、方法、信号组装成D-Bus底层需要的sd_bus_vtable结构数组。这个vtable虚函数表是sd-bus用来描述一个接口完整能力的元数据。然后它调用sd_bus_add_object_vtable将这个vtable注册到总线上对应的对象路径。最后它还会发射一个InterfacesAdded信号通知总线上的其他监听者“嗨我这儿有个新接口上线了”切记initialize()必须在所有属性和方法都注册完毕之后调用并且一个接口对象只能调用一次。如果你先initialize再尝试register_method新注册的方法是无效的因为vtable已经固化并提交给总线了。正确的模式永远是创建接口 - 注册属性/方法/信号 - 调用initialize- 启动事件循环。4. 事件匹配与信号监听让服务变得“智能”一个只会被动响应请求的服务是不够的。在真实的BMC开发中你的服务经常需要感知系统其他部分的变化并做出反应。比如风扇控制服务需要监听温度传感器的读数变化电源管理服务需要监听开机按钮的状态变化。这就需要用到D-Bus的信号Signal和匹配规则Match。信号是一种单向的、广播式的通信机制。一个服务可以发射Emit信号任何其他服务或客户端都可以通过添加匹配规则来监听它而不需要知道发射者是谁。这很像发布-订阅模式。4.1 发射信号首先我们看看如何在自己的接口里定义和发射一个信号。接着上面的计算器例子我们希望在每次计算结果更新时发射一个信号通知所有监听者。// ... 前面的头文件和定义 ... int main() { boost::asio::io_context io; auto conn std::make_sharedsdbusplus::asio::connection(io); conn-request_name(busServiceName.c_str()); auto objServer std::make_sharedsdbusplus::asio::object_server(conn); auto calculatorIface objServer-add_interface(objectPath, interfaceName); int lastResult 0; calculatorIface-register_property(LastResult, lastResult, sdbusplus::asio::PropertyPermission::readWrite); // 注册一个信号名为“ResultChanged”携带一个整数参数新的结果值 calculatorIface-register_signalvoid(int)(ResultChanged); calculatorIface-register_method(Add, [calculatorIface, lastResult](int a, int b){ lastResult a b; std::cout Add called: a b lastResult std::endl; // 在方法执行后发射信号 calculatorIface-signal_property(LastResult); // 自动发射属性变化信号 // 或者发射我们自定义的信号 calculatorIface-emit_signal(ResultChanged).value(lastResult); return lastResult; }); calculatorIface-initialize(); io.run(); return 0; }这里有两个关键点。第一register_signalvoid(int)(ResultChanged)定义了一个信号它不返回值void但携带一个int类型的参数。第二在Add方法里我们演示了两种发射信号的方式signal_property是专门为属性设计的便捷函数它会自动发射D-Bus标准的Properties.PropertiesChanged信号这对于那些遵循属性接口规范的客户端如WebUI来说是必需的。而emit_signal则是发射我们自定义的信号后面用.value(...)来传递参数。4.2 监听信号添加匹配规则现在我们创建另一个服务或同一个服务内的另一个部分来监听这个信号。#include sdbusplus/asio/connection.hpp #include sdbusplus/bus/match.hpp #include boost/asio/io_context.hpp #include iostream static const std::string interfaceToWatch xyz.openbmc_project.Calculator; int main() { boost::asio::io_context io; auto conn std::make_sharedsdbusplus::asio::connection(io); // 定义匹配规则监听特定接口的属性变化信号 std::unique_ptrsdbusplus::bus::match_t matchPropChanged std::make_uniquesdbusplus::bus::match_t( *conn, sdbusplus::bus::match::rules::propertiesChanged(/xyz/openbmc_project/calculator, interfaceToWatch), [](sdbusplus::message_t msg){ std::string interfaceName; std::unordered_mapstd::string, sdbusplus::message::variantint changedProps; std::vectorstd::string invalidatedProps; try { msg.read(interfaceName, changedProps, invalidatedProps); std::cout [PropertyChanged] Interface: interfaceName std::endl; for (const auto [propName, variantValue] : changedProps) { std::cout Property propName changed to: ; if (propName LastResult) { std::cout std::getint(variantValue) std::endl; } } } catch (const std::exception e) { std::cerr Error reading PropertiesChanged signal: e.what() std::endl; } } ); // 定义匹配规则监听自定义信号 std::unique_ptrsdbusplus::bus::match_t matchCustomSignal std::make_uniquesdbusplus::bus::match_t( *conn, sdbusplus::bus::match::rules::signalMember(ResultChanged).path(/xyz/openbmc_project/calculator).interface(interfaceToWatch), [](sdbusplus::message_t msg){ int newResult; try { msg.read(newResult); std::cout [CustomSignal] ResultChanged signal received. New value: newResult std::endl; } catch (const std::exception e) { std::cerr Error reading ResultChanged signal: e.what() std::endl; } } ); std::cout Signal listener started. Waiting for events... std::endl; io.run(); return 0; }这段代码创建了两个匹配规则。sdbusplus::bus::match_t是核心类它的构造函数接受三个参数总线连接、匹配规则字符串、回调函数。当总线上有符合规则的消息时回调函数就会被触发。匹配规则字符串的构建使用了sdbusplus::bus::match::rules命名空间下的辅助函数这比手动拼接字符串更安全、更易读。propertiesChanged函数帮我们生成了监听标准Properties.PropertiesChanged信号的规则并限定了对象路径和接口名这样我们就不会收到无关的信号。signalMember函数则用于监听我们自定义的ResultChanged信号。在回调函数中我们通过msg.read(...)来解析信号携带的参数。这里必须注意参数的顺序和类型必须与信号发射时完全一致否则会抛出异常。好的实践是总是用try-catch包裹读取逻辑。一个重要的坑匹配规则是基于字符串的并且是由D-Bus守护进程 (dbus-daemon) 进行过滤的。这意味着如果你的规则写错了比如路径拼写错误守护进程不会报错只是你永远收不到信号。调试时可以先用busctl monitor命令裸跑看看实际发出的信号路径、接口、成员名到底是什么再对照着写规则。5. 多接口管理与复杂对象模型在实际的BMC模块中一个对象路径下往往不止一个接口而且对象之间可能存在层级关系。例如一个风扇托盘/xyz/openbmc_project/inventory/fantray0可能同时实现Item库存项、Asset资产信息、State状态等多个接口。又或者一个电源单元/xyz/openbmc_project/power/psu0下面可能有多个子对象分别代表不同的传感器/xyz/openbmc_project/power/psu0/voltage/xyz/openbmc_project/power/psu0/current。5.1 同一对象路径下的多个接口这其实很简单因为add_interface可以在同一个路径上被多次调用每次添加一个不同的接口。// 假设我们在同一个对象路径上提供计算器和日志两个接口 auto calcIface objServer-add_interface(objectPath, xyz.openbmc_project.Calculator); auto logIface objServer-add_interface(objectPath, xyz.openbmc_project.Logger); calcIface-register_property(LastResult, lastResult, sdbusplus::asio::PropertyPermission::readWrite); calcIface-register_method(Add, [](int a, int b){ return a b; }); logIface-register_method(LogMessage, [](const std::string msg){ std::cout LOG: msg std::endl; return true; }); // 分别初始化 calcIface-initialize(); logIface-initialize();这样客户端就可以通过同一个对象路径调用不同接口下的方法。例如通过Calculator接口调用Add通过Logger接口调用LogMessage。ObjectManager在发送InterfacesAdded信号时会包含这个对象路径上所有新增的接口名。5.2 父子对象路径与层次结构更常见的情况是树状结构。比如我们要模拟一个传感器集合根路径是/xyz/openbmc_project/sensors下面有温度传感器/xyz/openbmc_project/sensors/temperature/cpu0和风扇传感器/xyz/openbmc_project/sensors/fan/fan0。// 创建根对象可能只是一个容器不提供具体功能接口或者提供集合管理接口 auto sensorRootIface objServer-add_interface(/xyz/openbmc_project/sensors, xyz.openbmc_project.SensorAggregator); sensorRootIface-register_method(GetAllSensorPaths, [](){ // 返回所有子传感器路径的逻辑 std::vectorstd::string paths {/xyz/openbmc_project/sensors/temperature/cpu0, /xyz/openbmc_project/sensors/fan/fan0}; return paths; }); sensorRootIface-initialize(); // 创建CPU温度传感器子对象 auto cpuTempIface objServer-add_interface(/xyz/openbmc_project/sensors/temperature/cpu0, xyz.openbmc_project.Sensor.Value); double cpuTemp 45.0; cpuTempIface-register_property(Value, cpuTemp, sdbusplus::asio::PropertyPermission::readOnly); cpuTempIface-register_property(Unit, std::string(DegreesC), sdbusplus::asio::PropertyPermission::readOnly); cpuTempIface-initialize(); // 创建风扇转速传感器子对象 auto fanSpeedIface objServer-add_interface(/xyz/openbmc_project/sensors/fan/fan0, xyz.openbmc_project.Sensor.Value); int fanSpeed 3200; fanSpeedIface-register_property(Value, fanSpeed, sdbusplus::asio::PropertyPermission::readOnly); fanSpeedIface-register_property(Unit, std::string(RPM), sdbusplus::asio::PropertyPermission::readOnly); fanSpeedIface-initialize();这里有一个至关重要的细节ObjectManager的路径。还记得object_server构造函数默认会调用add_manager(/)吗这意味着它会在根路径/上注册ObjectManager接口。当你在/xyz/openbmc_project/sensors/temperature/cpu0上添加接口并初始化时object_server会向总线发送InterfacesAdded信号但信号的发送者路径sender path是ObjectManager所在的路径也就是/。如果你在另一个服务里添加匹配规则像这样sdbusplus::bus::match::rules::interfacesAdded(/xyz/openbmc_project/sensors/temperature/cpu0)你是收不到信号的因为信号是从/路径发出的而不是从子对象路径发出的。正确的匹配规则应该监听ObjectManager所在的路径并在回调函数中检查消息体里新增的接口路径是否是你关心的。// 监听任何对象上新增接口的信号来自根路径的ObjectManager std::unique_ptrsdbusplus::bus::match_t matchAdded std::make_uniquesdbusplus::bus::match_t( *conn, sdbusplus::bus::match::rules::interfacesAdded(/), // 注意路径是根路径 [](sdbusplus::message_t msg){ sdbusplus::message::object_path objPath; // 消息体里包含了新增接口的对象路径和接口名列表 // 需要在这里解析msg判断objPath是不是我们关心的 // ... 解析逻辑 ... } );这个坑我踩过好几次现象就是监听器死活收不到对象添加的通知。后来用busctl monitor仔细看原始信号才发现sender和path字段的奥秘。所以在调试D-Bus信号时一定要先用监控工具看原始数据不要盲目相信自己的代码逻辑。5.3 动态对象管理有时候对象路径是动态生成的比如根据探测到的硬件数量来创建。这时就需要在运行时动态地添加和删除对象。object_server提供了add_interface和remove_interface来管理接口但删除一个接口后记得也要调用emit_interfaces_removed来通知总线这样监听InterfacesRemoved信号的客户端才能知道。// 动态创建一个新的传感器对象 std::string dynamicPath /xyz/openbmc_project/sensors/voltage/psu std::to_string(id); auto dynamicIface objServer-add_interface(dynamicPath, xyz.openbmc_project.Sensor.Value); // ... 配置属性和方法 ... dynamicIface-initialize(); // 对象服务器会自动发送 InterfacesAdded 信号 // 当需要删除这个对象时 objServer-remove_interface(dynamicIface); // 从内部管理列表中移除 // 需要手动通知总线该对象上的接口已被移除 conn-emit_interfaces_removed(dynamicPath.c_str(), {xyz.openbmc_project.Sensor.Value});管理好对象的生命周期确保信号发送的时机正确是构建健壮的BMC服务的关键。尤其是在服务重启或硬件热插拔场景下清晰的对象添加/移除信号流能保证上层管理界面或依赖服务状态的一致性。

相关文章:

OpenBMC sdbusplus接口实战:从服务注册到多接口管理

1. 初识sdbusplus:你的BMC服务开发起点 如果你正在为OpenBMC开发一个新的管理功能,比如监控机箱温度、控制风扇转速,或者实现一个自定义的硬件健康检查服务,那么你迟早要和D-Bus打交道。在OpenBMC的世界里,sdbusplus就…...

【交互式分割】从零到一:基于Mask Guidance的迭代训练实战与性能优化

1. 为什么我们需要Mask Guidance?从交互式分割的痛点说起 想象一下,你正在用Photoshop抠图,面对一张毛发边缘复杂的小猫照片,你用魔棒工具点一下,结果要么选多了背景,要么漏掉了毛发尖。你不得不反复调整容…...

深入解析ACK、NACK与REX:网络通信中的重传机制与优化策略

1. 从“收到请回复”说起:网络世界的确认与重传 不知道你有没有玩过那种需要“收到请回复”的群聊。你发出一条重要通知,如果没人吭声,你心里就会打鼓:他们到底看没看到?这时候,你可能会所有人,…...

阿里云ECS实战:Ollama云端部署与跨网络本地调用全解析

1. 为什么要把Ollama放到云端?聊聊我的真实想法 你可能和我一样,最开始接触大模型都是在自己的电脑上跑。装个Ollama,拉个几B的小模型,玩玩对话,感觉挺酷。但很快,问题就来了:我的MacBook Pro风…...

Windows下利用Docker容器化技术实现多EasyConnect实例共存

1. 为什么我们需要在Windows上运行多个EasyConnect? 如果你和我一样,是个经常需要穿梭在不同项目、不同办公环境之间的打工人,那你肯定对EasyConnect这个软件又爱又恨。爱它,是因为它确实是我们连接公司内网、访问内部资源的“通行…...

从被动防御到主动免疫:IPDRR模型如何重塑企业网络安全韧性

1. 从“筑高墙”到“强免疫”:为什么你的企业安全需要一次思维升级 我见过太多企业,在安全建设上投入不菲,买最好的防火墙、最贵的入侵检测系统,安全策略文档堆起来能有一人高。但真出了事,比如一次勒索病毒攻击&#…...

HanLP 2.x 多任务模型实战:从安装到文本分析全流程

1. 为什么你需要HanLP 2.x的多任务模型? 如果你正在处理中文文本,比如想从一堆新闻里自动提取关键信息,或者给你的聊天机器人加上理解用户意图的能力,那你很可能需要一套好用的自然语言处理(NLP)工具。几年…...

LingJing(灵境)与外部虚拟机的网络穿透实战:从NAT困境到桥接畅通

1. 为什么你的反向Shell总是“失联”?从NAT困境说起 如果你和我一样,是个喜欢在本地搭建渗透测试环境的爱好者,那你肯定遇到过这个让人抓狂的场景:在LingJing(灵境)靶场里,靶机明明启动了&#…...

BEYOND REALITY Z-Image作品分享:自然光人像系列——晨光/正午/黄昏三种氛围呈现

BEYOND REALITY Z-Image作品分享:自然光人像系列——晨光/正午/黄昏三种氛围呈现 1. 引言:当光影遇见AI人像 你有没有想过,一张AI生成的人像照片,能有多真实? 不是那种一眼就能看出来的“AI感”,而是光影…...

告别“发光纸片人”:Substance 3D 与 Unity 2D URP 联动的次世代 2D 动态光照与法线手绘工作流

上周某日下午,一位担任核心技术美术的朋友,在微信上给我发了一段他们最新类银河恶魔城游戏的内部测试视频,并附带了一长串抓狂的语音。他们团队耗巨资请了顶级的二次元原画师,为游戏主角绘制了极其精美的立绘和 Spine 切片。可是&…...

人工智能混合编程实践:C++调用封装好的DLL进行PP-OCR字符识别

人工智能混合编程实践:C++调用封装好的DLL进行PP-OCR字符识别 前言 相关介绍 C++简介 ONNX简介 ONNX Runtime 简介 **核心特点** DLL 简介 **核心特点** **创建与使用** **应用场景** **优点与挑战** OCR字符识别简介 1. 核心工作原理 2. 技术演进 3. 主要应用场景 4. 当前面临…...

互联网大数据环境下 MySQL 迁移至国产底座的技术实践与路径观察

互联网大数据环境下 MySQL 迁移至国产底座的技术实践与路径观察 在当前互联网大数据应用持续深化的背景下,企业对关系型数据库的性能稳定性、安全合规性及运维可控性提出了更高要求。随着技术体系日趋成熟,金仓数据库(KingbaseES&#xff09…...

YOLOv8全网首发:CVPR2026 Transformer注意力 | BinaryAttention 1-bit注意力,推理提速100%,超越FlashAttention2

💡💡💡问题点:Transformer 已取得广泛而显著的成功,但其注意力模块的计算复杂性仍然是视觉任务的主要瓶颈。现有方法主要采用 8-bit 或 4-bit 量化来平衡效率与精度 💡💡💡措施:我们通过理论论证指出,注意力的二值化保留了基本的相似性关系,并提出了 BinaryAt…...

论文查重 / AI 率双杀攻略:Paperxie 四大降重方案实测,从 99.8% 到 14.9% 的通关密码

paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/aippthttps://www.paperxie.cn/weight?type1https://www.paperxie.cn/weight?type1 前言:毕业季新噩梦 ——AI 率超标,比查重更让人崩溃的学术红线 当毕业论文终于写完&#xff…...

Highcharts React v4.2.1 正式发布:更自然的React开发体验,更清晰的数据处理

Highcharts React v4.2.1 版本正式发布了!这次更新不仅带来了错误修复和新功能,更重要的是对组件文档进行了全面重写。这体现了我们持续的努力——让使用 Highcharts 的 React 开发者能够获得更加自然、顺畅的开发体验。如果你一直在等待尝试新的集成&am…...

OpenClaw 生成测试用例

在安装完 OpenClaw 后,很多同学只会用它聊天。今天十二就带大家通过安装 Skill,让 OpenClaw 真正变成一个能理解业务、自动写用例的测试专家。 1、查找:测试用例生成Skills 全网 Skill 太多,不知道哪个生成的用例最靠谱。这里使用十二之前安装好的 find-skills 查找测试用…...

计算机毕业设计springboot数字化心理健康服务系统的设计与实现 基于SpringBoot的“树洞“心理咨询服务平台的设计与实现 基于SpringBoot的在线心理支持与智慧辅导平台

计算机毕业设计springboot数字化心理健康服务系统的设计与实现a2huw9 (配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。近年来,心理健康问题呈直线上升趋势&#xff0c…...

DO-254通读--10.0 硬件设计生命周期数据

10.0 硬件设计生命周期数据 本节描述了在硬件设计生命周期中可能产生的硬件设计生命周期数据项,用于提供设计保证和符合审定要求的证据。审定机构作为设计保证证据所需的生命周期数据的范围、数量和详细程度将因多种因素而异。这些因素包括适用的航空系统审定机构要…...

蓝牙学习系列(一):从零认识蓝牙技术体系

目录 一、什么是蓝牙(Bluetooth) 二、蓝牙的发展版本 三、Classic Bluetooth 与 BLE 3.1 Classic Bluetooth(经典蓝牙) 3.2 BLE(Bluetooth Low Energy) 四、蓝牙设备角色 4.1 Classic Bluetooth 4.…...

成都双流整装工厂,哪家才是靠谱企业?

家人们,在成都双流找靠谱的整装工厂可真是一件让人头疼的事儿!市面上的装修公司五花八门,一不小心就容易踩坑。今天我就用自己的亲身经历,给大家好好唠唠哪家整装工厂才是真靠谱,那就是九天全屋整装!我家就…...

用python flask做了一个,批量查询,修改一堆excel文件的工具

每次要找在excel里面找文件时,都一个个打开,找半天。要修改时,一些类似的数据,又要一个个文件去修改,非常没有效率。这个工具作用就是批量查询与修改。B/S架构,读出来的excel常驻内存,注意&…...

基于LQR控制的主动悬架模型:构建平顺性仿真,涵盖多种车辆模型与源文件集

【被动/LQR主动悬架模型】采用LQR控制的主动悬架模型,选取车身加速度、悬架动挠度等参数构造线性二次型最优控制目标函数。 输入为B级随机路面激励,输出为车身垂向加速度、俯仰角加速度、悬架动挠度等平顺性评价指标,可做汽车平顺性仿真。 二…...

jQuery如何扩展百度WebUploader组件支持教育行业PPT课件的跨平台分片上传?

前端老兵的20G文件夹上传血泪史(附部分代码) 各位前端同仁们好,我是老王,一个在福建靠写代码混口饭吃的"前端民工"。最近接了个奇葩项目,客户要求用原生JS实现20G文件夹上传下载,还要兼容IE9&am…...

Android 15 深色模式:第三方应用强制适配深色模式的开关在哪里?

很多朋友在打开手机的深色模式(也叫暗黑模式)后,可能会发现一个问题:手机自己的界面和自带应用都变黑了,但很多常用的第三方软件,比如微信、淘宝或者一些银行APP,却还是亮晃晃的白色背景。这不仅…...

双向RRT算法的三维路径规划MATLAB代码:包含路径平滑处理

bi-rrt算法三维MATLAB代码 双向rrt算法的三维路径规划 加入路径的平滑处理直接打开MATLAB开整三维空间路径规划。双向RRT(Bi-RRT)这玩意儿比传统RRT快不是一点半点,核心思路就是两头长树往中间怼。咱们先看节点数据结构怎么设计: …...

“扫频法阻抗扫描验证及复现双馈风机MMC电压源型VSG阻抗建模与程序注释

扫频法 阻抗扫描 阻抗建模验证 正负序阻抗 逆变器 虚拟同步控制 VSG 复现 双馈风机MMC 电压源型VSG阻抗建模及阻抗扫描验证 虚拟同步发电机序阻抗建模 风机多端MMC 可设置扫描范围、扫描点数,附送讲解 程序附带注释,每一行都能看懂 包括vsg仿真模型&…...

【异常】OpenClaw 调用 API 限速报错 API rate limit reached. Please try again later.

一、报错内容 在使用 OpenClaw 进行接口调用时,系统返回以下报错信息: API rate limit reached. Please try again later. 同时提示当前订阅套餐已达到调用限额,需等待周期刷新或升级套餐,建议5小时后重新进行交互操作。 二、报错说明 1. 报错核心含义 本次报错的核心是…...

声源定位实战:从仿真到嵌入式落地

2022声源定位相关资料及代码 内附声源定位算法基本原理及matlab仿真原理及实现方法; stm32f4实现源码(2022电赛) 3米处水平横向精度0.013m(可优化更低)。 视频5s,无快进,mcu为stm32f429zit6。 …...

AI人脸隐私卫士实测:多人合照自动打码,效果惊艳

AI人脸隐私卫士实测:多人合照自动打码,效果惊艳 1. 引言:当合照遇上隐私,AI如何成为你的守护者? 你有没有过这样的经历?公司团建拍了张大合照,想发朋友圈分享喜悦,却要花上十几分钟…...

Vue 3 源码阅读笔记:ref.ts

文章目录一、文件概览二、核心数据结构1. Ref 接口定义三、核心函数实现1. isRef - 类型守卫2. r[ReactiveFlags.IS_REF]详解一、 r[ReactiveFlags.IS_REF] 是什么意思?二、这个标记是怎么来的?三、为什么需要这个标记?四、完整的标记系统五、…...