【android bluetooth 协议分析 02】【bluetooth hal 层详解 3】【高通蓝牙hal主要流程介绍-上】
1. 背景
本节主要讨论 高通 蓝牙 hal 中,的一些流程。 看看你是否都清楚如下问题:
- 高通芯片电如何控制?
- 串口是在哪里控制的?
- 固件如何下载?
- 初始化流程是怎么样的?
如果你已经对上述讨论的问题,已经很清楚了,那你无需阅读该文章,请自行忽略。当然,也可以给笨叔挑挑错。 欢迎评论,一起探讨,毕竟都是笨叔自己的理解,难免有点出入,我也想进步!!!
在阅读这篇文章之前, 可以先阅读一下两篇文章。
【android bluetooth 框架分析 02】【Module详解 3】【HciHal 模块介绍】
【android bluetooth 协议分析 02】【bluetooth hal 层详解 2】【蓝牙hal层hidl_1.0和hidl_1.1层接口的区别是什么】
我们这里按照 hidl 1.0 接口讲解。1.1 类似。
2. bt server 触发流程
我们先来回顾一下,bt.server 是如何触发 hal 之间的通信的。
在 HciHal 模块的 Start 函数中,我们会去向 SM 获取 hal_1.0 服务, 并且调用 hal 的 initialize 接口
// system/gd/hal/hci_hal_android_hidl.ccvoid Start() override {std::string instance = GetHciInstance();if (bt_hci_1_1_ != nullptr) {} else {// 1. 向 SM 获取 IBluetoothHci_1_0 服务。bt_hci_ = IBluetoothHci_1_0::getService(instance);}LOG_INFO("GetService Done.");callbacks_ = new InternalHciCallbacks(btaa_logger_, btsnoop_logger_); // hal进程,会调用我们的 接口if (bt_hci_1_1_ != nullptr) {} else {// 2. 调用 hal 接口, 触发 hal 初始化,将回调接口告知 hal进程。bt_hci_->initialize(callbacks_);}// 3. 等待 hal initialize 执行完成。callbacks_->GetInitPromise()->get_future().wait();}
可以将上面 HciHal 模块的 Start 函数 总结为三步:
- 向 SM 找 bt hal 1.0 服务
- 调用 hal initialize
- 等待 hal initialize 完成
我们本节讨论的 蓝牙芯片上电,打开蓝牙串口, 固件下载。 都是在 这里调用 hal initialize 到 hal initialize 完成直接 进行的。
1. 向 SM 找 bt hal 1.0 服务
std::string GetHciInstance() {char buf[64];int hci_adapter = InitFlags::GetAdapterIndex();// 0 -> "default" (default bluetooth adapter)// 1 -> "hci1" (new bluetooth adapter)if (hci_adapter > 0) {snprintf(buf, sizeof(buf), "hci%d", hci_adapter);} else {snprintf(buf, sizeof(buf), "default");}return std::string(buf);
}
- 如果是 bt0 就找寻 android.hardware.bluetooth@1.0::IBluetoothHci/default 服务, 如果是 bt1 就找寻 android.hardware.bluetooth@1.0::IBluetoothHci/hci1 服务
bt_hci_ = IBluetoothHci_1_0::getService(instance);
- 通过 IBluetoothHci_1_0::getService 就能获取到 android.hardware.bluetooth@1.0::IBluetoothHci/default 服务。
2. 调用 hal initialize
callbacks_ = new InternalHciCallbacks(btaa_logger_, btsnoop_logger_); // hal进程,会调用我们的 接口bt_hci_->initialize(callbacks_);
class InternalHciCallbacks : public IBluetoothHciCallbacks {};
- InternalHciCallbacks 继承了 IBluetoothHciCallbacks。 hal 侧的回调就是调用到这里。
- 通过 调用 hal 的 initialize 方法将 bt.server 的回调函数注册进 hal.
调用 hal 的 initialize 方法 , 将完成很多工作。 完成的工作将在 hal 侧详细介绍。我们这里先梳理流程。
当 hal 侧处理完 initialize 后, 会通知 bt.server.继续执行。
3. 等待 hal initialize 完成
callbacks_->GetInitPromise()->get_future().wait();std::promise<void>* GetInitPromise() {return init_promise_;}
- 通过 init_promise_ 实现等待。
我们看一下, hal 是如何通知我们的。
class InternalHciCallbacks : public IBluetoothHciCallbacks {Return<void> initializationComplete(HidlStatus status) {common::StopWatch stop_watch(__func__);//ASSERT(status == HidlStatus::SUCCESS);if (status != HidlStatus::SUCCESS) {ALOGE("HidlStatus is not SUCCESS");exit(EXIT_FAILURE);}init_promise_->set_value();return Void();}}
在第2步,我们将 InternalHciCallbacks 注册进了 hal 进程。 hal 在处理完成 initialize 后,会通过 initializationComplete 告知 .bt.server.
此时,这里的等待就会退出, 继续执行其他操作。
3. hal 侧逻辑
1. hal服务启动和注册
android 系统中 会在对应的 rc 文件中启动当前的 蓝牙hal
service vendor.bluetooth-1-0-xxxx /vendor/bin/hw/android.hardware.bluetooth@1.0-service-xxxxinterface android.hardware.bluetooth@1.0::IBluetoothHci defaultclass haluser bluetoothgroup bluetooth system wakelock oem_2901 net_raw oem_2912capabilities BLOCK_SUSPEND NET_ADMIN
// hidl_hci/1.0/default/service.cppint main() {ALOGI("BT-Transport driver main");(void)umask(S_IWGRP | S_IWOTH);struct sched_param rt_params;rt_params.sched_priority = BT_TX_RT_PRIORITY;android::hardware::ProcessState::initWithMmapSize((size_t)(256144));configureRpcThreadpool(1, true /*callerWillJoin*/);ALOGI("isVendorEnhancedFramework: %d", isVendorEnhancedFramework);ALOGI("Registering BT Service");status_t status;status = registerBluetoothHci(); // 注册蓝牙服务if (status != OK)ALOGI("Error while registering BT service: %d", status);ALOGI("BTTPI: Main, joinRpcThreadpool for HIDL");joinRpcThreadpool();return status;
}static status_t registerBluetoothHci()
{status_t status;
#ifdef DUAL_BTconst char *bt_hci_instance = "hci1";
#elseconst char *bt_hci_instance = "default";
#endif#ifdef LAZY_SERVICEstatus = registerLazyPassthroughServiceImplementation<IBluetoothHci>(bt_hci_instance);
#elsestatus = registerPassthroughServiceImplementation<IBluetoothHci>(bt_hci_instance);
#endifreturn status;
}
我们当前的 hal 支持双蓝牙, 从 DUAL_BT 可以看出来,我们通过 编译来控制 编译两个不同的 bin. 用于 bt0 和 bt1.
在 registerBluetoothHci 函数中 当 registerPassthroughServiceImplementation<>() 被调用时:
- 框架在 libhidltransport.so 内部调用一个动态查找函数
- 它尝试在你的 HAL .so 中查找函数:
extern “C” IBluetoothHci* HIDL_FETCH_IBluetoothHci(const char* name);
3. 如果找到了,就调用它并返回 HAL 实例
// hidl_hci/1.0/default/bluetooth_hci.cppIBluetoothHci* HIDL_FETCH_IBluetoothHci(const char* name)
{ALOGI("%s ,new BluetoothHci() name:%s slot:%d", __func__, name, Util::getHwSlot());Util::init(name);return new BluetoothHci(Util::getHwSlot());
}
BluetoothHci 继承 IBluetoothHci 接口
class BluetoothHci : public IBluetoothHci {public:BluetoothHci();BluetoothHci(int slot);~BluetoothHci();Return<void> initialize(const ::android::sp<IBluetoothHciCallbacks>& cb) override;Return<void> sendHciCommand(const hidl_vec<uint8_t>& packet) override;Return<void> sendAclData(const hidl_vec<uint8_t>& data) override;Return<void> sendScoData(const hidl_vec<uint8_t>& data) override;Return<void> close() override;Return<void> debug(const hidl_handle& handle,const hidl_vec<hidl_string>& options) override;private:void sendDataToController(HciPacketType type, const hidl_vec<uint8_t>& data);void startDetectBluetooth(int slot);void stopDetectBluetooth();::android::sp<IBluetoothHciCallbacks> event_cb_;::android::sp<BluetoothDeathRecipient> deathRecipient;int hw_slot_;std::thread detect_bt_thread_;
};extern "C" IBluetoothHci* HIDL_FETCH_IBluetoothHci(const char* name);
// bt0 调用01-13 02:03:20.629763 714 714 I vendor.RunningAndroid.bluetooth@1.0-bluetooth_hci: HIDL_FETCH_IBluetoothHci ,new BluetoothHci() name:default slot:0// bt1 调用
01-13 02:03:20.630275 715 715 I vendor.RunningAndroid.bluetooth@1.0-bluetooth_hci: HIDL_FETCH_IBluetoothHci ,new BluetoothHci() name:hci1 slot:0
// 此时 bt0 的服务就已经注册成功了。 bt.server 就可以 get 当前服务了。
01-13 02:03:20.641529 714 714 I HidlServiceManagement: Registered android.hardware.bluetooth@1.0::IBluetoothHci/default
2. initialize 调用
当 bt.server 调用 bt_hci_->initialize(callbacks_);
将调用到如下代码
// hidl_hci/1.0/default/bluetooth_hci.cppReturn<void> BluetoothHci::initialize(const ::android::sp<IBluetoothHciCallbacks>& cb)
{bool rc = false;ALOGW("BluetoothHci::initialize(), slot%d", hw_slot_);if (cb == nullptr) {ALOGE("%s: Received NULL callback from BT client", __func__);return Void();}::android::sp<IBluetoothHciCallbacks> event_cb_tmp;event_cb_tmp = cb; // 将我们 bt.server 传递进来的 回调保存在 event_cb_tmp 中了。// 1. 调用 DataHandler::Init 方法rc = DataHandler::Init( TYPE_BT,[this, event_cb_tmp](bool status) {if (event_cb_tmp != nullptr) {ALOGI("%s: Set callbacks received from BT client inorder ""to provide status and data through them", __func__);event_cb_ = event_cb_tmp;}if (event_cb_ != nullptr) {auto hidl_client_status = event_cb_->initializationComplete(status ? Status::SUCCESS : Status::INITIALIZATION_ERROR);if(!hidl_client_status.isOk()) {ALOGE("Client dead, callback initializationComplete failed");}}},[this, event_cb_tmp](HciPacketType type, const hidl_vec<uint8_t> *packet) {DataHandler *data_handler = DataHandler::Get();if (event_cb_tmp == nullptr) {ALOGE("BluetoothHci: event_cb_tmp is null");if (data_handler)data_handler->SetClientStatus(false, TYPE_BT);return;}/* Skip calling client callback when client is dead */if(data_handler && (data_handler->GetClientStatus(TYPE_BT) == false)) {ALOGI("%s: Skip calling client callback when client is dead", __func__);return;}Logger::Get()->UpdateRxTimeStamp();switch (type) {case HCI_PACKET_TYPE_EVENT:{
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_PRE_STACK_EVT_CALL_BACK);
#endifauto hidl_client_status = event_cb_tmp->hciEventReceived(*packet);if(!hidl_client_status.isOk()) {ALOGE("Client dead, callback hciEventReceived failed");if (data_handler)data_handler->SetClientStatus(false, TYPE_BT);}
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_POST_STACK_EVT_CALL_BACK);
#endif}break;case HCI_PACKET_TYPE_ACL_DATA:{
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_PRE_STACK_ACL_CALL_BACK);
#endifauto hidl_client_status = event_cb_tmp->aclDataReceived(*packet);if(!hidl_client_status.isOk()) {ALOGE("Client dead, callback aclDataReceived failed");if (data_handler)data_handler->SetClientStatus(false, TYPE_BT);}
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_POST_STACK_ACL_CALL_BACK);
#endif}break;default:ALOGE("%s Unexpected event type %d", __func__, type);break;}});if (!rc && (cb != nullptr)) {...} else if (rc && (cb != nullptr)) {ALOGI("%s: linking to deathRecipient", __func__);cb->linkToDeath(deathRecipient, 0);}return Void();
}
// BluetoothHci::initialize 函数调用开始
01-13 02:03:26.451268 714 714 W vendor.RunningAndroid.bluetooth@1.0-bluetooth_hci: BluetoothHci::initialize(), slot001-13 02:03:26.451326 714 714 W vendor.RunningAndroid.bluetooth@1.0-data_handler: DataHandler:: Init()
01-13 02:03:26.451454 714 714 I vendor.RunningAndroid.bluetooth@1.0-data_handler: data_service_setup_sighandler: Entry
01-13 02:03:26.451487 714 714 D vendor.RunningAndroid.bluetooth@1.0-data_handler: isProtocolAdded:
01-13 02:03:26.451498 714 714 I vendor.RunningAndroid.bluetooth@1.0-data_handler: isProtocolAdded: status:0
01-13 02:03:26.451507 714 714 I vendor.RunningAndroid.bluetooth@1.0-data_handler: Open init_status 0
01-13 02:03:26.451897 714 714 D vendor.RunningAndroid.bluetooth@1.0-wake_lock: Init wakelock is initiated // BluetoothHci::initialize 函数调用退出
01-13 02:03:26.452016 714 714 I vendor.RunningAndroid.bluetooth@1.0-bluetooth_hci: initialize: linking to deathRecipient
在 BluetoothHci::initialize 中最主要就是调用 DataHandler::Init
1. DataHandler::Init
// hidl_hci/1.0/default/data_handler.cpp
bool DataHandler::Init(ProtocolType type, InitializeCallback init_cb,DataReadCallback data_read_cb)
{// lock required incase of multiple binder threadsALOGW("DataHandler:: Init()");std::unique_lock<std::mutex> guard(init_mutex_);Get();// 将上述两个回调 传入 他的 Open 函数return data_handler->Open(type, init_cb, data_read_cb);
}
DataHandler::Init 有两个回调 :
-
InitializeCallback init_cb
-
DataReadCallback data_read_cb
1. InitializeCallback init_cb
其中 InitializeCallback init_cb 传入的内容是:
[this, event_cb_tmp](bool status) {if (event_cb_tmp != nullptr) {ALOGI("%s: Set callbacks received from BT client inorder ""to provide status and data through them", __func__);event_cb_ = event_cb_tmp;}if (event_cb_ != nullptr) {auto hidl_client_status = event_cb_->initializationComplete(status ? Status::SUCCESS : Status::INITIALIZATION_ERROR);if(!hidl_client_status.isOk()) {ALOGE("Client dead, callback initializationComplete failed");}}}
2. DataReadCallback data_read_cb
DataReadCallback data_read_cb 传入的内容是:
[this, event_cb_tmp](HciPacketType type, const hidl_vec<uint8_t> *packet) {DataHandler *data_handler = DataHandler::Get();if (event_cb_tmp == nullptr) {ALOGE("BluetoothHci: event_cb_tmp is null");if (data_handler)data_handler->SetClientStatus(false, TYPE_BT);return;}/* Skip calling client callback when client is dead */if(data_handler && (data_handler->GetClientStatus(TYPE_BT) == false)) {ALOGI("%s: Skip calling client callback when client is dead", __func__);return;}Logger::Get()->UpdateRxTimeStamp();switch (type) {case HCI_PACKET_TYPE_EVENT:{
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_PRE_STACK_EVT_CALL_BACK);
#endifauto hidl_client_status = event_cb_tmp->hciEventReceived(*packet);if(!hidl_client_status.isOk()) {ALOGE("Client dead, callback hciEventReceived failed");if (data_handler)data_handler->SetClientStatus(false, TYPE_BT);}
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_POST_STACK_EVT_CALL_BACK);
#endif}break;case HCI_PACKET_TYPE_ACL_DATA:{
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_PRE_STACK_ACL_CALL_BACK);
#endifauto hidl_client_status = event_cb_tmp->aclDataReceived(*packet);if(!hidl_client_status.isOk()) {ALOGE("Client dead, callback aclDataReceived failed");if (data_handler)data_handler->SetClientStatus(false, TYPE_BT);}
#ifdef DUMP_RINGBUF_LOGLogger::Get()->UpdateRxEventTag(RX_POST_STACK_ACL_CALL_BACK);
#endif}break;default:ALOGE("%s Unexpected event type %d", __func__, type);break;}}
将上述两个回调 传入 他的 Open 函数
2. DataHandler::Open
bool DataHandler::Open(ProtocolType type, InitializeCallback init_cb,DataReadCallback data_read_cb)
{char dst_buff[MAX_BUFF_SIZE];char init_buff[MAX_BUFF_SIZE];struct timeval tv;std::map<ProtocolType, ProtocolCallbacksType *>::iterator it;std::unique_lock<std::mutex> guard(internal_mutex_);ALOGI("Open init_status %d \n", init_status_);// update the pending Init cb and other callbacksit = protocol_info_.find(type);if (it == protocol_info_.end()) {ProtocolCallbacksType *cb_data = new (ProtocolCallbacksType);cb_data->type = type;cb_data->is_pending_init_cb = true;cb_data->init_cb = init_cb; // 将我们回调保存在 cb_data 中cb_data->data_read_cb = data_read_cb;protocol_info_[type] = cb_data;}switch (init_status_) { // 这里默认的 状态是 0: INIT_STATUS_IDLE...case INIT_STATUS_IDLE:init_status_ = INIT_STATUS_INITIALIZING;break;}// 在这里 创建了一个 线程init_thread_ = std::thread(...);return true;
}
也就是说 bt.server 调用 initialize 函数后,hal 侧启动了一个线程 专门干这个事情。 initialize 函数就顺利返回了。
主要 initialize 的工作全部在 该线程中处理了。
3. 转门的线程处理 initialize 工作
线程处理函数如下:
[this, type]() {// 1. 启动定时器StartInitTimer();if (!IsSocAlwaysOnEnabled()) {soc_need_reload_patch = true;}ALOGI("%s: soc_need_reload_patch = %d", __func__, soc_need_reload_patch); // 这里是1 if (soc_type_ == BT_SOC_SMD) {} else {// 2. 创建 UartController 对象controller_ = static_cast<Controller *> (new UartController(soc_type_));}if (controller_) {int retry_count = 0;// 进入循环 初始化。如果初始化失败,接着尝试while (retry_count < INIT_MAX_RETRY_TIMES) {// 3. 调用 controller_->Init 函数status = controller_->Init([this](ProtocolType ptype, HciPacketType type,const hidl_vec<uint8_t> *hidl_data) {OnPacketReady(ptype, type, hidl_data);});if (status)break;++retry_count;}}...// 4. 初始化成功后 停止定时器StopInitTimer();std::unique_lock<std::mutex> guard(internal_mutex_);if (status) {/* Stop moving further if timeout detected */{guard.unlock();std::unique_lock<std::mutex> lock(DataHandler::init_timer_mutex_);if (GetInitTimerState() == TIMER_OVERFLOW) {ALOGW("Initialization timeout detected cleanup is in process");// Init thread exited.is_init_thread_killed = true;return;}guard.lock();init_status_ = INIT_STATUS_SUCCESS;ALOGD("Firmware download succeded.");}} else {...}std::map<ProtocolType, ProtocolCallbacksType *>::iterator it;for (auto& it: protocol_info_) {ProtocolCallbacksType *cb_data = (ProtocolCallbacksType*)it.second;cb_data->is_pending_init_cb = false;gettimeofday(&tv, NULL);snprintf(dst_buff, sizeof(dst_buff), "Init callback status = %d", status);BtState::Get()->AddLogTag(cb_status_buf, tv, dst_buff);BtState::Get()->SetTsStatusOfCbSent(cb_status_buf);// 5. 调用回调cb_data->init_cb(status); }// clear the list if the controller open call failsif (!status) {...}guard.unlock();// BT ON successfulproperty_set("persist.vendor.service.bdroid.system_delay_crash_count", "0");// Init thread exited.is_init_thread_killed = true;ALOGD("%s: init thread exited now", __func__);}
这个线程 主要做如下几件事情:
- 启动定时器
- 创建 UartController 对象
- 调用 controller_->Init 函数
- 初始化成功后 停止定时器
- 调用回调 init_cb
1. 启动定时器
启动定时器的目的是, 为了 防止 Controller 交互时, 无响应。 这里不是重点。暂时忽略。
2.创建 UartController 对象
// hidl_hci/1.0/default/uart_controller.cppUartController::UartController(BluetoothSocType soc_type): soc_crashed(false), soc_type_(soc_type),hci_packetizer_([this](hidl_vec<uint8_t> *data) { OnPacketReady(data); })
{
...
}
UartController 没有啥可以讲述的,一堆变量。暂时忽略
3. 调用 controller_->Init 函数
bool UartController::Init(PacketReadCallback pkt_read_cb)
{power_manager_.Init(soc_type_);// 1. 给芯片 上电if (soc_need_reload_patch) {// power off the chip firstpower_manager_.SetPower(false);// power on the chip using power managerpower_manager_.SetPower(true);}// 2. 初始化 HciTransporthci_transport_ = static_cast<HciTransport*> (uart_transport);ret = uart_transport->Init(soc_type_, soc_need_reload_patch);// 3. 创建 固件 补丁管理器patch_dl_manager = new (std::nothrow)PatchDLManager(soc_type_, uart_transport, &power_manager_);uart_transport->ClockOperation(USERIAL_OP_CLK_ON);//Download the NVM/RAM patchif (soc_need_reload_patch) {logger_->PropertyGet("vendor.wc_transport.skip_patch_dload", skip_patch_download, "false");if (strcmp(skip_patch_download, "true") != 0) {// 4. 开始打补丁, 下载固件if (patch_dl_manager->PerformChipInit() < 0) {}temp_add_on_features = patch_dl_manager->GetAddOnFeatureList();} else {}}// 获取 controller 的芯片版本, 做记录使用chipset_ver_ = patch_dl_manager->GetChipVersion();init_done_ = true;ALOGD("Init succeded");return init_done_;
}
由于篇幅原因,这里将于 下一篇,中讲解 这部分内容
4. 初始化成功后 停止定时器
这里不是重点。暂时忽略。
5. 调用回调 init_cb
当芯片上完电, 已经下载完固件后, 将调用 回调。
cb_data->init_cb(status);
这里将回调到 3.2.1.1 小节中的回调。
[this, event_cb_tmp](bool status) {if (event_cb_tmp != nullptr) {ALOGI("%s: Set callbacks received from BT client inorder ""to provide status and data through them", __func__);event_cb_ = event_cb_tmp;}if (event_cb_ != nullptr) {auto hidl_client_status = event_cb_->initializationComplete(status ? Status::SUCCESS : Status::INITIALIZATION_ERROR);if(!hidl_client_status.isOk()) {ALOGE("Client dead, callback initializationComplete failed");}}}
- 最终会回调到 bt.server 中的 initializationComplete 函数。告知 bt.server 初始化成功。
相关文章:
【android bluetooth 协议分析 02】【bluetooth hal 层详解 3】【高通蓝牙hal主要流程介绍-上】
1. 背景 本节主要讨论 高通 蓝牙 hal 中,的一些流程。 看看你是否都清楚如下问题: 高通芯片电如何控制?串口是在哪里控制的?固件如何下载?初始化流程是怎么样的? 如果你已经对上述讨论的问题,…...
Linux | tmux | 无法复制粘贴
问题:在Linux中使用tmux时,总是没法使用复制粘贴功能; 解决: 如果希望直接用鼠标选择并复制(类似普通终端),可以: 在 ~/.tmux.conf 中添加:sh set -g mouse on;重新加载 tmux 配置…...
如何通过小贝加速实现精准网络故障排查
在日常使用电脑的过程中,我们常常需要监控系统运行状态、优化性能或排查网络问题。最近发现一款名为小贝加速的桌面工具,在此分享关于小贝加速如何实现网络监控。 系统优化 该工具提供了简洁明了的系统优化功能。通过扫描可以清理系统冗余文件、释放内存…...
Nginx 网站服务
目录 一:基于授权的访问控制 1:基于授权的访问控制简介 2:基于授权的访问控制步骤 二:基于客户端的访问控制 1:基于客户端的访问控制简介 2:基于客户端的访问控制步骤 三:Nginx 虚拟主机…...
Python 字典的用法和技巧
字典的创建与初始化 Python 字典是一种可变容器模型,可存储任意类型对象。字典的每个键值对用冒号分隔,键值对之间用逗号分隔,整个字典包括在花括号中。 # 创建一个空字典 empty_dict {}# 创建一个包含键值对的字典 my_dict {name: Alice…...
电力设备制造企业数字化转型路径研究:从生产优化到生态重构
电力设备制造业作为支撑能源革命的核心领域,其数字化转型不仅关乎企业降本增效,更是实现“双碳”目标与新型电力系统建设的关键抓手。本文基于行业标杆案例与实践经验,系统梳理电力设备企业数字化转型的五大核心路径。 一、生产流程智能化&a…...
初识GPU加速:如何利用GPU提升AI训练效率
随着人工智能(AI)和深度学习技术的快速发展,训练深度神经网络(DNN)已经变得越来越复杂和计算密集。传统的CPU已经无法满足大量计算任务的需求,因此,GPU(图形处理单元)成为了训练深度学习模型时的必备工具。本篇文章将介绍如何利用GPU加速AI训练效率,以及在使用GPU时应…...
深入解析异步编程:Java NIO、Python `async/await` 与 C# `async/await` 的对比
在现代编程中,异步编程已成为处理 I/O 密集型任务(如网络请求、文件操作等)的高效方式。不同的编程语言提供了各自的异步编程模型,以提高程序的性能和资源利用率。本文将深入解析 Java 的 NIO、Python 的 async/await 和 C# 的 as…...
阿里云数据盘级别
数据盘PL0、PL1、PL2和PL3的区别体现在性能、容量范围以及应用场景等方面。具体分析如下: 性能 PL0:单盘最大IOPS为10,000,最大吞吐量为180MB/s。适用于中小型MySQL和SQLServer等数据库场景,中小规模ELK日志集群,SAP和…...
使用 Spring AI Alibaba 集成阿里云百炼大模型应用
随着人工智能技术的飞速发展,大模型在各个领域的应用越来越广泛。阿里云百炼大模型提供了强大的语言理解和生成能力,但如何将其高效地集成到实际应用中,一直是开发者关注的焦点。本文将详细介绍如何使用 Spring AI Alibaba 集成阿里云百炼大模…...
阿里云合集(不定期更新)
一、阿里云申请免费域名证书流程:https://blog.csdn.net/humors221/article/details/143266059 二、阿里云发送国内短信怎样编程:https://blog.csdn.net/humors221/article/details/139544193 三、阿里云ECS服务器磁盘空间不足的几个文件:h…...
零基础设计模式——创建型模式 - 抽象工厂模式
第二部分:创建型模式 - 抽象工厂模式 (Abstract Factory Pattern) 我们已经学习了单例模式(保证唯一实例)和工厂方法模式(延迟创建到子类)。现在,我们来探讨创建型模式中更为复杂和强大的一个——抽象工厂…...
ConcurrentHashMap导致的死锁事故
事故现象 某线上服务共100台容器,第二天上午流量高峰期部分容器(约10%)cpu飙升,升至100%。 部分堆栈信息 堆栈信息如下如所示: 当前线程堆栈显示在JsonContext.get方法中调用computeIfAbsent,其Lambda表…...
Python高效网络爬虫开发指南
Python 网络爬虫入门与实战 一、引言 随着互联网数据的爆炸性增长,获取和分析这些数据变得越来越重要。网络爬虫作为数据采集的重要工具,在这其中扮演了不可或缺的角色。 二、环境搭建 首先我们需要安装Python环境以及一些必要的库: req…...
关于C++使用位运算交换变量值的分析
1、使用临时变量交换 交换变量的值,最常见的方法就是用临时变量。 void swap1(int& a, int& b){int c a;a b;b c; }清晰明了。 2、位运算版 对于整数类型,相信很多人都见过下面方法,可以使用位运算,从而不借用临时…...
06 接口自动化-框架封装思想建立之httprunner框架(下)
文章目录 一、httprunner如何实现数据驱动第一种:直接在脚本里面指定参数列表,最简单。适合于参数比较少的情况。第二种:使用CSV文件,适合于参数比较大的情况。第三种方式:使用函数生成数据,适用于数据变化…...
Dirsearch 深度使用教程:从基础扫描到携带 Cookie 探索网站
在网络安全测试和网站信息收集过程中,Dirsearch 是一款强大的开源工具,能够快速扫描网站,找出潜在的目录和文件。而当面对需要登录才能访问的网站资源时,通过携带 Cookie 扫描,Dirsearch 可以模拟已登录状态࿰…...
垃圾回收(GC)基础原理全面解析
掌握 GC 原理,是高效 Java 开发的第一步! 前言 垃圾回收(Garbage Collection,简称 GC)是 Java 的核心优势之一,它让开发者无需手动管理内存,极大降低了内存泄露和悬挂指针的风险。但当应用进入高并发、大数据量的场景时,GC 机制本身反而会成为性能瓶颈。 理解 GC 的原…...
海康NVR录像回放SDK原始流转FLV视频流:基于Java的流媒体转码(无需安装第三方插件ffmpeg)
wlinker-video-monitor 代码地址:https://gitee.com/wlinker/wlinker-video-monitor 背景与需求 在安防监控、智能楼宇等场景中,海康威视设备作为行业主流硬件,常需要将录像回放功能集成到Web系统中。然而,海康设备的原始视频流…...
【项目】SpringBoot +MybatisPlus集成多数据源
引言 应项目需求,需要引入另外的Mysql数据库,但是项目已经引入一个Mysql,这时有几种方案 通过Dynamic-DataSource 框架,无缝集成 但是是动态切换数据源的,跟项目需求不符合,于是采取第二种通过自定义数据…...
Suricata 3规则介绍、以及使用
列出更新源列表(有好多个规则源,后面有介绍的) suricata-update list-sourcesName: sslbl/ja3-fingerprintsVendor: Abuse.chSummary: Abuse.ch Suricata JA3 Fingerprint RulesetLicense: CC0-1.0 Name: malsilo/win-malwareVendor: malsil…...
基于OpenCV的物体跟踪:CSRT算法
文章目录 引言一、系统概述二、CSRT算法简介三、核心代码解析1. 初始化跟踪器和摄像头2. 主循环结构3. 目标选择与跟踪初始化4. 目标跟踪与结果显示5. 资源释放 四、系统使用说明五、完整代码六、总结 引言 目标跟踪是计算机视觉领域的重要应用之一,广泛应用于视频…...
面向未来,遨游推出5G-A智能防爆对讲机等系列终端
从5G扬帆到5G-A启航,遨游通讯始终立于技术潮头。在通信技术加速向5G-A演进的关键节点,遨游通讯旗舰产品AORO M6 Pro智能防爆对讲机,不仅实现了芯片到系统架构的全面自主可控,更通过5G-A技术的高速率、低时延、广连接与通感一体能力…...
qt浏览文件支持惯性
#include <QApplication> #include <QListWidget> #include <QScroller> #include <QScrollerProperties>int main(int argc, char *argv[]) {QApplication app(argc, argv);// 创建列表控件并添加示例项QListWidget listWidget;for (int i 0; i <…...
算子窗口操作
抠图 (提取图像感兴趣的区域) * 使用halcon 抠图* 窗体属性设设置: 设置窗体绘制图案的模式 magrin边框模式(只有一个边框) * fill填充模式(边框内部会有一个遮罩层) dev_set_draw (fill)* 设置颜色 dev_set_color (green) * 设置线宽dev_set_line_width (5)read_image (Im…...
如何提灯验车
✅ 重点 车标倾斜特别严重 导航定位不准 发动机顿挫异响 自动门把手关闭时异响 底盘有划痕和主驾位与扶手箱位置间隙过小磨损 蓝牙钥匙解锁异常,开关解锁不灵敏 空调无法制冷 灯罩有划痕 开启大灯就有嗡嗡嗡的异响 ✅ 一、文件与证件检…...
人工智能在生物医学研究中的创新应用
随着人工智能(AI)技术的飞速发展,其在生物医学领域的应用逐渐成为研究热点。AI不仅为生物医学研究提供了强大的工具,还在疾病诊断、药物研发、基因编辑等方面展现出巨大的潜力。本文将探讨人工智能在生物医学研究中的创新应用&…...
迁移学习实战:用预训练模型解决小样本图像分类
🚀 迁移学习实战:用预训练模型解决小样本图像分类(PyTorch实现) 当我们没有成千上万的训练样本时,如何训练一个表现良好的图像分类模型?答案是——迁移学习。本篇将带你用 PyTorch 快速上手迁移学习,用预训练模型(如 ResNet18)解决小样本分类问题。 🧠 一、什么是迁…...
html,js获取扫码设备的输入内容
<script type"text/javascript"><!-- window.onload function () {// 获取扫描的二维码内容 var code ""; var lastTime, nextTime; var lastCode, nextCode; document.onkeypress function (e) { nextCode e.which; ne…...
项目执行中缺乏风险管理,如何预防潜在问题?
要预防潜在问题,必须在项目执行中融入建立全面的风险识别机制、制定应对策略、实施动态监控、强化团队风险意识、定期评估与复盘。其中,建立全面的风险识别机制至关重要。项目初期若未进行系统性的风险识别,就很难在项目过程中及时应对变化&a…...
树形展示三级分类数据
vue3 实现多级分类_产品设计 平台端添加多个二级三级分类的页面-CSDN博客...
大模型如何助力数学可视化?
大家好,我是 i 学习的老章 在数学学习和教学中,将抽象概念可视化对于理解至关重要。Manim 是一个强大的数学动画引擎,由著名数学科普视频作者 3Blue1Brown 开发并广为人知。 老章较早之前就介绍过 manim:B 站上爆红的数学视频&a…...
什么是endpoints?
在 Kubernetes 中,Endpoints 是一个资源对象,它表示服务(Service)到 Pod 的网络连接。 Endpoints 的主要作用是将服务的虚拟 IP 地址映射到实际的 Pod IP 地址,从而实现服务发现和负载均衡。 1.Endpoints 的作用 服务…...
基于 Redis 实现短信验证码登录功能的完整方案
🧱 一、技术栈与依赖配置 使用 Spring Boot Redis 实现短信验证码登录,以下是推荐的 Maven 依赖: <dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><ar…...
监控易一体化运维:拥有全部核心技术,助力国产化信创运维
在数字化转型浪潮与信创产业蓬勃发展的当下,企业对运维系统的要求愈发严苛。随着数字化领域的巨大变迁,一款强大且适配信创环境的运维系统对企业的重要性不言而喻。今天,让我们一同深度剖析监控易系统在信创领域展现出的卓越优势。 信创产业&…...
微 PE , USM 魔术师两款 PE 对比
微 PE 和 USM 魔术师两款 PE 各有特点: 纯净度 微 PE:没有植入强制性、商业性软件和链接,也没有病毒和木马,非常纯净。USM 魔术师:同样无广告、无流氓、无捆绑、无后门,从官方途径下载能保证纯净度。 功能…...
测试模版1
本篇技术博文摘要 🌟 引言 📘 在这个变幻莫测、快速发展的技术时代,与时俱进是每个IT工程师的必修课。我是盛透侧视攻城狮,一名什么都会一丢丢的网络安全工程师,也是众多技术社区的活跃成员以及多家大厂官方认可人员&a…...
elementUI 中el-date-picker和el-select的样式调整
1. el-date-picker <el-date-picker class"select1" size"small" v-model"timeRangeArr" type"daterange" align"right" unlink-panels range-separator"至" start-placeholder"开始日期" end-pla…...
基于亚马逊云科技构建音视频直播审核方案
1. 前言 随着互联网内容形态的多样化发展,用户生成内容(UGC)呈现爆发式增长。社交平台、直播、短视频、语聊房等应用场景中,海量的音视频内容需要进行实时审核,以维护平台安全与用户体验。 然而,企业在构…...
Vue3 组件之间传值
在 Vue3 中,组件之间的数据传递主要有以下几种方式,适用于不同的场景: 一、父组件向子组件传值:props 1. 子组件定义 props <!-- ChildComponent.vue --> <script setup> // 组合式 API(推荐)…...
深入理解用于中断控制的 NVIC 寄存器
NVIC 中有多个用于中断控制的寄存器(异常类型 16~255),这些寄存器位于系统控制空间(SCS)地址区域。下表是这些寄存器的概览: 除了软件触发寄存器(STIR)外,所有这些寄存器…...
Podman(Pod Manager)简介
Podman 简介 Podman(Pod Manager)是一个开源的容器管理工具,由红帽(Red Hat)开发,用于替代 Docker,支持运行、管理 OCI(Open Container Initiative)容器和容器镜像。它设…...
HarmonyOS NEXT端云一体化工程目录结构
视频课程学习报名入口:HarmonyOS NEXT端云一体化开发 端云一体化开发工程由端开发工程(Application)和云开发工程(CloudProgram)两大核心模块构成。 1)端开发工程目录结构 端开发工程主要用于开发应用端侧的业务代码,通用云开发模板的端开发工程目录结构如下图所示: …...
【C++ 真题】P1075 [NOIP 2012 普及组] 质因数分解
P1075 [NOIP 2012 普及组] 质因数分解 题目描述 已知正整数 n n n 是两个不同的质数的乘积,试求出两者中较大的那个质数。 输入格式 输入一个正整数 n n n。 输出格式 输出一个正整数 p p p,即较大的那个质数。 输入输出样例 #1 输入 #1 21输…...
力扣热题100,力扣148.排序链表力扣.26找出字符串中第一个匹配项的下标力扣146.LRU缓存序列管理器
目录 力扣148.排序链表 力扣.26找出字符串中第一个匹配项的下标 力扣146.LRU缓存 序列管理器 力扣148.排序链表 那个O1,暂时我没有考虑,但是这个nlogn,我就想什么时候会有log呢,应该是2的次幂方向思考,一说2,是否能想到2分呢&…...
防火墙高可靠性
防火墙高可靠性技术概述 防火墙高可靠性技术分为两类:设备高可靠性和链路高可靠性 防火墙双机热备 双机热备概述及相关协议 HRP协议:即Huawei Redundancy Protocol,主要用于实现防火墙双机之间关键配置命令和状态化信息的备份,…...
C++ stack对象创建、入栈、获取栈顶
stack对象创建直接调用C对应的<stack>,进行创建 #include<iostream> #include<stack>using namespace std;int main() {// 1 默认构造函数stack<int> stk1;// 2 拷贝构造函数stack<int> stk2;stk1 stk2;return 0;} 入栈操作有一条…...
x-cmd install | Pillager:Go 语言打造的敏感信息文件系统扫描利器
目录 Pillager 的独特优势安装Pillager 的应用场景Pillager 的核心功能 还在为文件系统中潜在的敏感信息泄露而担忧吗?Pillager 是一款由 Go 语言编写的强大工具,旨在帮助你轻松扫描文件系统,发现隐藏的密钥、密码、API 令牌等敏感信息。 Pil…...
第9.1讲、Tiny Encoder Transformer:极简文本分类与注意力可视化实战
项目简介 本项目实现了一个极简版的 Transformer Encoder 文本分类器,并通过 Streamlit 提供了交互式可视化界面。用户可以输入任意文本,实时查看模型的分类结果及注意力权重热力图,直观理解 Transformer 的内部机制。项目采用 HuggingFace …...
asp.net web form nlog的安装
一、安装NuGet包 核心包安装 NLog提供日志记录核心功能 NLog.Config自动生成默认配置文件模板 配置NLog文件 配置文件创建 项目根目录自动生成NLog.config文件(通过NuGet安装NLog.Config时创建) <?xml version"1.0" encoding&…...