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

【Bluedroid】蓝牙存储模块配置管理:启动、读写、加密与保存流程解析

本文围绕蓝牙存储模块展开,主要解析了蓝牙存储模块(StorageModule)的初始化流程,重点围绕配置文件校验、读取、设备类型修复及加密处理展开。通过工厂重置检测、校验和验证、多源配置加载、设备类型推断修正等步骤,确保配置完整性,并结合延迟保存机制优化存储性能。

StorageModule::Start

/packages/modules/Bluetooth/system/gd/storage/storage_module.cc
void StorageModule::Start() {// 1. 线程同步std::lock_guard<std::recursive_mutex> lock(mutex_);// 2. 处理工厂重置std::string file_source;if (os::GetSystemProperty(kFactoryResetProperty) == "true") {LOG_INFO("%s is true, delete config files", kFactoryResetProperty.c_str());LegacyConfigFile::FromPath(config_file_path_).Delete();LegacyConfigFile::FromPath(config_backup_path_).Delete();os::SetSystemProperty(kFactoryResetProperty, "false");}// 3. 校验配置文件的校验和if (!is_config_checksum_pass(kConfigFileComparePass)) {LegacyConfigFile::FromPath(config_file_path_).Delete();}if (!is_config_checksum_pass(kConfigBackupComparePass)) {LegacyConfigFile::FromPath(config_backup_path_).Delete();}// 4. 读取配置文件bool save_needed = false;auto config = LegacyConfigFile::FromPath(config_file_path_).Read(temp_devices_capacity_);if (!config || !config->HasSection(kAdapterSection)) {LOG_WARN("cannot load config at %s, using backup at %s.", config_file_path_.c_str(), config_backup_path_.c_str());config = LegacyConfigFile::FromPath(config_backup_path_).Read(temp_devices_capacity_);file_source = "Backup";// Make sure to update the file, since it wasn't read from the config_file_path_save_needed = true;}if (!config || !config->HasSection(kAdapterSection)) {LOG_WARN("cannot load backup config at %s; creating new empty ones", config_backup_path_.c_str());config.emplace(temp_devices_capacity_, Device::kLinkKeyProperties);file_source = "Empty";}// 5. 设置文件源信息if (!file_source.empty()) {config->SetProperty(kInfoSection, kFileSourceProperty, std::move(file_source));}// 6. 清理临时配对信息// Cleanup temporary pairings if we have left guest modeif (!is_restricted_mode_) {config->RemoveSectionWithProperty("Restricted");}// 7. 设置配置文件创建时间戳// Read or set config file creation timestampauto time_str = config->GetProperty(kInfoSection, kTimeCreatedProperty);if (!time_str) {std::stringstream ss;auto now = std::chrono::system_clock::now();auto now_time_t = std::chrono::system_clock::to_time_t(now);ss << std::put_time(std::localtime(&now_time_t), kTimeCreatedFormat.c_str());config->SetProperty(kInfoSection, kTimeCreatedProperty, ss.str());}// 8. 修复设备类型不一致问题config->FixDeviceTypeInconsistencies();// 9. 创建 impl 对象// TODO (b/158035889) Migrate metrics module to GDpimpl_ = std::make_unique<impl>(GetHandler(), std::move(config.value()), temp_devices_capacity_);// 10. 延迟保存配置文件if (save_needed) {// Set a timer and write the new config file to disk.SaveDelayed();}// 11. 设置持久化配置更改回调pimpl_->cache_.SetPersistentConfigChangedCallback([this] { this->CallOn(this, &StorageModule::SaveDelayed); });// 12. 必要时转换加密或解密密钥if (bluetooth::os::ParameterProvider::GetBtKeystoreInterface() != nullptr) {bluetooth::os::ParameterProvider::GetBtKeystoreInterface()->ConvertEncryptOrDecryptKeyIfNeeded();}
}

StorageModule::Start 函数是 StorageModule 类的启动函数,主要负责初始化存储模块,包括处理工厂重置、校验配置文件的校验和、读取配置文件、清理临时配对信息、设置配置文件创建时间戳等操作,最后创建 impl 对象并在必要时延迟保存配置文件。

LegacyConfigFile::Read

/packages/modules/Bluetooth/system/gd/storage/legacy_config_file.cc
std::optional<ConfigCache> LegacyConfigFile::Read(size_t temp_devices_capacity) {// 1. 路径检查与文件打开ASSERT(!path_.empty());std::ifstream config_file(path_);if (!config_file || !config_file.is_open()) {LOG_ERROR("unable to open file '%s', error: %s", path_.c_str(), strerror(errno));return std::nullopt;}// 2. 初始化变量[[maybe_unused]] int line_num = 0;ConfigCache cache(temp_devices_capacity, Device::kLinkKeyProperties);std::string line;std::string section(ConfigCache::kDefaultSectionName);// 3. 逐行读取文件while (std::getline(config_file, line)) {++line_num;line = common::StringTrim(std::move(line)); // 去除行首尾的空白字符if (line.empty()) {continue;}if (line.front() == '\0' || line.front() == '#') {continue;}// 4. 处理配置文件节if (line.front() == '[') {if (line.back() != ']') {LOG_WARN("unterminated section name on line %d", line_num);return std::nullopt;}// Read 'test' from '[text]', hence -2section = line.substr(1, line.size() - 2); // 提取方括号内的节名}// 5. 处理键值对else {auto tokens = common::StringSplit(line, "=", 2);if (tokens.size() != 2) {LOG_WARN("no key/value separator found on line %d", line_num);return std::nullopt;}tokens[0] = common::StringTrim(std::move(tokens[0]));tokens[1] = common::StringTrim(std::move(tokens[1]));cache.SetProperty(section, tokens[0], std::move(tokens[1]));}}return cache;
}

从指定路径的配置文件中读取配置信息,并将其解析为 ConfigCache 对象。如果文件打开失败、配置文件格式错误,将返回 std::nullopt 表示读取失败;若读取和解析成功,则返回包含配置信息的 ConfigCache 对象。

ConfigCache::SetProperty

packages/modules/Bluetooth/system/gd/storage/config_cache.cc
void ConfigCache::SetProperty(std::string section, std::string property, std::string value) {// 1. 线程同步std::lock_guard<std::recursive_mutex> lock(mutex_);// 2.去除 section、property 和 value 字符串中的换行符TrimAfterNewLine(section);TrimAfterNewLine(property);TrimAfterNewLine(value);ASSERT_LOG(!section.empty(), "Empty section name not allowed");ASSERT_LOG(!property.empty(), "Empty property name not allowed");// 3. 处理非设备节if (!IsDeviceSection(section)) {auto section_iter = information_sections_.find(section);if (section_iter == information_sections_.end()) {section_iter = information_sections_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}// 4. 处理设备节且属性为持久属性的情况auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is setauto section_properties = temporary_devices_.extract(section);if (section_properties) {section_iter = persistent_devices_.try_emplace_back(section, std::move(section_properties->second)).first;} else { //创建一个空的 common::ListMap 用于存储属性和值section_iter = persistent_devices_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}}// 5. 持久设备节属性的加密处理与存储if (section_iter != persistent_devices_.end()) {bool is_encrypted = value == kEncryptedStr;// 值不为空、蓝牙密钥库接口可用、系统处于通用准则模式、属性在加密密钥名称列表中且值未加密if ((!value.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property) && !is_encrypted) {// 对值进行加密if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(section + "-" + property, value)) {value = kEncryptedStr;}}// 将属性和值插入或更新到该节的 common::ListMap 中section_iter->second.insert_or_assign(property, std::move(value));// 通知配置发生更改PersistentConfigChangedCallback();return;}// 6. 处理临时设备节section_iter = temporary_devices_.find(section);if (section_iter == temporary_devices_.end()) {auto triple = temporary_devices_.try_emplace(section, common::ListMap<std::string, std::string>{});section_iter = std::get<0>(triple);}section_iter->second.insert_or_assign(property, std::move(value));
}

在配置缓存中设置指定节(section)下的属性(property)及其对应的值(value)。根据节的类型(是否为设备节)和属性的持久性,将属性和值存储到不同的存储结构中,同时支持在特定条件下对敏感属性值进行加密处理,并在配置发生更改时调用回调函数。

ConfigCache::FixDeviceTypeInconsistencies

packages/modules/Bluetooth/system/gd/storage/config_cache.cc
bool ConfigCache::FixDeviceTypeInconsistencies() {// 1. 线程同步std::lock_guard<std::recursive_mutex> lock(mutex_);bool persistent_device_changed = false;// 2. 遍历信息部分和持久设备部分for (auto* config_section : {&information_sections_, &persistent_devices_}) {for (auto& elem : *config_section) {if (FixDeviceTypeInconsistencyInSection(elem.first, elem.second)) {persistent_device_changed = true;}}}// 3. 遍历临时设备部分bool temp_device_changed = false;for (auto& elem : temporary_devices_) {if (FixDeviceTypeInconsistencyInSection(elem.first, elem.second)) {temp_device_changed = true;}}// 4. 处理持久设备部分的变更if (persistent_device_changed) {PersistentConfigChangedCallback();}return persistent_device_changed || temp_device_changed;
}

修复配置缓存中设备类型的不一致问题。遍历配置缓存中的不同部分(包括信息部分、持久设备部分和临时设备部分),针对每个部分调用 FixDeviceTypeInconsistencyInSection 函数来检查并修复设备类型的不一致。若有任何部分的设备类型发生了改变,会调用 PersistentConfigChangedCallback 函数通知配置发生了持久化变更,最后返回是否有设备类型被修改的结果。

FixDeviceTypeInconsistencyInSection

/packages/modules/Bluetooth/system/gd/storage/config_cache.cc
namespace {bool FixDeviceTypeInconsistencyInSection(const std::string& section_name, common::ListMap<std::string, std::string>& device_section_entries) {// 1. 检查节名是否为有效的蓝牙地址if (!hci::Address::IsValidAddress(section_name)) {return false;}// 2. 处理设备类型为双模蓝牙(DUAL)的情况auto device_type_iter = device_section_entries.find("DevType");if (device_type_iter != device_section_entries.end() &&device_type_iter->second == std::to_string(hci::DeviceType::DUAL)) {// We might only have one of classic/LE keys for a dual device, but it is still a dual device,// so we should not change the DevType.return false;}// 3. 推断设备的实际类型// we will ignore the existing DevType, since it is not known to be a DUAL device so// the keys we have should be sufficient to infer the correct DevTypebool is_le = false;bool is_classic = false;// defaulthci::DeviceType device_type = hci::DeviceType::BR_EDR;for (const auto& entry : device_section_entries) {if (kLePropertyNames.find(entry.first) != kLePropertyNames.end()) {is_le = true;}if (kClassicPropertyNames.find(entry.first) != kClassicPropertyNames.end()) {is_classic = true;}}if (is_classic && is_le) {device_type = hci::DeviceType::DUAL;} else if (is_classic) {device_type = hci::DeviceType::BR_EDR;} else if (is_le) {device_type = hci::DeviceType::LE;}// 4. 检查并更新设备类型bool inconsistent = true; // 表示默认存在不一致std::string device_type_str = std::to_string(device_type);if (device_type_iter != device_section_entries.end()) {inconsistent = device_type_str != device_type_iter->second;if (inconsistent) {device_type_iter->second = std::move(device_type_str);}} else {device_section_entries.insert_or_assign("DevType", std::move(device_type_str));}return inconsistent;
}}  // namespace

修复配置节中设备类型的不一致问题。根据设备配置节中的属性信息,推断出设备的实际类型(如经典蓝牙、低功耗蓝牙或双模蓝牙),并与配置节中已有的设备类型进行比较。如果存在不一致,则更新配置节中的设备类型,最后返回是否进行了更新的标志。

ConfigCache::RemoveSectionWithProperty

packages/modules/Bluetooth/system/gd/storage/config_cache.cc
void ConfigCache::RemoveSectionWithProperty(const std::string& property) {std::lock_guard<std::recursive_mutex> lock(mutex_);size_t num_persistent_removed = 0;// 遍历信息部分和持久设备部分for (auto* config_section : {&information_sections_, &persistent_devices_}) {for (auto it = config_section->begin(); it != config_section->end();) {if (it->second.contains(property)) {LOG_INFO("Removing persistent section %s with property %s", it->first.c_str(), property.c_str());it = config_section->erase(it);num_persistent_removed++;continue;}it++;}}//  遍历临时设备部分for (auto it = temporary_devices_.begin(); it != temporary_devices_.end();) {if (it->second.contains(property)) {LOG_INFO("Removing temporary section %s with property %s", it->first.c_str(), property.c_str());it = temporary_devices_.erase(it);continue;}it++;}// 处理持久设备部分的变更if (num_persistent_removed > 0) {PersistentConfigChangedCallback();}
}

从配置缓存里移除所有包含指定属性的节。遍历配置缓存的不同部分,也就是信息部分、持久设备部分以及临时设备部分,一旦发现某个节包含指定属性,就将该节移除。如果持久设备部分有节被移除,会调用 PersistentConfigChangedCallback 函数来通知配置发生了持久化变更。

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_);//  在信息部分查找属性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;}}//  在持久设备部分查找属性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;}}// 在临时设备部分查找属性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。

SaveDelayed

延迟保存存储模块的配置信息。安排一个延迟任务,在指定的时间后调用 SaveImmediately 函数来实际保存配置。通过这种方式,可以避免频繁地保存配置,减少对存储设备的读写操作,提高性能。

StorageModule::SaveImmediately

/packages/modules/Bluetooth/system/gd/storage/config_cache.cc
void StorageModule::SaveImmediately() {std::lock_guard<std::recursive_mutex> lock(mutex_);//  处理待执行的保存任务if (pimpl_->has_pending_config_save_) {pimpl_->config_save_alarm_.Cancel();pimpl_->has_pending_config_save_ = false;}// 重命名旧的配置文件为备份文件// 1. rename old config to backup nameif (os::FileExists(config_file_path_)) {ASSERT(os::RenameFile(config_file_path_, config_backup_path_));}// 将内存中的配置信息写入主配置文件// 2. write in-memory config to disk, if failed, backup can still be usedASSERT(LegacyConfigFile::FromPath(config_file_path_).Write(pimpl_->cache_));// 将内存中的配置信息写入备份文件// 3. now write back up to disk as wellif (!LegacyConfigFile::FromPath(config_backup_path_).Write(pimpl_->cache_)) {LOG_ERROR("Unable to write backup config file");}// 在特定条件下保存配置文件的校验和// 4. save checksum if it is running in common criteria modeif (bluetooth::os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&bluetooth::os::ParameterProvider::IsCommonCriteriaMode()) {bluetooth::os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(kConfigFilePrefix, kConfigFileHash);}
}

立即将存储模块中的配置信息保存到磁盘上。处理待执行的保存任务、对旧配置文件进行重命名、将内存中的配置信息写入主配置文件和备份文件,并且在满足特定条件时保存配置文件的校验和。

FileExists
packages/modules/Bluetooth/system/gd/os/linux_generic/files.cc
bool FileExists(const std::string& path) {// 尝试打开文件std::ifstream input(path, std::ios::binary | std::ios::ate);return input.good(); // 返回文件流状态
}

input(path, std::ios::binary | std::ios::ate):调用 std::ifstream 的构造函数来尝试打开指定路径的文件。

  • path:要打开的文件的路径。

  • std::ios::binary:以二进制模式打开文件,这样可以避免在读取文件时进行文本模式的转换,确保数据的原始性。

  • std::ios::ate:打开文件后将文件指针定位到文件末尾,ate 是 at end 的缩写。此模式在判断文件是否存在时并非必需,但它允许在后续需要时获取文件的大小。

RenameFile
/packages/modules/Bluetooth/system/gd/os/linux_generic/files.cc
bool RenameFile(const std::string& from, const std::string& to) {if (std::rename(from.c_str(), to.c_str()) != 0) {LOG_ERROR("unable to rename file from '%s' to '%s', error: %s", from.c_str(), to.c_str(), strerror(errno));return false;}return true;
}
LegacyConfigFile::Write
/packages/modules/Bluetooth/system/gd/storage/legacy_config_file.cc
bool LegacyConfigFile::Write(const ConfigCache& cache) {return os::WriteToFile(path_, cache.SerializeToLegacyFormat());
}
WriteToFile
packages/modules/Bluetooth/system/gd/os/linux_generic/files.cc
bool WriteToFile(const std::string& path, const std::string& data) {ASSERT(!path.empty());// Steps to ensure content of data gets to disk://// 1) Open and write to temp file (e.g. bt_config.conf.new).// 2) Flush the stream buffer to the temp file.// 3) Sync the tempReadSmallFile file to disk with fsync().// 4) Rename temp file to actual config file (e.g. bt_config.conf).//    This ensures atomic update.// 5) Sync directory that has the conf file with fsync().//    This ensures directory entries are up-to-date.//// We are using traditional C type file methods because C++ std::filesystem and std::ofstream do not support:// - Operation on directories// - fsync() to ensure content is written to disk// 1. 构建临时文件路径和提取目录路径// Build temp config file based on config file (e.g. bt_config.conf.new).const std::string temp_path = path + ".new"; // 临时文件的路径,在原文件路径后加上 .new// Extract directory from file path (e.g. /data/misc/bluedroid).// libc++fs is not supported in APEX yet and hence cannot use std::filesystem::path::parent_pathstd::string directory_path; // 提取文件路径中的目录部分{// Make a temporary variable as inputs to dirname() will be modified and return value points to input char array// temp_path_for_dir must not be destroyed until results from dirname is appended to directory_pathstd::string temp_path_for_dir(path);directory_path.append(dirname(temp_path_for_dir.data())); //获取目录路径}if (directory_path.empty()) {LOG_ERROR("error extracting directory from '%s', error: %s", path.c_str(), strerror(errno));return false;}// 2. 打开目录文件描述符int dir_fd = open(directory_path.c_str(), O_RDONLY | O_DIRECTORY);if (dir_fd < 0) {LOG_ERROR("unable to open dir '%s', error: %s", directory_path.c_str(), strerror(errno));return false;}// 3. 打开临时文件并写入数据FILE* fp = std::fopen(temp_path.c_str(), "wt");if (!fp) {LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));HandleError(temp_path, &dir_fd, &fp);return false;}if (std::fprintf(fp, "%s", data.c_str()) < 0) {LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));HandleError(temp_path, &dir_fd, &fp);return false;}// 4. 刷新缓冲区并同步到磁盘// Flush the stream buffer to the temp file.if (std::fflush(fp) != 0) {LOG_ERROR("unable to write flush buffer to file '%s', error: %s", temp_path.c_str(), strerror(errno));HandleError(temp_path, &dir_fd, &fp);return false;}// Sync written temp file out to disk. fsync() is blocking until data makes it// to disk.if (fsync(fileno(fp)) != 0) { // 将临时文件同步到磁盘LOG_WARN("unable to fsync file '%s', error: %s", temp_path.c_str(), strerror(errno));// Allow fsync to fail and continue}// 5. 关闭文件并更改文件权限if (std::fclose(fp) != 0) {LOG_ERROR("unable to close file '%s', error: %s", temp_path.c_str(), strerror(errno));HandleError(temp_path, &dir_fd, &fp);return false;}fp = nullptr;// Change the file's permissions to Read/Write by User and Groupif (chmod(temp_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0) {LOG_ERROR("unable to change file permissions '%s', error: %s", temp_path.c_str(), strerror(errno));struct stat dirstat {};if (fstat(dir_fd, &dirstat) == 0) {LOG_ERROR("dir st_mode = 0x%02x", dirstat.st_mode);LOG_ERROR("dir uid = %d", dirstat.st_uid);LOG_ERROR("dir gid = %d", dirstat.st_gid);} else {LOG_ERROR("unable to call fstat on the directory, error: %s", strerror(errno));}struct stat filestat {};if (stat(temp_path.c_str(), &filestat) == 0) {LOG_ERROR("file st_mode = 0x%02x", filestat.st_mode);LOG_ERROR("file uid = %d", filestat.st_uid);LOG_ERROR("file gid = %d", filestat.st_gid);} else {LOG_ERROR("unable to call stat, error: %s", strerror(errno));}HandleError(temp_path, &dir_fd, &fp);return false;}// 6. 重命名临时文件// Rename written temp file to the actual config file.if (std::rename(temp_path.c_str(), path.c_str()) != 0) {LOG_ERROR("unable to commit file from '%s' to '%s', error: %s", temp_path.c_str(), path.c_str(), strerror(errno));HandleError(temp_path, &dir_fd, &fp);return false;}// 7. 同步目录并关闭目录文件描述符// This should ensure the directory is updated as well.if (fsync(dir_fd) != 0) {LOG_WARN("unable to fsync dir '%s', error: %s", directory_path.c_str(), strerror(errno));}if (close(dir_fd) != 0) {LOG_ERROR("unable to close dir '%s', error: %s", directory_path.c_str(), strerror(errno));HandleError(temp_path, &dir_fd, &fp);return false;}return true;
}

通过一系列步骤确保数据能安全地写入磁盘,利用临时文件和重命名操作实现原子更新,同时处理了多种可能出现的错误情况,保证了数据写入的可靠性。具体为:

  1. 把数据写入临时文件。

  2. 把流缓冲区的数据刷新到临时文件。

  3. 运用 fsync() 函数将临时文件同步到磁盘。

  4. 把临时文件重命名为实际的配置文件,以此实现原子更新。

  5. 利用 fsync() 函数同步包含配置文件的目录,保证目录条目是最新的。

set_encrypt_key_or_remove_key

bool set_encrypt_key_or_remove_key(std::string prefix,std::string decryptedString) override {log::verbose("prefix: {}", prefix);if (!callbacks) {log::warn("callback isn't ready. prefix: {}", prefix);return false;}// Save the value into a map.key_map[prefix] = decryptedString;//  在 JNI 线程中调用回调函数do_in_jni_thread(base::BindOnce(&bluetooth::bluetooth_keystore::BluetoothKeystoreCallbacks::set_encrypt_key_or_remove_key,base::Unretained(callbacks), prefix, decryptedString));return true;
}

将一个解密后的字符串与特定前缀关联起来,并保存到一个映射中,同时通过 JNI 线程调用回调函数来进一步处理这个关联信息。用于处理密钥的加密存储或移除操作,根据传入的前缀和已解密的字符串进行相应处理。

JNI: set_encrypt_key_or_remove_key

/packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_BluetoothKeystore.cppvoid set_encrypt_key_or_remove_key(const std::string prefixString,const std::string decryptedString) override {log::info("");std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;jstring j_prefixString = sCallbackEnv->NewStringUTF(prefixString.c_str());jstring j_decryptedString =sCallbackEnv->NewStringUTF(decryptedString.c_str());// 调用 Java 层的回调方法sCallbackEnv->CallVoidMethod(mCallbacksObj,method_setEncryptKeyOrRemoveKeyCallback,j_prefixString, j_decryptedString);}

将 C++ 层的 prefixString 和 decryptedString 传递到 Java 层的回调方法中。通过 JNI 环境创建对应的 Java 字符串对象,然后调用 Java 层的 setEncryptKeyOrRemoveKeyCallback 方法,完成从 C++ 到 Java 的跨语言交互。

setEncryptKeyOrRemoveKeyCallback

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreNativeInterface.java// Callbacks from the native stack back into the Java framework.// All callbacks are routed via the Service which will disambiguate which// state machine the message should be routed to.private void setEncryptKeyOrRemoveKeyCallback(String prefixString, String decryptedString) {final BluetoothKeystoreService service = mBluetoothKeystoreService;if (service == null) {Log.e(TAG,"setEncryptKeyOrRemoveKeyCallback: Event ignored, service not available: "+ prefixString);return;}try {service.setEncryptKeyOrRemoveKey(prefixString, decryptedString);} catch (InterruptedException e) {Log.e(TAG, "Interrupted while operating.");} catch (IOException e) {Log.e(TAG, "IO error while file operating.");} catch (NoSuchAlgorithmException e) {Log.e(TAG, "encrypt could not find the algorithm: SHA256");}}

从本地(Native)层回调到 Java 框架层的接口。当本地层调用 set_encrypt_key_or_remove_key 方法并通过 JNI 将信息传递到 Java 层时,会触发这个回调方法。主要功能是将接收到的 prefixString 和 decryptedString 传递给 BluetoothKeystoreService 进行进一步处理。

setEncryptKeyOrRemoveKey

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java/*** Sets or removes the encryption key value.** <p>If the value of decryptedString matches {@link #CONFIG_FILE_HASH} then* read the hash file and decrypt the keys and place them into {@link mPendingEncryptKey}* otherwise cleanup all data and remove the keys.** @param prefixString key to use* @param decryptedString string to decrypt*/public void setEncryptKeyOrRemoveKey(String prefixString, String decryptedString)throws InterruptedException, IOException, NoSuchAlgorithmException {infoLog("setEncryptKeyOrRemoveKey: prefix: " + prefixString);if (prefixString == null || decryptedString == null) {return;}if (prefixString.equals(CONFIG_FILE_PREFIX)) {if (decryptedString.isEmpty()) {cleanupAll();} else if (decryptedString.equals(CONFIG_FILE_HASH)) {readHashFile(CONFIG_FILE_PATH, CONFIG_FILE_PREFIX);mPendingEncryptKey.put(CONFIG_FILE_PREFIX);readHashFile(CONFIG_BACKUP_PATH, CONFIG_BACKUP_PREFIX);mPendingEncryptKey.put(CONFIG_BACKUP_PREFIX);saveEncryptedKey();}return;}if (decryptedString.isEmpty()) {// clear the item by prefixString.mNameDecryptKey.remove(prefixString);mNameEncryptKey.remove(prefixString);} else {mNameDecryptKey.put(prefixString, decryptedString);mPendingEncryptKey.put(prefixString);}}

根据传入的 prefixString(键)和 decryptedString(待解密的字符串)来设置或移除加密密钥值。根据不同的条件进行不同的操作,例如清空数据、读取哈希文件、保存加密密钥等

BtifConfigInterface::ConvertEncryptOrDecryptKeyIfNeeded

/packages/modules/Bluetooth/system/main/shim/config.cc
void BtifConfigInterface::ConvertEncryptOrDecryptKeyIfNeeded() {GetStorage()->ConvertEncryptOrDecryptKeyIfNeeded();
}

StorageModule::ConvertEncryptOrDecryptKeyIfNeeded

void StorageModule::ConvertEncryptOrDecryptKeyIfNeeded() {std::lock_guard<std::recursive_mutex> lock(mutex_);pimpl_->cache_.ConvertEncryptOrDecryptKeyIfNeeded();
}

ConfigCache::ConvertEncryptOrDecryptKeyIfNeeded

void ConfigCache::ConvertEncryptOrDecryptKeyIfNeeded() {std::lock_guard<std::recursive_mutex> lock(mutex_);LOG_INFO("%s", __func__);//  获取持久设备的节auto persistent_sections = GetPersistentSections();// 遍历持久设备的节for (const auto& section : persistent_sections) {auto section_iter = persistent_devices_.find(section);//  遍历需要加密或解密的属性列表for (const auto& property : kEncryptKeyNameList) {auto property_iter = section_iter->second.find(std::string(property));if (property_iter != section_iter->second.end()) {// 加密操作bool is_encrypted = property_iter->second == kEncryptedStr;if ((!property_iter->second.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&os::ParameterProvider::IsCommonCriteriaMode() && !is_encrypted) {// 对属性值进行加密if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(section + "-" + std::string(property), property_iter->second)) {// 表示该属性已加密SetProperty(section, std::string(property), kEncryptedStr);}}//  解密操作if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && is_encrypted) {// 获取解密后的字符串 value_strstd::string value_str =os::ParameterProvider::GetBtKeystoreInterface()->get_key(section + "-" + std::string(property));if (!os::ParameterProvider::IsCommonCriteriaMode()) {SetProperty(section, std::string(property), value_str); // 将该属性的值更新为解密后的字符串}}}}}
}

对配置缓存中持久设备部分的指定属性进行加密或解密操作。遍历持久设备的各个节,检查每个节中特定属性的加密状态,并根据系统配置和属性值的情况决定是否进行加密或解密,然后更新配置缓存中的属性值。

总结

  1. 初始化流程:处理工厂重置、校验配置文件,优先加载主配置,失败则使用备份或新建空配置。

  2. 配置解析:逐行读取配置文件,区分设备节与非设备节,加密敏感属性(如链接密钥),支持动态回调通知变更。

  3. 设备类型修复:通过属性特征推断设备类型(经典/低功耗/双模),自动修正配置不一致。

  4. 加密管理:在通用准则模式下,对持久化属性加密存储,支持按需解密或密钥移除。

  5. 延迟保存机制:合并频繁的配置变更操作,定时批量写入主备份文件,减少I/O开销。

流程图

相关文章:

【Bluedroid】蓝牙存储模块配置管理:启动、读写、加密与保存流程解析

本文围绕蓝牙存储模块展开&#xff0c;主要解析了蓝牙存储模块&#xff08;StorageModule&#xff09;的初始化流程&#xff0c;重点围绕配置文件校验、读取、设备类型修复及加密处理展开。通过工厂重置检测、校验和验证、多源配置加载、设备类型推断修正等步骤&#xff0c;确保…...

SpringBoot启动后初始化的几种方式

目录 一、静态代码块 二、构造方法 三、PostConstruct 四、InitializingBean 接口 五、 Bean 注解中的 initMethod 六、 CommandLineRunner 接口 七、ApplicationRunner 接口 八、EventListener事件 九、SmartInitializingSingleton接口 十、ApplicationListener接口…...

asp.net core webapi+efcore

简洁的restfull风格 目前c#提供了多种风格的web编程&#xff0c;因为微软有自己的前端&#xff0c;所以集成了很多内容&#xff0c;不过基于现在编程前后端分离的模式&#xff0c;webapi是合适的。 webapi 目前网络上有很多介绍&#xff0c;不反复说这个了。在建立控制器时&…...

java怎么完善注册,如果邮箱中途更换,能否判断

解析在下面 附赠代码 private static class CodeInfo {String code;long timestamp;CodeInfo(String code, long timestamp) {this.code code;this.timestamp timestamp;}}// 存储验证码&#xff08;邮箱 -> 验证码信息&#xff09;(保证线程安全) 以免中途更改邮箱pri…...

实战设计模式之备忘录模式

概述 与解释器模式、迭代器模式一样&#xff0c;备忘录模式也是一种行为设计模式。备忘录模式允许我们保存一个对象的状态&#xff0c;并在稍后恢复到这个状态。该模式非常适合于需要回滚、撤销或历史记录等功能的应用场景。通过使用备忘录模式&#xff0c;开发者可以轻松添加诸…...

数据库表设计

一对一关系 共享主键 两个表的主键是相同的 唯一外键 从表中记录主表的id 一对多关系 从表&#xff08;多的表&#xff09;存储主表的id 多对多关系 设计一个中间表&#xff08;关联表&#xff09;&#xff0c;它有两列分别记录两个主表&#xff08;A 和 B&#xff09;…...

Linux 桌面环境 LXQt 2.2 发布

Linux 桌面环境 LXQt 2.2 于 2025 年 4 月 17 日正式发布。这是该轻量级开源 Qt 桌面环境的最新稳定版本&#xff0c;带来了诸多改进&#xff0c;特别是在 Wayland 支持方面。以下是一些主要的更新内容&#xff1a; Wayland 支持增强&#xff1a; 提升了多屏支持&#xff0c;使…...

多人五子棋联机对战平台 测试报告

目录 项目介绍 测试用例设计 部分功能测试示例 自动化测试 测试范围 排除范围 自动化测试目录​编辑 执行全部自动化测试用例 性能说明 总结 性能测试 结果分析 测试总结 项目介绍 该项目基于WebSocket实现实时通信&#xff0c;采用SSM框架构建在线五子棋多人联机…...

探索 .bat 文件:自动化任务的利器

在现代计算机操作中&#xff0c;批处理文件&#xff08;.bat 文件&#xff09;是一种简单而强大的工具&#xff0c;它可以帮助我们自动化重复性任务&#xff0c;工作效率提高。尽管随着编程语言和脚本工具的发展&#xff0c;.bat 文件的使用频率有所下降&#xff0c;但它依然是…...

240419 leetcode exercises

240419 leetcode exercises jarringslee 文章目录 240419 leetcode exercises[19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)&#x1f501; 经典方法&#xff1a;两次遍历暴力求解&#x1f501; 双指针法 &#xff1a;快慢…...

开源Midjourney替代方案:企业级AI绘画+PPT生成系统+AI源码

「AI取代设计师&#xff1f;」开源Midjourney替代方案&#xff1a;企业级AI绘画PPT生成系统 ——零代码私有化部署&#xff0c;5倍速出图100%版权合规 设计师行业的危机与机遇 1. 传统设计流程的致命短板 痛点 人工设计 AI系统 单张海报耗时 3小时&#xff08;含反复修改…...

学习笔记十七——Rust 支持面向对象编程吗?

&#x1f9e0; Rust 支持面向对象编程吗&#xff1f; Rust 是一门多范式语言&#xff0c;主要以 安全、并发、函数式、系统级编程为核心目标&#xff0c;但它同时也支持面向对象的一些关键特性&#xff0c;比如&#xff1a; 特性传统 OOP&#xff08;如 Java/C&#xff09;Ru…...

图灵奖得主LeCun:DeepSeek开源在产品层是一种竞争,但在基础方法层更像是一种合作;新一代AI将情感化

图片来源&#xff1a;This is World 来源 | Z Potential Z Highlights&#xff1a; 新型的AI系统是以深度学习为基础&#xff0c;能够理解物理世界并且拥有记忆、推理和规划能力的。一旦成功构建这样的系统&#xff0c;它们可能会有类似情感的反应&#xff0c;但这些情感是基…...

Flink框架十大应用场景

Flink框架适合应用的场景 1. 流式数据处理 Flink框架最常用的应用场景是流式数据处理。流式数据处理是指对实时数据进行处理,以便及时地做出决策。例如,一个电商网站需要对用户的行为进行实时分析,以便根据用户的兴趣和行为推荐商品。Flink框架可以帮助电商网站实时地处理数…...

C++镌刻数据密码的树之铭文:二叉搜索树

文章目录 1.二叉搜索树的概念2.二叉搜索树的实现2.1 二叉搜索树的结构2.2 二叉搜索树的节点寻找2.2.1 非递归2.2.2 递归 2.3 二叉搜索树的插入2.3.1 非递归2.3.2 递归 2.4 二叉搜索树的删除2.4.1 非递归2.4.2 递归 2.5 二叉搜索树的拷贝 3.二叉树的应用希望读者们多多三连支持小…...

CAN与CANFD协议说明

在 CAN&#xff08;Controller Area Network&#xff0c;控制器局域网&#xff09;协议里&#xff0c;仲裁域波特率和数据域比特率有着不同的含义和作用&#xff0c;下面为你详细介绍并举例说明。 概念解释 仲裁域波特率 含义&#xff1a;仲裁域是 CAN 数据帧中的一部分&…...

【C++ Qt】信号和槽(内配思维导图 图文并茂 通俗易懂)

每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” 绪论​&#xff1a; 本章是Qt中的第三章&#xff0c;也是我们理解Qt中必备的点 信号槽&#xff0c;它本质由信号和槽两个来实现&#xff0c;其中将细致的讲述如何自定义信号…...

【实战】在 Linux 上使用 Nginx 部署 Python Flask 应用

在 Linux 上使用 Nginx 部署 Python Flask 应用 步骤一&#xff1a;准备 Flask 应用 创建 Flask 应用 确保你有一个可以运行的 Flask 应用。例如&#xff0c;创建一个简单的 app.py 文件&#xff1a; from flask import Flask app Flask(__name__)app.route(/) def hello_wor…...

java ai 图像处理

Java AI 图像处理 图像处理是人工智能&#xff08;AI&#xff09;领域中非常重要的一个应用方向。通过使用Java编程语言和相应的库&#xff0c;我们可以实现各种图像处理任务&#xff0c;如图像识别、图像分类、图像分割等。本文将介绍一些常见的图像处理算法&#xff0c;并通过…...

【绘制图像轮廓】图像处理(OpenCV) -part7

15 绘制图像轮廓 15.1 什么是轮廓 轮廓是一系列相连的点组成的曲线&#xff0c;代表了物体的基本外形。相对于边缘&#xff0c;轮廓是连续的&#xff0c;边缘不一定连续&#xff0c;如下图所示。轮廓是一个闭合的、封闭的形状。 轮廓的作用&#xff1a; 形状分析 目标识别 …...

Mesh模型孔洞修补算法总汇

关于Mesh 孔洞修补算法&#xff08;Hole Filling in Meshes&#xff09;&#xff0c;这是计算几何和图形学中的一个重要话题&#xff0c;常用于重建、3D 扫描、建模等领域。下面我会系统总结主流和经典的孔洞修补方法&#xff0c;并按技术路线分类说明每种的原理、优缺点&#…...

ARINC818协议(六)

上图中&#xff0c;红色虚线上面为我们常用的simple mode简单模式&#xff0c;下面和上面的结合在一起&#xff0c;就形成了extended mode扩展模式。 ARINC818协议 container header容器头 ancillary data辅助数据 视频流 ADVB帧映射 FHCP传输协议 R_CTRL:路由控制routing ctr…...

RTMP握手流程

RTMP&#xff08;Real-Time Messaging Protocol&#xff09; 不支持除H.264/AAC之外的标准。 使用TCP,当到达网络拥塞、宽带上限时&#xff0c;传输质量受到影响。 URL格式&#xff1a; rtmp://host:port/app&#xff08;名称&#xff09;/stream&#xff08;流ID&#xff…...

【解决】torch引入过程中的ImportError: __nvJitLinkAddData_12_1, version libnvJitLink.so.12

大纲 本文记录在环境配置好后&#xff0c;在 import torch 过程中报了 异常 ImportError: /home/Coding/Envs/envs/only_test/lib/python3.10/site-packages/torch/lib/../../nvidia/cusparse/lib/libcusparse.so.12: undefined symbol: __nvJitLinkAddData_12_1, version lib…...

面试招聘:新能源汽车研发测试人员需求内部研讨会纪要(2025年4月19日草稿流出)

标题&#xff1a;XX汽车技术中心技术管理岗闭门会议纪要完整版&#xff1a;双非招聘策略、面试话术与风控方案&#xff08;附待定事项&#xff09; 【内部密级文件】 时间&#xff1a;2025年4月15日 14:00-17:30 地点&#xff1a;某主机厂研发中心会议室&#xff08;305&#…...

从零开始学习 Lucene.Net:.NET Core 中的全文搜索与索引管理

Lucene.Net 是一个开源的全文搜索引擎库&#xff0c;它是 Apache Lucene 项目的 .NET 移植版本。Lucene.Net 提供了强大的搜索功能&#xff0c;广泛应用于文档搜索、日志分析、数据检索等场景。随着大数据的爆发&#xff0c;开发者越来越依赖高效的搜索引擎来实现复杂的搜索需求…...

opencv--图像处理

图像处理技术 图像处理技术是利用计算机对图像进行计算,分析和处理的技术,包括数字图像处理和计算机视觉两大领域。 对图像的处理包括滤波,缩放,分割,识别(两种信息对比)等。 链接 数字图像处理 1. 数字图像处理(Digital Image Processing) 数字图像处理主要关注图…...

JCST 2025年 区块链论文 录用汇总

Conference&#xff1a;Journal of Computer Science and Technology (JCST) CCF level&#xff1a;CCF B Categories&#xff1a;交叉/综合/新兴 Year&#xff1a;2025&#xff08;截止4.19&#xff09; JCST 2024年 区块链论文 录用汇总 1 Title: An Understandable Cro…...

股指期货跨期套利是如何赚取价差利润的?

股指期货跨期套利&#xff0c;简单来说&#xff0c;就是在同一交易所内&#xff0c;针对同一股指期货品种的不同交割月份合约进行的套利交易。投资者会同时买入某一月份的股指期货合约&#xff0c;并卖出另一月份的股指期货合约&#xff0c;待未来某个时间点&#xff0c;再将这…...

【java实现+4种变体完整例子】排序算法中【冒泡排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格

以下是冒泡排序的详细解析&#xff0c;包含基础实现、常见变体的完整代码示例&#xff0c;以及各变体的对比表格&#xff1a; 一、冒泡排序基础实现 原理 通过重复遍历数组&#xff0c;比较相邻元素并交换逆序对&#xff0c;逐步将最大值“冒泡”到数组末尾。 代码示例 pu…...

毕业论文超清pdf带标签导出

Word直接导出的pdf不够清晰&#xff0c;使用打印导出的pdf又不带书签以及目录跳转功能这一问题&#xff0c;查阅网上资料使用Adobe DC似乎能够解决但是下载安装比较麻烦&#xff0c;于是写了python程序解决该问题。 解决思路&#xff1a; 使用python脚本对两个pdf文件进行合并…...

STM32单片机入门学习——第43节: [12-3] 读写备份寄存器实时时钟

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.19 STM32开发板学习——第43节: [12-3] 读写备份寄存器&实时时钟 前言开发板说明…...

筛选法(埃氏筛法)C++

判断N个数是否质数 输入N个整数M&#xff0c;判断它们是否为质数。如果是输出“YES”&#xff0c;否则输出“NO”。&#xff08;1<n<10000&#xff09; 输入格式 第一行为N&#xff0c;第2&#xff5e;n1行每行为一个正整数M。&#xff08;1<M<1000000&#xff09;…...

PointCore——利用局部全局特征的高效无监督点云异常检测器论文与算法解读

概述 三维点云异常检测旨在从训练集中检测出异常数据点&#xff0c;是工业检测、自动驾驶等众多应用的基础。然而&#xff0c;现有的点云异常检测方法通常采用多个特征存储库来充分保留局部和全局特征表示&#xff0c;这带来了高昂的计算成本以及特征之间的不匹配问题。为解决…...

洛谷P1177【模板】排序:十种排序算法全解(1)

扯谈 之前我已经把十大排序算法全讲了一遍&#xff08;具体详见专栏C排序算法&#xff09;,今天我们来用一道简单的题目总结实战一下。 算法实现 一、桶排序&#xff08;Bucket Sort&#xff09; ‌适用场景‌&#xff1a;数据范围已知且较小&#xff08;需根据测试数据调整…...

Graham Scan算法求解二维凸包

一、凸包及其概念 凸包&#xff08;Convex Hull&#xff09;是计算几何中的一个重要概念。在一个实数向量空间中&#xff0c;对于给定的点集&#xff0c;凸包是指包含这些点的最小凸多边形。在二维平面上&#xff0c;凸包可以形象地理解为用一个橡皮圈将所有点紧紧包裹起来&am…...

【java实现+4种变体完整例子】排序算法中【希尔排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格

以下是希尔排序的详细解析&#xff0c;包含基础实现、常见变体的完整代码示例&#xff0c;以及各变体的对比表格&#xff1a; 一、希尔排序基础实现 原理 希尔排序是插入排序的改进版本&#xff0c;通过分步缩小增量间隔&#xff0c;将数组分成多个子序列进行插入排序&#…...

【文件操作与IO】详细解析文件操作与IO (二)

本篇博客是上一篇文章的续写,重点介绍数据流,还包括三道练习题. &#x1f40e;文章专栏: JavaEE初阶 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f680; 要开心…...

【java实现+4种变体完整例子】排序算法中【基数排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格

基数排序详解及代码示例 基数排序原理 基数排序通过处理每一位数字进行排序&#xff0c;分为 LSD&#xff08;最低位优先&#xff09; 和 MSD&#xff08;最高位优先&#xff09; 两种方式。核心步骤&#xff1a; 确定最大值&#xff1a;计算数组中最大数的位数。逐位排序&am…...

Java中的函数式编程详解

Java中的函数式编程是一个在Java 8中引入的特性&#xff0c;它将计算视为数学函数的求值&#xff0c;避免使用可变状态和数据。其核心特性包括Lambda表达式、函数式接口和Stream API。以下将结合代码示例和具体场景详细讲解这些特性。 1. Lambda表达式 Lambda表达式是Java 8引…...

专精特新政策推动,B端UI设计如何赋能中小企业创新发展?

在当前数字化转型浪潮下&#xff0c;专精特新政策为中小企业提供了强大的支持&#xff0c;助力其在细分领域实现专业化、精细化、特色化和创新化发展。B端UI设计作为提升企业数字化产品用户体验和工作效率的重要手段&#xff0c;能够有效赋能中小企业创新发展。本文将探讨专精特…...

从零开始学A2A四:A2A 协议的高级应用与优化

A2A 协议的高级应用与优化 学习目标 掌握 A2A 高级功能 理解多用户支持机制掌握长期任务管理方法学习服务性能优化技巧 理解与 MCP 的差异 分析多智能体场景下的优势掌握不同场景的选择策略 第一部分&#xff1a;多用户支持机制 1. 用户隔离架构 #mermaid-svg-6SCFaVO4oDU…...

海关总署广东:广东外贸一季度进出口2.14万亿元 同期增长4.2%

大湾区经济网湾区财经报道&#xff0c;据海关总署广东分署统计&#xff0c;今年一季度&#xff0c;广东外贸进出口2.14万亿元&#xff0c;较去年同期&#xff08;下同&#xff09;增长4.2%&#xff0c;增速高于全国2.9个百分点。其中&#xff0c;出口1.34万亿元&#xff0c;增长…...

C++代码优化

前段时间写了一些代码&#xff0c;但是在运算过程中发现有些代码可以进行改进以提高运行效率&#xff0c;尤其是与PCL相关的部分&#xff0c;可以进行大幅度提高&#xff0e;特意在此进行记录&#xff0c;分享给大家&#xff0c;也供自己查看&#xff0e; pcl::PointCloud< …...

Manim教程:第七章 坐标系统

#什么是坐标系统?特点是什么? 坐标系统是一个用于确定空间中点位置的数学工具。它通过一组数值(坐标)来描述一个点在某个空间中的位置。不同类型的坐标系统可以用于不同的应用场景,最常见的包括: 笛卡尔坐标系:使用直角坐标系,通常用坐标轴(如x轴和y轴)来表示二维空间…...

U盘实现——双盘符实现

文章目录 双盘符实现描述符类特殊命名get max luninquiry上一篇文章中介绍了 U 盘的枚举过程 U盘实现——U 盘枚举过程 双盘符实现 描述符 双盘符的时候中,描述符的实现与上节完全一致,不同的只有类特殊命令 设备描述符配置描述符接口描述符输出端点描述符输入端点描述符上…...

【Linux】【阿里云服务器】【树莓派】学习守护进程编程、gdb调试原理和内网穿透信息

目录 一. 守护进程的含义及编程实现的主要过程 1.1守护进程 1.2编程实现的主要过程 二、在树莓派中通过三种方式创建守护进程 2.1nohup命令创建 2.2fork()函数创建 2.3daemon()函数创建 三、在阿里云中通过三种方式创建守护进程 3.1nohup命令创建 3.2fork()函数创建 …...

2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(二级)答案 + 解析

青少年软件编程(Python)等级考试试卷(二级) 分数:100 题数:37 一、单选题(共25题,共50分) 1. 老师要求大家记住四大名著的作者,小明机智地想到了可以用字典进行记录,以下哪个选项的字典格式是正确?( ) A. [‘曹雪芹’:‘红楼梦’, ‘吴承恩’:‘西游记’, ‘罗贯…...

【Linux系统篇】:System V IPC核心技术解析---从共享内存到消息队列与信号量

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;c篇–CSDN博客 文章目录 一.System V共享内存&#xff08;重点&#xff09;1.基本概念和原理…...

关于GPU的涡轮散热与被动散热

显卡涡轮散热与被动散热的深度解析 一、涡轮散热的定义与工作原理 涡轮散热技术是通过高速旋转的涡轮风扇配合封闭式风道设计,将冷空气吸入并强制排出热量的主动散热方案。其核心原理包含以下关键点: 气流动力学设计:涡轮风扇采用精密叶片(如离心式结构),在相同尺寸下能…...