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

【Bluedroid】蓝牙 HID 设备信息加载与注册机制及配置缓存系统源码解析

本篇解析Android蓝牙子系统加载配对HID设备的核心流程,通过btif_storage_load_bonded_hid_info实现从NVRAM读取设备属性、验证绑定状态、构造描述符并注册到BTA_HH模块。重点剖析基于ConfigCache的三层存储架构(全局配置/持久设备/临时设备),其通过动态持久化判定策略和LRU淘汰机制,在保证数据可靠性的同时实现高效内存管理。系统采用递归锁保障线程安全,支持多层级密钥解密校验,为蓝牙HID设备管理提供标准化解决方案。

  • 作用:从NVRAM加载已配对蓝牙HID设备的信息,并将其注册到蓝牙HID主机模块(BTA_HH)

  • 触发场景:系统启动时或需要重新加载HID设备信息时调用

btif_storage_load_bonded_hid_info

packages/modules/Bluetooth/system/btif/src/btif_profile_storage.cc
/********************************************************************************* Function         btif_storage_load_bonded_hid_info** Description      BTIF storage API - Loads hid info for all the bonded devices*                  from NVRAM and adds those devices  to the BTA_HH.** Returns          BT_STATUS_SUCCESS if successful, BT_STATUS_FAIL otherwise*******************************************************************************/
bt_status_t btif_storage_load_bonded_hid_info(void) {// 1. 遍历所有已配对设备for (const auto& bd_addr : btif_config_get_paired_devices()) {auto name = bd_addr.ToString();tAclLinkSpec link_spec; // 用于存储设备的连接信息log::verbose("Remote device:{}", ADDRESS_TO_LOGGABLE_CSTR(bd_addr));int value;// 2. 获取 HID 属性掩码if (!btif_config_get_int(name, BTIF_STORAGE_KEY_HID_ATTR_MASK, &value))continue;uint16_t attr_mask = (uint16_t)value;// 3. 检查设备是否已绑定if (btif_in_fetch_bonded_device(name) != BT_STATUS_SUCCESS) {btif_storage_remove_hid_info(bd_addr); // 移除该设备的 HID 信息continue;}// 4. 初始化设备描述信息结构体tBTA_HH_DEV_DSCP_INFO dscp_info;memset(&dscp_info, 0, sizeof(dscp_info));// 5. 获取设备描述信息btif_config_get_int(name, BTIF_STORAGE_KEY_HID_SUB_CLASS, &value);uint8_t sub_class = (uint8_t)value;btif_config_get_int(name, BTIF_STORAGE_KEY_HID_APP_ID, &value);uint8_t app_id = (uint8_t)value;btif_config_get_int(name, BTIF_STORAGE_KEY_HID_VENDOR_ID, &value);dscp_info.vendor_id = (uint16_t)value;btif_config_get_int(name, BTIF_STORAGE_KEY_HID_PRODUCT_ID, &value);dscp_info.product_id = (uint16_t)value;btif_config_get_int(name, BTIF_STORAGE_KEY_HID_VERSION, &value);dscp_info.version = (uint16_t)value;btif_config_get_int(name, BTIF_STORAGE_KEY_HID_COUNTRY_CODE, &value);dscp_info.ctry_code = (uint8_t)value;value = 0;btif_config_get_int(name, BTIF_STORAGE_KEY_HID_SSR_MAX_LATENCY, &value);dscp_info.ssr_max_latency = (uint16_t)value;value = 0;btif_config_get_int(name, BTIF_STORAGE_KEY_HID_SSR_MIN_TIMEOUT, &value);dscp_info.ssr_min_tout = (uint16_t)value;// 6. 获取设备描述符size_t len =btif_config_get_bin_length(name, BTIF_STORAGE_KEY_HID_DESCRIPTOR);if (len > 0) {dscp_info.descriptor.dl_len = (uint16_t)len;dscp_info.descriptor.dsc_list = (uint8_t*)alloca(len);btif_config_get_bin(name, BTIF_STORAGE_KEY_HID_DESCRIPTOR,(uint8_t*)dscp_info.descriptor.dsc_list, &len);}// 7. 添加设备到 BTA_HH 模块// add extracted information to BTA HHlink_spec.addrt.bda = bd_addr;link_spec.addrt.type = BLE_ADDR_PUBLIC;link_spec.transport = BT_TRANSPORT_AUTO;if (btif_hh_add_added_dev(link_spec, attr_mask)) {BTA_HhAddDev(link_spec, attr_mask, sub_class, app_id, dscp_info);}}return BT_STATUS_SUCCESS;
}

从非易失性随机访问存储器(NVRAM)里加载所有已配对蓝牙 HID(Human Interface Device,人机接口设备)设备的信息,并且将这些设备添加到 BTA_HH(Bluetooth Application - Human Interface Device Host)模块中。

核心流程:

btif_config_get_paired_devices

/packages/modules/Bluetooth/system/btif/src/btif_config.cc
std::vector<RawAddress> btif_config_get_paired_devices() {std::vector<std::string> names;CHECK(bluetooth::shim::is_gd_stack_started_up());// 获取持久化存储的设备名称names = bluetooth::shim::BtifConfigInterface::GetPersistentDevices();std::vector<RawAddress> result;result.reserve(names.size());// 遍历设备名称并转换为地址for (const auto& name : names) {RawAddress addr = {};// Gather up known devices from configuration section namesif (RawAddress::FromString(name, addr)) {result.emplace_back(addr);}}return result;
}

获取所有已配对蓝牙设备的地址,返回一个包含 RawAddress 对象的向量。借助 bluetooth::shim::BtifConfigInterface 来获取持久化存储的设备名称,然后将这些名称转换为 RawAddress 对象。

GetPersistentDevices

/packages/modules/Bluetooth/system/main/shim/config.cc
std::vector<std::string> BtifConfigInterface::GetPersistentDevices() {return GetStorage()->GetPersistentSections();
}
GetPersistentSections
packages/modules/Bluetooth/system/gd/storage/storage_module.cc
std::vector<std::string> StorageModule::GetPersistentSections() const {std::lock_guard<std::recursive_mutex> lock(mutex_);return pimpl_->cache_.GetPersistentSections();
}
ConfigCache::GetPersistentSections
/packages/modules/Bluetooth/system/gd/storage/config_cache.cc
std::vector<std::string> ConfigCache::GetPersistentSections() const {std::lock_guard<std::recursive_mutex> lock(mutex_);std::vector<std::string> paired_devices;paired_devices.reserve(persistent_devices_.size());// 遍历持久化设备并添加名称到容器中for (const auto& elem : persistent_devices_) {paired_devices.emplace_back(elem.first);}return paired_devices;
}

获取所有持久化设备的名称(可能代表设备的 MAC 地址等标识),并将这些名称存储在一个 std::vector<std::string> 类型的容器中返回。

btif_config_get_int

packages/modules/Bluetooth/system/btif/src/btif_config.cc
bool btif_config_get_int(const std::string& section, const std::string& key,int* value) {CHECK(bluetooth::shim::is_gd_stack_started_up());return bluetooth::shim::BtifConfigInterface::GetInt(section, key, value);
}

BtifConfigInterface::GetInt

packages/modules/Bluetooth/system/main/shim/config.cc
bool BtifConfigInterface::GetInt(const std::string& section,const std::string& property, int* value) {ASSERT(value != nullptr);auto ret = GetStorage()->GetInt(section, property);if (ret) {*value = *ret;}return ret.has_value();
}

从配置存储中获取指定部分(section)下指定属性(property)的整数值,并将该值存储到传入的指针 value 所指向的内存位置。若成功获取到值,则返回 true;若未获取到值,则返回 false。

packages/modules/Bluetooth/system/gd/storage/storage_module.cc
std::optional<int> StorageModule::GetInt(const std::string& section, const std::string& property) const {std::lock_guard<std::recursive_mutex> lock(mutex_);return ConfigCacheHelper::FromConfigCache(pimpl_->cache_).GetInt(section, property);
}

ConfigCacheHelper::GetInt

/packages/modules/Bluetooth/system/gd/storage/config_cache_helper.cc
std::optional<int> ConfigCacheHelper::GetInt(const std::string& section, const std::string& property) const {// 1. 获取属性的字符串值auto value_str = config_cache_.GetProperty(section, property);if (!value_str) {return std::nullopt;}// 2. 获取属性的 int64_t 类型值auto large_value = GetInt64(section, property);if (!large_value) {return std::nullopt;}// 3. 检查值是否在 int 类型的数值范围内if (!common::IsNumberInNumericLimits<int>(*large_value)) {return std::nullopt;}return static_cast<uint32_t>(*large_value);
}

从配置缓存中获取指定部分(section)和属性(property)对应的整数值。如果获取成功且该值在 int 类型的数值范围内,函数将返回该整数值;否则,返回 std::nullopt 表示未获取到有效的 int 类型值。

ConfigCache::GetProperty
/packages/modules/Bluetooth/system/gd/storage/config_cache.cc
std::optional<std::string> ConfigCache::GetProperty(const std::string& section, const std::string& property) const {// 加锁以保证线程安全std::lock_guard<std::recursive_mutex> lock(mutex_);// 1. 在 information_sections_ 中查找属性auto section_iter = information_sections_.find(section);if (section_iter != information_sections_.end()) {auto property_iter = section_iter->second.find(property);if (property_iter != section_iter->second.end()) {return property_iter->second;}}// 2. 在 persistent_devices_ 中查找属性section_iter = persistent_devices_.find(section);if (section_iter != persistent_devices_.end()) {auto property_iter = section_iter->second.find(property);if (property_iter != section_iter->second.end()) {std::string value = property_iter->second;if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && value == kEncryptedStr) {return os::ParameterProvider::GetBtKeystoreInterface()->get_key(section + "-" + property);}return value;}}// 3. 在 temporary_devices_ 中查找属性section_iter = temporary_devices_.find(section);if (section_iter != temporary_devices_.end()) {auto property_iter = section_iter->second.find(property);if (property_iter != section_iter->second.end()) {return property_iter->second;}}return std::nullopt;
}

从配置缓存里获取指定部分(section)和属性(property)的值。若能找到对应的值,就返回该值;若找不到,则返回 std::nullopt。

get_key
packages/modules/Bluetooth/system/btif/src/btif_keystore.cc
std::string get_key(std::string prefix) override {log::verbose("prefix: {}", prefix);// 检查回调函数指针是否为空if (!callbacks) {log::warn("callback isn't ready. prefix: {}", prefix);return "";}// 用于存储解密后的密钥字符串std::string decryptedString;// 尝试在 key_map 中查找以 prefix 为键的元素std::map<std::string, std::string>::iterator iter = key_map.find(prefix);if (iter == key_map.end()) {// 如果在 key_map 中未找到对应的元素// 调用回调函数从外部获取密钥decryptedString = callbacks->get_key(prefix);// 将获取到的密钥存储到 key_map 中,以便后续使用key_map[prefix] = decryptedString;log::verbose("get key from bluetoothkeystore.");} else {// 如果在 key_map 中找到了对应的元素// 直接从 key_map 中获取解密后的密钥decryptedString = iter->second;}// 返回解密后的密钥字符串return decryptedString;
}

根据给定的前缀 prefix 来获取对应的密钥。先尝试从本地的 key_map 中查找该密钥,如果找不到,会通过回调函数 callbacks->get_key 从外部(如蓝牙密钥存储库)获取密钥,并将其保存到 key_map 中,以便后续使用。

IsNumberInNumericLimits
packages/modules/Bluetooth/system/gd/common/numbers.h
// Check if input is within numeric limits of RawType
template <typename RawType, typename InputType>
bool IsNumberInNumericLimits(InputType input) {// Only arithmetic types are supportedstatic_assert(std::is_arithmetic_v<RawType> && std::is_arithmetic_v<InputType>);// Either both are signed or both are unsignedstatic_assert((std::is_signed_v<RawType> && std::is_signed_v<InputType>) ||(std::is_unsigned_v<RawType> && std::is_unsigned_v<InputType>));// 检查输入类型的最大值是否超过目标类型的最大值if (std::numeric_limits<InputType>::max() > std::numeric_limits<RawType>::max()) {if (input > std::numeric_limits<RawType>::max()) {return false;}}// 检查输入类型的最小值是否低于目标类型的最小值if (std::numeric_limits<InputType>::lowest() < std::numeric_limits<RawType>::lowest()) {if (input < std::numeric_limits<RawType>::lowest()) {return false;}}// 如果输入值在目标类型的数值范围内,返回 truereturn true;
}

IsNumberInNumericLimits 是一个模板函数,其作用是检查输入值 input 是否处于 RawType 类型所规定的数值范围之内。借助 C++ 的类型特性和数值极限工具,保证仅对算术类型(如整数、浮点数)进行操作,并且会对输入类型和目标类型的有符号性进行检查。

btif_config_get_bin_length

packages/modules/Bluetooth/system/btif/src/btif_config.cc
size_t btif_config_get_bin_length(const std::string& section,const std::string& key) {CHECK(bluetooth::shim::is_gd_stack_started_up());return bluetooth::shim::BtifConfigInterface::GetBinLength(section, key);
}
/packages/modules/Bluetooth/system/main/shim/config.cc
size_t BtifConfigInterface::GetBinLength(const std::string& section,const std::string& property) {auto value_vec = GetStorage()->GetBin(section, property);if (!value_vec) {return 0;}return value_vec->size();
}
packages/modules/Bluetooth/system/gd/storage/storage_module.cc
std::optional<int> StorageModule::GetInt(const std::string& section, const std::string& property) const {std::lock_guard<std::recursive_mutex> lock(mutex_);return ConfigCacheHelper::FromConfigCache(pimpl_->cache_).GetInt(section, property);
}

btif_hh_add_added_dev

packages/modules/Bluetooth/system/btif/src/btif_hh.cc
/********************************************************************************  Static variables******************************************************************************/
btif_hh_cb_t btif_hh_cb;/********************************************************************************* Function         btif_hh_add_added_dev** Description      Add a new device to the added device list.** Returns          true if add successfully, otherwise false.******************************************************************************/
bool btif_hh_add_added_dev(const tAclLinkSpec& link_spec,tBTA_HH_ATTR_MASK attr_mask) {int i;// 第一次遍历:检查设备是否已经存在于已添加设备列表中for (i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) {if (btif_hh_cb.added_devices[i].link_spec.addrt.bda ==link_spec.addrt.bda) {// 如果设备已经存在,返回 falselog::warn("Device {} already added", ADDRESS_TO_LOGGABLE_STR(link_spec));return false;}}// 第二次遍历:寻找列表中的空位来添加新设备for (i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) {if (btif_hh_cb.added_devices[i].link_spec.addrt.bda.IsEmpty()) {// 找到空位后,记录添加设备的日志log::warn("Added device {}", ADDRESS_TO_LOGGABLE_STR(link_spec));// 将新设备的链接规格信息赋值给该空位btif_hh_cb.added_devices[i].link_spec = link_spec;// 初始化设备句柄为无效句柄btif_hh_cb.added_devices[i].dev_handle = BTA_HH_INVALID_HANDLE;// 赋值设备属性掩码btif_hh_cb.added_devices[i].attr_mask = attr_mask;// 添加成功,返回 truereturn true;}}// 如果列表已满,没有空位,记录错误日志并返回 falselog::warn("Error, out of space to add device");return false;
}

将一个新的设备添加到已添加设备列表中。先检查设备是否已经存在于列表中,如果存在则不添加并返回 false;若不存在,会尝试在列表中找到一个空位来添加该设备,若成功添加则返回 true;如果列表已满,无法添加新设备,也会返回 false。

BTA_HhAddDev

packages/modules/Bluetooth/system/bta/hh/bta_hh_api.cc
/********************************************************************************* Function         BTA_HhAddDev** Description      Add a virtually cabled device into HID-Host device list*                  to manage and assign a device handle for future API call,*                  host applciation call this API at start-up to initialize its*                  virtually cabled devices.** Returns          void*******************************************************************************/
void BTA_HhAddDev(const tAclLinkSpec& link_spec, tBTA_HH_ATTR_MASK attr_mask,uint8_t sub_class, uint8_t app_id,tBTA_HH_DEV_DSCP_INFO dscp_info) {// 1. 计算所需内存大小size_t len = sizeof(tBTA_HH_MAINT_DEV) + dscp_info.descriptor.dl_len;// 2. 分配内存tBTA_HH_MAINT_DEV* p_buf = (tBTA_HH_MAINT_DEV*)osi_calloc(len);// 3. 设置消息头信息p_buf->hdr.event = BTA_HH_API_MAINT_DEV_EVT;p_buf->sub_event = BTA_HH_ADD_DEV_EVT;p_buf->hdr.layer_specific = BTA_HH_INVALID_HANDLE;// 4. 设置设备属性p_buf->attr_mask = (uint16_t)attr_mask;p_buf->sub_class = sub_class;p_buf->app_id = app_id;p_buf->link_spec = link_spec;// 5. 复制描述符信息memcpy(&p_buf->dscp_info, &dscp_info, sizeof(tBTA_HH_DEV_DSCP_INFO));if (dscp_info.descriptor.dl_len != 0 && dscp_info.descriptor.dsc_list) {p_buf->dscp_info.descriptor.dl_len = dscp_info.descriptor.dl_len;p_buf->dscp_info.descriptor.dsc_list = (uint8_t*)(p_buf + 1);memcpy(p_buf->dscp_info.descriptor.dsc_list, dscp_info.descriptor.dsc_list,dscp_info.descriptor.dl_len);} else {p_buf->dscp_info.descriptor.dsc_list = NULL;p_buf->dscp_info.descriptor.dl_len = 0;}// 6. 发送消息bta_sys_sendmsg(p_buf);
}

将一个虚拟有线设备添加到 HID - Host(人机接口设备主机)设备列表中进行管理,并为未来的 API 调用分配一个设备句柄。主机应用程序在启动时会调用此 API 来初始化其虚拟有线设备。

class ConfigCache

namespace bluetooth {
namespace storage {class Mutation;// A memory operated section-key-value structured config
//
// A section can be either persistent or temporary. When a section becomes persistent, all its properties are
// written to disk.
//
// A section becomes persistent when a property that is part of persistent_property_names_ is written to config cache;
// A section becomes temporary when all properties that are part of persistent_property_names_ is removed
//
// The definition of persistent sections is up to the user and is defined through the |persistent_property_names|
// argument. When these properties are link key properties, then persistent sections is equal to bonded devices
//
// This class is thread safe
class ConfigCache {public:ConfigCache(size_t temp_device_capacity, std::unordered_set<std::string_view> persistent_property_names);ConfigCache(const ConfigCache&) = delete;ConfigCache& operator=(const ConfigCache&) = delete;virtual ~ConfigCache() = default;// no copy// can moveConfigCache(ConfigCache&& other) noexcept;ConfigCache& operator=(ConfigCache&& other) noexcept;// comparison operators, callback doesn't countbool operator==(const ConfigCache& rhs) const;bool operator!=(const ConfigCache& rhs) const;// observersvirtual bool HasSection(const std::string& section) const;virtual bool HasProperty(const std::string& section, const std::string& property) const;// Get property, return std::nullopt if section or property does not existvirtual std::optional<std::string> GetProperty(const std::string& section, const std::string& property) const;// Returns a copy of persistent device MAC addressesvirtual std::vector<std::string> GetPersistentSections() const;// Return true if a section is persistentvirtual bool IsPersistentSection(const std::string& section) const;// Return true if a section has one of the properties in |property_names|virtual bool HasAtLeastOneMatchingPropertiesInSection(const std::string& section, const std::unordered_set<std::string_view>& property_names) const;// Return true if a property is part of persistent_property_names_virtual bool IsPersistentProperty(const std::string& property) const;// Serialize to legacy config formatvirtual std::string SerializeToLegacyFormat() const;// Return a copy of pair<section_name, property_value> with propertystruct SectionAndPropertyValue {std::string section;std::string property;bool operator==(const SectionAndPropertyValue& rhs) const {return section == rhs.section && property == rhs.property;}bool operator!=(const SectionAndPropertyValue& rhs) const {return !(*this == rhs);}};virtual std::vector<SectionAndPropertyValue> GetSectionNamesWithProperty(const std::string& property) const;// modifiers// Commit all mutation entries in sequence while holding the config mutexvirtual void Commit(std::queue<MutationEntry>& mutation);virtual void SetProperty(std::string section, std::string property, std::string value);virtual bool RemoveSection(const std::string& section);virtual bool RemoveProperty(const std::string& section, const std::string& property);virtual void ConvertEncryptOrDecryptKeyIfNeeded();// TODO: have a systematic way of doing this instead of specialized methods// Remove sections with |property| setvirtual void RemoveSectionWithProperty(const std::string& property);// remove all content in this config cache, restore it to the state after the explicit constructorvirtual void Clear();// Set a callback to notify interested party that a persistent config change has just happenedvirtual void SetPersistentConfigChangedCallback(std::function<void()> persistent_config_changed_callback);// Device config specific methods// TODO: methods here should be moved to a device specific config cache if this config cache is supposed to be generic// Legacy stack has device type inconsistencies, this method is trying to fix itvirtual bool FixDeviceTypeInconsistencies();// static methods// Check if section is formatted as a MAC addressstatic bool IsDeviceSection(const std::string& section);// constantsstatic const std::string kDefaultSectionName;private:mutable std::recursive_mutex mutex_;// A callback to notify interested party that a persistent config change has just happened, empty by defaultstd::function<void()> persistent_config_changed_callback_;// A set of property names that if set would make a section persistent and if non of these properties are set, a// section would become temporary againstd::unordered_set<std::string_view> persistent_property_names_;// Common section that does not relate to remote device, will be written to diskcommon::ListMap<std::string, common::ListMap<std::string, std::string>> information_sections_;// Information about persistent devices, normally paired, will be written to diskcommon::ListMap<std::string, common::ListMap<std::string, std::string>> persistent_devices_;// Information about temporary devices, normally unpaired, will not be written to disk, will be evicted automatically// if capacity exceeds given value during initializationcommon::LruCache<std::string, common::ListMap<std::string, std::string>> temporary_devices_;// Convenience method to check if the callback is valid before calling itinline void PersistentConfigChangedCallback() const {if (persistent_config_changed_callback_) {persistent_config_changed_callback_();}}
};}  // namespace storage
}  // namespace bluetooth

ConfigCache 类用于管理配置信息,这些信息以部分(section)和键值对的形式存储。部分可以是持久的(persistent)或临时的(temporary)。当写入属于 persistent_property_names_ 集合的属性时,部分会变为持久的;当移除所有属于 persistent_property_names_ 的属性时,部分会变为临时的。

采用分层存储策略的蓝牙配置缓存系统,通过持久化/临时化的二元分类和LRU淘汰机制,在内存效率与数据持久性之间取得平衡。

关键架构设计

①三元存储结构

  • 通用配置段 (information_sections_)

    • 存储与具体设备无关的全局配置

    • 使用ListMap保证有序存储

    • 始终持久化到磁盘

  • 持久化设备段 (persistent_devices_)

    • 存储已配对设备的配置信息

    • 当包含persistent_property_names_定义的属性时自动持久化

    • 使用ListMap维护插入顺序

  • 临时设备段 (temporary_devices_)

    • 存储未配对设备的临时配置

    • 采用LruCache实现,当容量超过初始化阈值时自动淘汰最久未使用的条目

    • 不写入磁盘

②持久化判定机制

  • 通过构造函数传入的persistent_property_names集合定义持久化属性

  • 当设备配置段包含任意持久化属性时:

    • 该段升级为持久化状态

    • 所有属性(包括非持久化属性)将被写入磁盘

  • 当所有持久化属性被移除时:

    • 该段降级为临时状态

    • 可能被LRU机制淘汰

③线程安全实现

  • 使用std::recursive_mutex保护所有访问操作

  • 支持递归加锁,避免死锁风险

  • 所有public方法均通过mutex保护,保证多线程环境下的数据一致性

时序图

总结

①核心流程:系统启动时,通过遍历NVRAM中存储的已配对设备信息,提取HID属性(如子类、应用ID、描述符等),校验设备绑定状态后,将有效信息注册到蓝牙HID主机模块(BTA_HH)。采用7步链式处理(设备遍历→属性校验→描述符构建→主机注册),通过btif_config系列接口实现跨模块数据透传,确保HID设备信息的完整性加载

②架构创新

  • 三元存储结构:分离全局配置/持久设备/临时设备数据,采用差异化的ListMap与LruCache容器

  • 动态持久化:通过预定义persistent_property_names自动升级设备段存储等级,支持密钥触发式落盘

  • 安全机制:集成数值范围校验模板(IsNumberInNumericLimits)、加密属性自动解密、双阶段设备查重

③生产级特性

  • 递归锁实现配置操作的原子性

  • 设备描述符内存动态分配(alloca)避免内存泄漏

  • 通过Mutation队列实现批量配置更新的事务性提交

相关文章:

【Bluedroid】蓝牙 HID 设备信息加载与注册机制及配置缓存系统源码解析

本篇解析Android蓝牙子系统加载配对HID设备的核心流程&#xff0c;通过btif_storage_load_bonded_hid_info实现从NVRAM读取设备属性、验证绑定状态、构造描述符并注册到BTA_HH模块。重点剖析基于ConfigCache的三层存储架构&#xff08;全局配置/持久设备/临时设备&#xff09;&…...

【计算机视觉】CV实战项目 - PCC-Net 人群计数

PCC-Net 人群计数项目 项目特点项目运行方式与步骤1. 环境准备2. 数据准备3. 模型训练4. 实验结果 常见问题及解决方法 PCC-Net&#xff08;Perspective Crowd Counting via Spatial Convolutional Network&#xff09;是一个用于人群计数的深度学习项目&#xff0c;旨在通过空…...

Towards Transferable Targeted 3D Adversarial Attack in the Physical World--阅读笔记

目录 简介&#xff1a; 背景&#xff1a; 挑战&#xff1a; 目的&#xff1a; 技术细节&#xff1a; 贡献&#xff1a; ​​1. NeRF的核心作用&#xff1a;3D重建与参数化表示​​ ​​2. 对抗优化的创新&#xff1a;NeRF参数空间的双优化​​ ​​2.1 传统方法的局限…...

​opencv图像库编程

一、下载安装 opencv 1.1 下载安装包 1.2 解压缩 unzip opencv-3.4.11.zip 解压缩以后主目录文件夹如下&#xff1a; 1.3 进入到解压后的文件夹中 cd opencv-3.4.11 二、使用 cmake安装opencv 2.1 进入 root 用户&#xff0c;并更新一下 sudo su sudo apt-get update …...

星拍相机APP:时尚与科技的完美融合,打造你的专属美

在数字时代&#xff0c;手机相机不仅是记录生活的工具&#xff0c;更是表达个性和创意的平台。今天&#xff0c;我们要介绍的 星拍相机APP&#xff0c;就是这样一款匠心制作的手机相机应用。它融合了时尚与科技&#xff0c;提供了多样化的魔法美颜功能&#xff0c;让每一次拍摄…...

puzzle(0531)脑力航迹

目录 脑力航迹 规则 解法 简单模式 中等模式 困难模式 专家模式 脑力航迹 规则 2条航迹会产生一个相对航迹&#xff1a; 根据相对航迹和其中一个航迹推导另外一个航迹。 解法 没有任何需要推理的地方&#xff0c;就是纯粹的2个矢量相加。 简单模式 中等模式 困难模…...

【英语语法】词法---形容词

目录 形容词1. 形容词的核心功能2. 形容词的位置(1) 前置定语&#xff08;最常见&#xff09;(2) 后置定语&#xff08;特殊情况&#xff09;(3) 表语位置&#xff08;系动词后&#xff09; 3. 形容词的比较级与最高级(1) 规则变化(2) 不规则变化(3) 用法对比 4. 多个形容词修饰…...

理解 React 的 useEffect

文章目录 React 的 useEffect一、什么是副作用&#xff08;Side Effects&#xff09;&#xff1f;二、useEffect 的基本用法三、依赖数组的三种情况1. 无依赖数组&#xff08;每次渲染后都执行, 不推荐&#xff09;2. 空依赖数组&#xff08;仅在挂载时执行一次&#xff09;3. …...

2.1 基于委托的异步编程方法

基于委托的异步编程模型是 .NET 早期版本中实现异步操作的一种方式,主要通过 BeginInvoke 和 EndInvoke 方法来实现。这种基于委托的异步模式已被 Task 和 async/await 模式取代,但在维护旧代码时仍可能遇到这种模式。 委托的方法中:Invoke用于同步调用; 而BeginInvoke与E…...

对于在线教育或知识付费类网站视频处理方案

一、视频格式&#xff1a; 1. 推荐格式&#xff1a;HLS&#xff08;HTTP Live Streaming&#xff09; 优势‌&#xff1a; ‌自适应码率‌&#xff1a;根据用户网络状况自动切换清晰度&#xff0c;避免卡顿。‌广泛兼容性‌&#xff1a;iOS/macOS 原生支持&#xff0c;Android…...

Gen - CDPT举例说明:动态上下文前缀(输入先和标签结合,输出结果会更贴近标签内容)

Gen - CDPT举例说明:动态上下文前缀(输入先和标签结合,输出结果会更贴近标签内容) 目录 Gen - CDPT举例说明:动态上下文前缀(输入先和标签结合,输出结果会更贴近标签内容)输入文本示例Gen - CDPT模型处理过程示例什么是:提示次优动态前缀提示方法生成与这条评论上下文…...

UCSC CTF 2025|MISC

1、USB flag{ebdfea9b-3469-41c7-9070-d7833ecc6102} 2、three part1是图片隐水印 part1&#xff1a;8f02d3e7 part2是2进制变换 -ce89-4d6b-830e- Part3先从pass.pcapng得到密码字典 解压缩密码&#xff1a;thinkbell 3个部分合并得到flag{8f02d3e7-ce89-4d6b-830e-5d0cb5…...

FTP客户端实现(文件传输)

文章目录 &#x1f9f1; 一、FTP 基础架构回顾&#x1f680; 二、FTP 客户端的核心结构&#x1f517; 三、连接与登录过程&#x1f4cc; 1. ftp_create()&#x1f4cc; 2. ftp_connect()&#x1f4cc; 3. ftp_login() &#x1f4c1; 四、上传文件实现&#xff08;ftp_upload_fi…...

状态管理最佳实践:Bloc架构实践

状态管理最佳实践&#xff1a;Bloc架构实践 引言 Bloc (Business Logic Component) 是Flutter中一种强大的状态管理解决方案&#xff0c;它基于响应式编程思想&#xff0c;通过分离业务逻辑和UI表现层来实现清晰的代码架构。本文将深入探讨Bloc的核心概念、实现原理和最佳实践…...

嵌入式人工智能应用-第三章 opencv操作 5 二值化、图像缩放

嵌入式人工智能应用 嵌入式人工智能应用-第三章 opencv操作 5 二值化 嵌入式人工智能应用1 二值化1.1 概念介绍1.2 函数介绍1.2 基本应用1.3 参考案例 2 图像缩放2.1 基本概念2.2 函数介绍2.3 基本参考代码2.4 pyrUp 和 pyrDown 函数2.5 函数介绍2.6 参考代码2.7 总结 1 二值化…...

[OS_7] 访问操作系统对象 | offset | FHS | Handle

实验代码可以看去年暑假的这篇文章&#xff1a;【Linux】进程间通信&#xff1a;详解 VSCode使用 | 匿名管道 我们已经知道&#xff0c;进程从 execve 后的初始状态开始&#xff0c;可以通过 mmap 改变自己的地址空间&#xff0c;通过 fork 创建新的进程&#xff0c;再通过 exe…...

【Vulkan 入门系列】创建帧缓冲、命令池、命令缓存,和获取图片(六)

这一节主要介绍创建帧缓冲&#xff08;Framebuffer&#xff09;&#xff0c;创建命令池&#xff0c;创建命令缓存&#xff0c;和从文件加载 PNG 图像数据&#xff0c;解码为 RGBA 格式&#xff0c;并将像素数据暂存到 Vulkan 的 暂存缓冲区中。 一、创建帧缓冲 createFramebu…...

Linux 进程控制(自用)

非阻塞调用waitpid 这样父进程就不会阻塞&#xff0c;此时循环使用我们可以让父进程执行其他任务而不是阻塞等待 进程程序替换 进程PCB加载到内存中的代码和数据 替换就是完全替换当前进程的代码段、数据段、堆和栈&#xff0c;保存当前的PCB 代码指的是二进制代码不是源码&a…...

FreeSWITCH 简单图形化界面41 - 批量SIP视频呼叫测试

FreeSWITCH 简单图形化界面41 - 批量视频测试 0、界面预览00、安装测试工具1、注册分机2、设置接听选项2.1 上传媒体文件2.2 设置接听设置 3、呼叫测试 0、界面预览 http://myfs.f3322.net:8020/ 用户名&#xff1a;admin&#xff0c;密码&#xff1a;admin FreeSWITCH界面安…...

通过爬虫方式实现头条号发布视频(2025年4月)

1、将真实的cookie贴到代码目录中toutiaohao_cookie.txt文件里,修改python代码里的user_agent和video_path, cover_path等变量的值,最后运行python脚本即可; 2、运行之前根据import提示安装一些常见依赖,比如requests等; 3、2025年4月份最新版; 代码如下: import js…...

《AI大模型应知应会100篇》第28篇:大模型在文本创作中的应用技巧

第28篇&#xff1a;大模型在文本创作中的应用技巧 &#x1f9e0; 摘要 在内容为王的时代&#xff0c;AI大模型正在重塑文本创作的每一个环节。从创意构思到风格润色&#xff0c;从论文报告到小说脚本&#xff0c;AI不仅是创作者的助手&#xff0c;更是灵感的激发器。本文将带你…...

字节跳动发布UI-TARS-1.5,入门AI就来近屿智能

近日&#xff0c;字节跳动在 Hugging Face 平台正式开源了其最新多模态代理模型——UI-TARS-1.5。作为 UI-TARS 系列的革新之作&#xff0c;该模型以视觉语言模型为基础&#xff0c;突破性实现跨平台 GUI 自动化交互&#xff0c;为自动化与智能交互领域注入了强劲动能。无论是开…...

大数据学习栈记——MapReduce技术

本文介绍hadoop中的MapReduce技术的应用&#xff0c;使用java API。操作系统&#xff1a;Ubuntu24.04。 MapReduce概述 MapReduce概念 MapReduce是一个分布式运算程序的编程框架&#xff0c;核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序…...

GO语言入门:常用数学函数2

14.6 大型数值 math/big 包中公开了一些实用 API&#xff0c;用于表示大型整数值和浮点数值。当基础类型无法容纳要使用的数值时&#xff0c;应改用 big 包中提供的新类型。例如 Int、Float 等。 14.6.1 大型整数值之间的运算 若希望让下面两个整数值完成加、减法运算&#…...

Django 使用教程

Django 使用教程 Django 是一个高级的 Python Web 框架&#xff0c;采用了 MTV&#xff08;Model-Template-View&#xff09;设计模式&#xff0c;旨在帮助开发者快速构建高效、可维护的 Web 应用。它有着非常丰富的功能&#xff0c;包括 ORM、用户认证、表单处理、管理后台等…...

deepseek + kimi制作PPT

目录 一、kimi简介二、deepseek生成内容三、生成PPT四、编辑PPT 一、kimi简介 kimi是一款只能ppt生成器&#xff0c;擅长将文本内容生成PPT。 在这里&#xff0c;​​DeepSeek 负责内容生成与逻辑梳理​​&#xff0c;​​Kimi 优化表达与提供设计建议​​。 二、deepseek生…...

C++学习:六个月从基础到就业——内存管理:RAII原则

C学习&#xff1a;六个月从基础到就业——内存管理&#xff1a;RAII原则 本文是我C学习之旅系列的第十九篇技术文章&#xff0c;也是第二阶段"C进阶特性"的第四篇&#xff0c;主要介绍C中的RAII原则及其在资源管理中的应用。查看完整系列目录了解更多内容。 引言 在…...

量子计算与经典计算融合:开启计算新时代

一、引言 随着科技的飞速发展&#xff0c;计算技术正迎来一场前所未有的变革。量子计算作为前沿技术&#xff0c;以其强大的并行计算能力和对复杂问题的高效处理能力&#xff0c;吸引了全球科技界的关注。然而&#xff0c;量子计算并非要完全取代经典计算&#xff0c;而是与经典…...

RV1126网络环境TFTPNFS搭建(二)

二、RV1126 开发板TFTP环境搭建 2.1、Ubuntu下安装和配置 xinetd 执行以下指令&#xff0c;安装 xinetd sudo apt-get install xinetd 执行以下指令创建一个 xinetd.conf 文件 sudo vi /etc/xinetd.conf 修改 xinetd.conf 文件内容如下&#xff1a; # Simple configurat…...

计算机视觉7——齐次坐标与相机内外参

一、透视投影 透视投影&#xff08;Perspective Projection&#xff09;是计算机视觉和图形学中描述三维物体在二维平面成像的基础模型&#xff0c;其核心思想是模拟人类视觉系统的成像原理——中心投影。具体而言&#xff0c;三维空间中的点通过一个固定的投影中心&#xff0…...

学习笔记—C++—string(一)

目录 string 为什么学习string的类 string类的常用接口 string类对象的常见构造 string类对象的访问及遍历操作 operator[] 迭代器 范围for auto 迭代器&#xff08;二&#xff09; string类对象的容量操作 size,length,max_size,capacity,clear基本用法 reserve 提…...

Linux命令-Shell编程

Shell是一个命令行解释器&#xff0c;它接收应用程序/用户命令&#xff0c;然后调用操作系统内核。 写一个hello.sh脚本&#xff1a; 1.mkdir scripts 2.cd scripts 3.touch hello.sh 4.vim hello.sh #!/bin/bash echo "hello,world" 5.bash hello.sh&#xff08…...

基于Django的AI客服租车分析系统

基于Django的AI客服租车分析系统 【包含内容】 【一】项目提供完整源代码及详细注释 【二】系统设计思路与实现说明 【三】AI智能客服与用户交互指导手册 【技术栈】 ①&#xff1a;系统环境&#xff1a;Python 3.8&#xff0c;Django 4.2框架 ②&#xff1a;开发环境&a…...

计算机组成与体系结构:计算机结构的分类(classifications of computer architecture)

目录 Von Neumann Architecture&#xff08;冯诺依曼结构&#xff09; Harvard Architecture&#xff08;哈佛结构&#xff09; Modified Harvard Architecture&#xff08;改进哈佛结构&#xff09; 三种结构对比总结表 &#x1f4a1; 从“内存访问结构”角度分类&#x…...

在阿里云和树莓派上编写一个守护进程程序

目录 一、阿里云邮件守护进程 1. 安装必要库 2. 创建邮件发送脚本 mail_daemon.py 3. 设置后台运行 二、树莓派串口守护进程 1. 启用树莓派串口 2. 安装依赖库 3. 创建串口输出脚本 serial_daemon.py 4. 设置开机自启 5. 使用串口助手接收 一、阿里云邮件守护进程 1.…...

Redis 的几种数据类型

Redis 提供了多种数据类型&#xff0c;以支持不同的应用场景。每种数据类型都有其特定的操作方式&#xff0c;并且在内部实现上也有所优化&#xff0c;能够满足不同的业务需求。以下是 Redis 支持的几种常见数据类型&#xff1a; 1. 字符串&#xff08;String&#xff09; 描…...

Spring之我见 - Spring Boot Starter 自动装配原理

欢迎光临小站&#xff1a;致橡树 Spring Boot Starter 的核心设计理念是 约定优于配置&#xff0c;其核心实现基于 自动配置&#xff08;Auto-Configuration&#xff09; 和 条件化注册&#xff08;Conditional Registration&#xff09;。以下是其生效原理&#xff1a; 约定…...

LeRobot 项目部署运行逻辑(二)—— Mobile Aloha 真机部署

LeRobot 在开源项目中详细说明了部署流程&#xff0c;所以首先看一下开源的内容&#xff0c;然后再逐步拆解 首先&#xff0c;LeRobot 开源的硬件是配全部在 examples 文件夹中 包括了 Stretch 3、Aloha and Aloha 2 stationary、SO-100、LeKiwi、Moss v1 等机器人 恰好实验…...

大模型面经 | 介绍一下CLIP和BLIP

大家好,我是皮先生!! 今天给大家分享一些关于大模型面试常见的面试题,希望对大家的面试有所帮助。 往期回顾: 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题一) 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题二) 大模型面经 | 春招、秋招算法…...

Java发生OOM是否必然导致JVM退出

Java发生OOM是否必然导致JVM退出&#xff1f; 核心结论 不一定。OOM是否导致JVM退出取决于以下因素&#xff1a; OOM发生的区域JVM启动参数配置是否捕获了OOM异常 详细分析 1. 不同内存区域的OOM影响 内存区域错误类型默认是否导致JVM退出可恢复性Java堆OutOfMemoryError…...

Docker Compose 外部网络(`external: true`)与内部网络的区别

Docker Compose 外部网络(external: true)与内部网络的区别 在 Docker Compose 中&#xff0c;external: true 声明的外部网络与普通(内部)网络有重要区别&#xff0c;以下是它们的详细对比&#xff1a; 1. 定义与创建方式 特性外部网络 (external: true)内部网络 (默认)创建…...

【Android】Wallpaper学习

从wallpaper的设置来了解相关内容&#xff1a; 一&#xff0c;静态壁纸 静态壁纸设置的原理是在WallpaperManagerService里监听/data/system/users/0/wallpaper_orig相关文件的变化来触发设置&#xff0c;通过相应的组件程序去进行绘制&#xff0c; 相应的组件如&#xff1a…...

Java基础-第一章、基本数据类型

运算符&#xff1a; 1.算术运算符&#xff1a;加减乘除%等 2.逻辑运算符&#xff1a;与或非等 3.关系运算符&#xff1a;大于、小于... 4.赋值运算符&#xff1a; 这里牵扯运算符的运算先后顺序了。 赋值运算的返回值&#xff1a;就是赋值的变量本身...

《Operating System Concepts》阅读笔记:p748-p748

《Operating System Concepts》学习第 64 天&#xff0c;p748-p748 总结&#xff0c;总计 1 页。 一、技术总结 1.Transmission Control Protocol(TCP) 重点是要自己能画出其过程&#xff0c;这里就不赘述了。 二、英语总结(生词&#xff1a;3) transfer, transport, tran…...

Arduino示例代码讲解:Project 08 - Digital Hourglass 数字沙漏

Arduino示例代码讲解:Project 08 - Digital Hourglass 数字沙漏 Project 08 - Digital Hourglass 数字沙漏程序功能概述功能:硬件要求:输出:代码结构全局变量`setup()` 函数`loop()` 函数计时和点亮LED:读取倾斜开关状态:重置LED和计时器:运行过程注意事项Project 08 - …...

报告总结笔记 | Jeff Dean ETH AI趋势 笔记:AI 的重要趋势:我们是如何走到今天的,我们现在能做什么,以及我们如何塑造 AI 的未来?

报告总结笔记 | Jeff Dean ETH AI趋势 笔记&#xff1a;AI 的重要趋势&#xff1a;我们是如何走到今天的&#xff0c;我们现在能做什么&#xff0c;以及我们如何塑造 AI 的未来&#xff1f; 2025年 4 月 14 日&#xff0c;Google Research 及 Google DeepMind 的首席科学家、A…...

RocketMQ实现基于可靠消息的最终一致性

RocketMQ实现基于可靠消息的最终一致性 文章目录 RocketMQ实现基于可靠消息的最终一致性一、RocketMQ应用场景**应用解耦****流量削峰****数据分发** 二、RocketMQ 基础概念1. 核心组件2. 消费模式3. 消息可靠性 三、消息类型按发送方式分同步发送异步发送单向发送 按使用功能特…...

【题解-Acwing】790. 数的三次方根

题目:790. 数的三次方根 题目描述 给定一个浮点数 n,求它的三次方根。 输入 共一行,包含一个浮点数 n 。 输出 共一行,包含一个浮点数,表示问题的解。 注意,结果保留 6 位小数。 数据范围 −10000 ≤ n ≤ 10000 时空限制 1s / 64MB 输入样例 1000.00输出样…...

一键升级OpenSSH/OpenSSL修复安全漏洞

在服务器安全运维过程中&#xff0c;我们经常面临这样的问题&#xff1a;收到高危漏洞通报&#xff08;如最近的OpenSSH多个CVE漏洞&#xff09;&#xff0c;但Ubuntu系统无法通过apt直接升级到修复版本。这种情况下&#xff0c;传统方法需要手动编译源码&#xff0c;处理依赖关…...

Pycharm 如何删除某个 Python Interpreter

在PyCharm中&#xff0c;点击右下角的“Interpreter Settings”按钮&#xff0c;或者通过菜单栏选择“File” > “Settings”&#xff08;macOS用户选择“PyCharm” > “Preferences”&#xff09;。在设置窗口中&#xff0c;导航到“Project: [Your Project Name]” >…...