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

记录NVIDIA Orin启动流程,镜像文件,AB双分区,ota升级

一、Orin 核心镜像文件

在 NVIDIA Orin 平台开发中,构建完整系统镜像时会生成一系列核心镜像文件,涵盖启动引导、内核、根文件系统及辅助组件。以下是主要镜像及其作用:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以下是 NVIDIA Orin 开发中构建完整系统时常见的核心镜像文件清单,按功能分类列出,涵盖引导、内核、根文件系统及烧录相关组件:

在这里插入图片描述
在这里插入图片描述
除了这些orin分区,还有很多客户自定义的分区

二、cboot镜像的作用

CBoot 是 NVIDIA 为 Jetson 系列平台(包括 Orin、Xavier、TX2 等)开发的专有引导程序,全称为 Core Bootloader,是系统启动流程中的关键环节。它介于底层硬件初始化程序(如 MB1、MB2)和上层引导程序(如 U-Boot)或内核之间,负责完成系统启动的核心初始化工作。。

在这里插入图片描述
每次换ddr型号时,需要找NVIDIA合作,修改这部分代码

在这里插入图片描述

启动顺序

硬件上电 → MB1(第一阶段初始化) → MB2(第二阶段初始化) → CBoot → U-Boot / 内核 → 根文件系统

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

三、ABF分区ota升级

NVIDIA Jetson Orin 的 OTA 升级机制基于 Linux for Tegra (L4T) 系统的设计,结合了双分区(A/B Partition)、引导程序控制和用户空间工具,整体流程如下:

在这里插入图片描述

在这里插入图片描述

软件触发(主流方式)
OTA 升级失败或需要强制恢复时,可通过用户空间程序设置标志位(如写入 /proc 或特定分区),引导程序(CBoot)下次启动时检测到标志,自动进入 recovery 模式(加载 recovery 镜像,执行修复逻辑)

在这里插入图片描述

其实升级过程,烧录完毕后,会写入结构体flag,l类似下面
stuct
{
partA = active
partB = active

successA = true
successB = true
}

当处于a,升级b时,会重新激活b,partB = active,successB = false,然后重启,完成首次启动,按流程启动kernel,init启动后,重新successB = true,置为true,如若超时未启动kernel,rootfs,则置为false,显示更新失败。
会重新激活partA = active,重启,cboot判断从a启动。

在这里插入图片描述

下面是nvidia平台,ota开发流程记录,依赖这些命令,nvidia以及编译提供了
在这里插入图片描述

#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>// 日志工具
#define LOG_INFO(msg) std::cout << "[INFO] " << msg << std::endl
#define LOG_ERROR(msg) std::cerr << "[ERROR] " << msg << std::endl// 执行系统命令并返回结果
std::string exec_command(const char* cmd) {char buffer[128];std::string result = "";FILE* pipe = popen(cmd, "r");if (!pipe) {return "ERROR";}while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {result += buffer;}pclose(pipe);return result;
}// 1. 获取当前激活槽位(A/B)
char get_active_slot() {std::string output = exec_command("bootctrl get-active-boot-slot");if (output.empty() || output.find("ERROR") != std::string::npos) {LOG_ERROR("Failed to get active slot");return ' ';}return output[0]; // 返回 'A' 或 'B'
}// 2. 下载 OTA 升级包
bool download_ota_package(const std::string& url, const std::string& save_path) {LOG_INFO("Downloading OTA package from: " << url);std::string cmd = "curl -f -o " + save_path + " " + url; // -f 失败时返回非0int ret = system(cmd.c_str());if (ret != 0) {LOG_ERROR("Download failed");return false;}LOG_INFO("Download completed: " << save_path);return true;
}// 3. 验证 OTA 包签名(使用 openssl)
bool verify_ota_signature(const std::string& ota_path, const std::string& pub_key_path) {LOG_INFO("Verifying OTA package signature");std::string cmd = "openssl dgst -sha256 -verify " + pub_key_path + " -signature " + ota_path + ".sig " + ota_path;int ret = system(cmd.c_str());if (ret != 0) {LOG_ERROR("Signature verification failed");return false;}LOG_INFO("Signature verified");return true;
}// 4. 刷写非活动槽位(调用 l4t-ota)
bool flash_inactive_slot(const std::string& ota_path, char target_slot) {LOG_INFO("Flashing target slot: " << target_slot);std::string cmd = "sudo l4t-ota --flash-only --target_slot " + std::string(1, target_slot) + " " + ota_path;int ret = system(cmd.c_str());if (ret != 0) {LOG_ERROR("Flash failed for slot " << target_slot);return false;}LOG_INFO("Flash completed for slot " << target_slot);return true;
}// 5. 设置下次启动槽位
bool set_boot_slot(char target_slot) {LOG_INFO("Setting next boot slot to: " << target_slot);std::string cmd = "sudo bootctrl set-active-boot-slot " + std::string(1, target_slot);int ret = system(cmd.c_str());if (ret != 0) {LOG_ERROR("Failed to set boot slot");return false;}// 设置重试次数(最多3次)system("sudo bootctrl set-retries 3");return true;
}// 6. 重启设备
void reboot_device() {LOG_INFO("Rebooting to apply update...");system("sudo reboot");
}// 7. 新系统启动后验证(首次启动时执行)
bool verify_new_system() {LOG_INFO("Verifying new system...");// 检查关键服务(示例:检查 nvargus-daemon 是否运行)std::string output = exec_command("pgrep nvargus-daemon");if (output.empty()) {LOG_ERROR("Critical service not running");// 标记当前槽位启动失败system("sudo bootctrl mark-boot-failed");return false;}// 标记当前槽位启动成功system("sudo bootctrl mark-successful");LOG_INFO("New system verified successfully");return true;
}int main(int argc, char* argv[]) {// 检查权限(需要 root)if (getuid() != 0) {LOG_ERROR("This program requires root privileges");return 1;}// 配置参数std::string ota_url = "http://your-server/ota_v2.0.zip"; // OTA包URLstd::string ota_local_path = "/tmp/ota_v2.0.zip";       // 本地保存路径std::string pub_key = "/etc/nvidia/ota_pub.key";        // 公钥路径// 步骤1:获取当前激活槽位char current_slot = get_active_slot();if (current_slot != 'A' && current_slot != 'B') {return 1;}char target_slot = (current_slot == 'A') ? 'B' : 'A';LOG_INFO("Current slot: " << current_slot << ", Target slot: " << target_slot);// 步骤2:下载OTA包if (!download_ota_package(ota_url, ota_local_path)) {return 1;}// 步骤3:验证签名if (!verify_ota_signature(ota_local_path, pub_key)) {remove(ota_local_path.c_str()); // 删除无效包return 1;}// 步骤4:刷写目标槽位if (!flash_inactive_slot(ota_local_path, target_slot)) {return 1;}// 步骤5:设置启动槽位if (!set_boot_slot(target_slot)) {return 1;}// 步骤6:重启(新系统启动后会执行 verify_new_system)reboot_device();return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
NVIDIA Jetson 的 OTA 升级通过命令行工具链实现,开发者无需直接调用底层 API,而是通过封装这些工具构建客户端。上述代码涵盖了完整流程,可根据实际需求扩展(如添加断点续传、差分升级、日志上报等功能)。核心是利用 l4t-ota 处理镜像刷写,bootctrl 管理槽位状态,确保升级安全与可回滚。

在这里插入图片描述

#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <chrono>
#include <ctime>
#include <sstream>
#include <curl/curl.h>// 日志级别
enum LogLevel {LOG_DEBUG,LOG_INFO,LOG_WARNING,LOG_ERROR
};// 全局配置
struct OtaConfig {std::string ota_url;               // OTA完整包URLstd::string diff_url;              // 差分包URLstd::string local_ota_path;        // 本地OTA包路径std::string local_diff_path;       // 本地差分包路径std::string pub_key_path;          // 公钥路径std::string log_server_url;        // 日志上报服务器std::string current_version;       // 当前系统版本std::string target_version;        // 目标系统版本int max_retries = 3;               // 最大重试次数int log_level = LOG_INFO;          // 日志级别
};// 全局配置实例
OtaConfig config;// 日志回调函数
typedef void (*LogCallback)(LogLevel level, const std::string& message);
LogCallback log_callback = nullptr;// 断点续传进度回调
struct DownloadProgress {std::string filename;off_t total_size;off_t downloaded_size;std::chrono::time_point<std::chrono::steady_clock> start_time;
};// 日志输出到控制台并保存到文件
void default_log_callback(LogLevel level, const std::string& message) {// 获取当前时间auto now = std::chrono::system_clock::now();std::time_t now_time = std::chrono::system_clock::to_time_t(now);std::string time_str = std::ctime(&now_time);time_str.pop_back(); // 移除换行符// 日志级别字符串std::string level_str;switch (level) {case LOG_DEBUG: level_str = "DEBUG"; break;case LOG_INFO: level_str = "INFO"; break;case LOG_WARNING: level_str = "WARNING"; break;case LOG_ERROR: level_str = "ERROR"; break;}// 格式化日志std::string log_msg = "[" + time_str + "] [" + level_str + "] " + message;// 输出到控制台if (level >= config.log_level) {if (level == LOG_ERROR) {std::cerr << log_msg << std::endl;} else {std::cout << log_msg << std::endl;}}// 保存到本地日志文件std::ofstream log_file("ota_update.log", std::ios::app);if (log_file.is_open()) {log_file << log_msg << std::endl;log_file.close();}
}// 日志上报函数
void report_log_to_server(const std::string& message) {if (config.log_server_url.empty()) return;CURL* curl = curl_easy_init();if (curl) {std::string post_data = "message=" + message + "&device_id=" + exec_command("cat /proc/cpuinfo | grep Serial | awk '{print $3}'") +"&version=" + config.current_version;curl_easy_setopt(curl, CURLOPT_URL, config.log_server_url.c_str());curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str());CURLcode res = curl_easy_perform(curl);if (res != CURLE_OK) {log_callback(LOG_WARNING, "Failed to report log: " + std::string(curl_easy_strerror(res)));}curl_easy_cleanup(curl);}
}// 带级别和上报的日志函数
void log(LogLevel level, const std::string& message, bool report = false) {if (log_callback) {log_callback(level, message);}// 如果需要上报且是重要日志,则上报到服务器if (report && (level >= LOG_WARNING)) {report_log_to_server(message);}
}// 执行系统命令并返回结果
std::string exec_command(const char* cmd) {char buffer[128];std::string result = "";FILE* pipe = popen(cmd, "r");if (!pipe) {log(LOG_ERROR, "Failed to execute command: " + std::string(cmd), true);return "ERROR";}while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {result += buffer;}pclose(pipe);return result;
}// 获取文件大小
off_t get_file_size(const std::string& filename) {struct stat file_info;if (stat(filename.c_str(), &file_info) == 0) {return file_info.st_size;}return 0;
}// 断点续传进度回调函数
static int download_progress_callback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {DownloadProgress* progress = (DownloadProgress*)clientp;progress->total_size = dltotal;progress->downloaded_size = dlnow;// 每5秒打印一次进度auto now = std::chrono::steady_clock::now();auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - progress->start_time).count();if (elapsed % 5 == 0) {if (dltotal > 0) {int percent = (int)((dlnow * 100) / dltotal);log(LOG_INFO, "Downloading " + progress->filename + ": " + std::to_string(percent) + "% (" + std::to_string(dlnow / (1024 * 1024)) + "MB / " + std::to_string(dltotal / (1024 * 1024)) + "MB)");}}return 0;
}// 断点续传下载函数
bool resume_download(const std::string& url, const std::string& local_path, int max_retries = 3) {log(LOG_INFO, "Starting download: " + url + " to " + local_path);// 检查文件是否已部分下载off_t file_size = get_file_size(local_path);bool resume = (file_size > 0);if (resume) {log(LOG_INFO, "Resuming download from " + std::to_string(file_size) + " bytes");}CURL* curl = curl_easy_init();FILE* file;DownloadProgress progress;progress.filename = local_path.substr(local_path.find_last_of("/") + 1);progress.start_time = std::chrono::steady_clock::now();int retries = 0;bool success = false;while (retries < max_retries && !success) {if (resume) {file = fopen(local_path.c_str(), "ab"); // 追加模式} else {file = fopen(local_path.c_str(), "wb"); // 写入模式}if (!file) {log(LOG_ERROR, "Failed to open file: " + local_path, true);return false;}if (curl) {curl_easy_setopt(curl, CURLOPT_URL, url.c_str());curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, download_progress_callback);curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &progress);curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300L); // 5分钟超时// 设置断点续传if (resume) {std::string range = std::to_string(file_size) + "-";curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());}// 执行下载CURLcode res = curl_easy_perform(curl);fclose(file);curl_easy_cleanup(curl);if (res == CURLE_OK) {log(LOG_INFO, "Download completed successfully: " + local_path);success = true;} else {log(LOG_ERROR, "Download failed (attempt " + std::to_string(retries + 1) + "/" + std::to_string(max_retries) + "): " + std::string(curl_easy_strerror(res)), true);retries++;// 短暂延迟后重试if (retries < max_retries) {sleep(5);}}} else {fclose(file);log(LOG_ERROR, "Failed to initialize curl", true);return false;}}return success;
}// 1. 获取当前激活槽位(A/B)
char get_active_slot() {std::string output = exec_command("bootctrl get-active-boot-slot");if (output.empty() || output.find("ERROR") != std::string::npos) {log(LOG_ERROR, "Failed to get active slot", true);return ' ';}return output[0]; // 返回 'A' 或 'B'
}// 2. 验证文件哈希值
bool verify_file_hash(const std::string& file_path, const std::string& expected_hash) {log(LOG_INFO, "Verifying hash for: " + file_path);std::string cmd = "sha256sum " + file_path + " | awk '{print $1}'";std::string actual_hash = exec_command(cmd.c_str());// 移除可能的换行符if (!actual_hash.empty() && actual_hash.back() == '\n') {actual_hash.pop_back();}if (actual_hash == expected_hash) {log(LOG_INFO, "Hash verification successful");return true;} else {log(LOG_ERROR, "Hash verification failed. Expected: " + expected_hash + ", Actual: " + actual_hash, true);return false;}
}// 3. 验证 OTA 包签名(使用 openssl)
bool verify_ota_signature(const std::string& ota_path, const std::string& pub_key_path) {log(LOG_INFO, "Verifying OTA package signature");std::string cmd = "openssl dgst -sha256 -verify " + pub_key_path + " -signature " + ota_path + ".sig " + ota_path;int ret = system(cmd.c_str());if (ret != 0) {log(LOG_ERROR, "Signature verification failed", true);return false;}log(LOG_INFO, "Signature verified");return true;
}// 4. 应用差分包生成完整OTA包
bool apply_diff_package(const std::string& old_path, const std::string& diff_path, const std::string& new_path) {log(LOG_INFO, "Applying diff package to create new OTA package");// 使用bsdiff工具应用差分包std::string cmd = "bsdiff " + old_path + " " + new_path + " " + diff_path;int ret = system(cmd.c_str());if (ret != 0) {log(LOG_ERROR, "Failed to apply diff package", true);// 清理可能生成的不完整文件if (access(new_path.c_str(), F_OK) != -1) {remove(new_path.c_str());}return false;}log(LOG_INFO, "Successfully created new OTA package from diff");return true;
}// 5. 刷写非活动槽位(调用 l4t-ota)
bool flash_inactive_slot(const std::string& ota_path, char target_slot) {log(LOG_INFO, "Flashing target slot: " << target_slot);std::string cmd = "sudo l4t-ota --flash-only --target_slot " + std::string(1, target_slot) + " " + ota_path;int ret = system(cmd.c_str());if (ret != 0) {log(LOG_ERROR, "Flash failed for slot " << target_slot, true);return false;}log(LOG_INFO, "Flash completed for slot " << target_slot);return true;
}// 6. 设置下次启动槽位
bool set_boot_slot(char target_slot) {log(LOG_INFO, "Setting next boot slot to: " << target_slot);std::string cmd = "sudo bootctrl set-active-boot-slot " + std::string(1, target_slot);int ret = system(cmd.c_str());if (ret != 0) {log(LOG_ERROR, "Failed to set boot slot", true);return false;}// 设置重试次数cmd = "sudo bootctrl set-retries " + std::to_string(config.max_retries);system(cmd.c_str());return true;
}// 7. 重启设备
void reboot_device() {log(LOG_INFO, "Rebooting to apply update...", true);system("sudo reboot");
}// 8. 新系统启动后验证(首次启动时执行)
bool verify_new_system() {log(LOG_INFO, "Verifying new system...");// 检查关键服务std::string services[] = {"nvargus-daemon", "docker", "nvidia-container-runtime"};bool all_services_ok = true;for (const std::string& service : services) {std::string output = exec_command(("pgrep " + service).c_str());if (output.empty()) {log(LOG_ERROR, "Critical service not running: " + service, true);all_services_ok = false;}}// 检查系统版本std::string version_output = exec_command("cat /etc/nv_tegra_release");if (version_output.find(config.target_version) == std::string::npos) {log(LOG_ERROR, "System version mismatch. Expected: " + config.target_version, true);all_services_ok = false;}if (!all_services_ok) {// 标记当前槽位启动失败system("sudo bootctrl mark-boot-failed");log(LOG_ERROR, "New system verification failed", true);return false;}// 标记当前槽位启动成功system("sudo bootctrl mark-successful");log(LOG_INFO, "New system verified successfully", true);return true;
}// 9. 获取远程文件信息(大小和哈希)
bool get_remote_file_info(const std::string& url, off_t& file_size, std::string& file_hash) {// 实际实现中,这里应该从服务器API获取文件信息// 简化实现:假设我们有一个获取元数据的URLstd::string meta_url = url + ".meta";std::string meta_path = "/tmp/ota_meta.txt";if (!resume_download(meta_url, meta_path, 2)) {log(LOG_WARNING, "Failed to download metadata, proceeding without it");return false;}std::ifstream meta_file(meta_path);if (meta_file.is_open()) {std::string line;while (std::getline(meta_file, line)) {size_t colon_pos = line.find(':');if (colon_pos != std::string::npos) {std::string key = line.substr(0, colon_pos);std::string value = line.substr(colon_pos + 1);if (key == "size") {file_size = std::stoll(value);} else if (key == "hash") {file_hash = value;}}}meta_file.close();remove(meta_path.c_str()); // 清理临时文件return true;}return false;
}int main(int argc, char* argv[]) {// 初始化日志系统log_callback = default_log_callback;// 检查权限(需要 root)if (getuid() != 0) {log(LOG_ERROR, "This program requires root privileges", true);return 1;}// 配置参数 - 实际应用中应从配置文件或命令行参数读取config.ota_url = "http://your-server/ota_v2.0.zip";config.diff_url = "http://your-server/ota_v1.0_to_v2.0.diff";config.local_ota_path = "/tmp/ota_v2.0.zip";config.local_diff_path = "/tmp/ota_v1.0_to_v2.0.diff";config.pub_key_path = "/etc/nvidia/ota_pub.key";config.log_server_url = "http://your-server/logs";config.current_version = "v1.0";config.target_version = "v2.0";config.max_retries = 3;config.log_level = LOG_INFO;log(LOG_INFO, "Starting OTA update process from version " + config.current_version + " to " + config.target_version, true);// 步骤1:获取当前激活槽位char current_slot = get_active_slot();if (current_slot != 'A' && current_slot != 'B') {log(LOG_ERROR, "Invalid active slot", true);return 1;}char target_slot = (current_slot == 'A') ? 'B' : 'A';log(LOG_INFO, "Current slot: " << current_slot << ", Target slot: " << target_slot);// 步骤2:获取本地当前版本的OTA包(用于差分升级)std::string local_current_ota = "/tmp/ota_" + config.current_version + ".zip";bool has_current_ota = (access(local_current_ota.c_str(), F_OK) != -1);// 步骤3:决定使用完整包还是差分包bool use_diff = false;if (has_current_ota) {// 检查差分包是否可用且更小off_t full_size = 0, diff_size = 0;get_remote_file_info(config.ota_url, full_size, "");get_remote_file_info(config.diff_url, diff_size, "");if (diff_size > 0 && (full_size == 0 || diff_size < full_size * 0.5)) {// 差分包小于完整包的50%,使用差分升级use_diff = true;log(LOG_INFO, "Using differential update (saves " + std::to_string((full_size - diff_size) / (1024 * 1024)) + "MB)");}}// 步骤4:下载升级包(完整包或差分包)bool download_success = false;std::string expected_hash;if (use_diff) {// 下载差分包download_success = resume_download(config.diff_url, config.local_diff_path, config.max_retries);// 获取差分包预期哈希off_t dummy_size;get_remote_file_info(config.diff_url, dummy_size, expected_hash);// 验证差分包if (download_success && !expected_hash.empty()) {download_success = verify_file_hash(config.local_diff_path, expected_hash);}// 应用差分包生成完整OTA包if (download_success) {download_success = apply_diff_package(local_current_ota, config.local_diff_path, config.local_ota_path);}} else {// 下载完整OTA包download_success = resume_download(config.ota_url, config.local_ota_path, config.max_retries);// 获取完整包预期哈希off_t dummy_size;get_remote_file_info(config.ota_url, dummy_size, expected_hash);// 验证完整包if (download_success && !expected_hash.empty()) {download_success = verify_file_hash(config.local_ota_path, expected_hash);}}if (!download_success) {log(LOG_ERROR, "Failed to download or verify OTA package", true);return 1;}// 步骤5:验证签名if (!verify_ota_signature(config.local_ota_path, config.pub_key_path)) {remove(config.local_ota_path.c_str());if (use_diff) {remove(config.local_diff_path.c_str());}return 1;}// 步骤6:刷写目标槽位if (!flash_inactive_slot(config.local_ota_path, target_slot)) {return 1;}// 步骤7:设置启动槽位if (!set_boot_slot(target_slot)) {return 1;}// 清理临时文件remove(config.local_ota_path.c_str());if (use_diff) {remove(config.local_diff_path.c_str());}// 步骤8:重启(新系统启动后会执行 verify_new_system)reboot_device();return 0;
}
http://www.dtcms.com/a/312570.html

相关文章:

  • STM32复位电路解析
  • Java常用英语单词
  • adb 与pad 交互方法
  • PPT自动化 python-pptx - 9: 图表(chart)
  • 服务器中切换盘的操作指南
  • Jetson Orin NX/NANO+ubuntu22.04+humble+MAVROS2安装教程
  • Kafka——常见工具脚本大汇总
  • /usr/bin/ld: 找不到 -lev
  • stm32f103重新上电后前面的打印内容无法打印出来的原因
  • Springboot 04 starter
  • 分布式文件系统05-生产级中间件的Java网络通信技术深度优化
  • ClickHouse Windows迁移方案与测试
  • HiveMQ 2024.9 设计与开发文档
  • 知识随记-----MySQL 连接池健康检测与 RAII 资源管理技术
  • Timer串口常用库函数(STC8系列)
  • Docker--解决x509: certificate signed by unknown authority
  • 系统学习算法:专题十六 字符串
  • 基于SpringBoot+MyBatis+MySQL+VUE实现的电商平台管理系统(附源码+数据库+毕业论文+部署教程+配套软件)
  • WSUS服务器数据库维护与性能优化技术白皮书
  • Leetcode 12 java
  • CSS 预处理器(Preprocessor)和后处理器(Postprocessor)
  • python工具方法51 视频数据的扩充(翻转、resize、crop、re_fps)
  • 01.MySQL 安装
  • 仓库管理系统-15-前端之管理员管理和用户管理
  • 01数据结构-时间复杂度和空间复杂度
  • 每日五个pyecharts可视化图表-bars(2)
  • HCIP笔记(第四章)
  • Flutter各大主流状态管理框架技术选型分析及具体使用步骤
  • 网络原理 - TCP/IP
  • 计算机网络(TCP篇)