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

ESP-IDF+vscode开发ESP32第十三讲——NVS

目录一、NVS梳理1.1 分区 (Partition)NVS 的专属“仓库”1.2 页面 (Page)仓库里的“货架”1.3 条目 (Entry)货架上的“最小存储格”1.4 键值对 (Key-Value Pair)实际存放的“货物”1.5 命名空间 (Namespace)货物的“分类文件夹”二、完善工程1 nvs_basics.c2 nvs_basics.h3 mian.c三、添加官方nvs控制台命令四、结果展示五、nvs分区生成程序5.1 使用函数烧录5.2 使用python工具烧录5.3 结果展示前言NVSNon-Volatile Storage非易失性存储是一个数据储存库。它的核心作用就是在 Flash 中持久化地存储键值对格式的数据。它非常适合存储那些不经常更改、但需要长期保留的配置数据。例如wifi和蓝牙的各种配网凭据。本文使用的开发板是微雪的ESP32-P4-Module-DEV-KIT。ESP-IDF版本是6.0.1。基于第一章的工程模板。来实现NVS库的各种操作。一、NVS梳理NVSNon-Volatile Storage非易失性存储是 ESP32 中用于在 Flash 中持久化保存数据的官方库。它本质上是一个轻量级的键值对存储系统。但是各种名词、概念较多容易产生误解。这里我从宏观到微观的层级来梳理这些概念。1.1 分区 (Partition)NVS 的专属“仓库”NVS 的数据最终是存储在 ESP32 的 Flash 上的。 ESP32 的 Flash使用一个分区表对各个类型的数据空间进行管理其中就有也必须有一个专门的分区给 NVS 使用。在partitions.csv分区表中类型Type为data子类型SubType为nvs的分区。默认通常是 24KB (0x6000)。如果你的设备需要存储大量配置可以在分区表中手动调大这个数值。它是 NVS 库操作的物理边界所有的 NVS 数据都只能在这个划定的 Flash 区域内读写。ESP32 的 Flash实际分区表如下关于各种分区的解释可以去看《分区表》前面工程《ESP-IDFvscode开发ESP32第九讲——I2S工程1》也涉及到分区表的使用。# ESP-IDF Partition Table # Name , Type, SubType, Offset , Size , Flags nvs , data, nvs , 0x9000 , 0x6000, phy_init , data, phy , 0xf000 , 0x1000, factory , app , factory, 0x10000, 10M , ,1.2 页面 (Page)仓库里的“货架”NVS 并不是把数据杂乱无章地堆在分区里而是将分区划分成了若干个固定大小的“页面”。大小通常一个页面的大小等于 Flash 的一个物理扇区即 4096 字节 (4KB)。状态管理每个页面都有自己的生命周期状态如空、活跃、写满、擦除中。磨损均衡NVS 采用日志式的写入方式。当前活跃的页面写满后系统会自动切换到下一个空白页面继续写入。这种机制避免了频繁擦写同一个物理地址极大地延长了 Flash 的使用寿命。1.3 条目 (Entry)货架上的“最小存储格”页面进一步被划分为更小的单元称为“条目”。大小每个条目固定为 32 字节。作用它是 NVS 存储数据的最小物理单位。无论是存一个小小的整数还是存一段很长的字符串都会占用至少一个条目。空间计算如果你发现 NVS 经常报“空间不足”往往是因为条目被耗尽了。例如在一个 24KB 的 NVS 分区中大约只有几千个条目可供使用。1.4 键值对 (Key-Value Pair)实际存放的“货物”这是开发者在写代码时最直接接触的概念。NVS 的所有数据操作都是基于键值对的。键 (Key)数据的名字ASCII 字符串最大长度不能超过 15 个字符。值 (Value)实际要存储的数据。支持多种类型整数如int8_t,int32_t,uint64_t等字符串以\0结尾最大约 4000 字节二进制大对象BLOB用于存结构体、数组等最大约 50 万字节存储逻辑当你写入一个键值对时NVS 会根据数据的大小占用 1 个或多个连续的“条目”来存放它。1.5 命名空间 (Namespace)货物的“分类文件夹”当我们需要存入大量键值对很有可能有重名的键为了防止不同功能模块的键名Key发生冲突NVS 引入了命名空间的概念。作用相当于电脑里的“文件夹”。你可以把 Wi-Fi 的配置放在名为wifi的命名空间里把传感器的校准数据放在sensor的命名空间里。隔离性即使两个命名空间里都有叫config的键它们也是完全独立、互不干扰的。限制命名空间的名称同样不能超过 15 个字符。到这所有NVS的储存结构我们清楚了下面就可以尝试去使用NVS了。二、完善工程创建组件nvs_basics在cmakelists中添加依赖声明REQUIRES nvs_flash1 nvs_basics.c#include stdio.h #include nvs_basics.h static const char *TAG NVS_BASICS; typedef struct { uint8_t id; char name[32]; float values[2]; uint32_t flags; int16_t counts[2]; bool active; } test_data_t; void nvs_init(void) { esp_err_t err; err nvs_flash_init(); if (err ESP_ERR_NVS_NO_FREE_PAGES ) { ESP_LOGW(TAG, 分区中没有了空页面); return; } else if (err ESP_ERR_NOT_FOUND ) { ESP_LOGW(TAG, 没有找到NVS分区); return; } nvs_handle_t storage_handle; ESP_ERROR_CHECK(nvs_open(storage, NVS_READWRITE_PURGE , storage_handle)); ESP_ERROR_CHECK(nvs_set_i32(storage_handle, num, 123456789)); ESP_ERROR_CHECK(nvs_set_str(storage_handle, str, Hello, ESP32!)); test_data_t test_data { .id 123, .name Test Sample, .values {3.14f, 2.718f}, .flags 0xABCD1234, .counts {-100, 100}, .active true }; err nvs_set_blob(storage_handle, test_data, test_data, sizeof(test_data_t)); int32_t num; ESP_ERROR_CHECK(nvs_get_i32(storage_handle, num, num)); ESP_LOGI(TAG, 从NVS读取到的整数: %d, num); size_t len 0; err nvs_get_str(storage_handle, str, NULL, len); if(err ESP_OK){ char* message malloc(len); // 分配足够的内存来存储字符串 ESP_ERROR_CHECK(nvs_get_str(storage_handle, str, message, len)); ESP_LOGI(TAG, 从NVS读取到的字符串: %s, message); free(message); } size_t required_size 0; err nvs_get_blob(storage_handle, test_data, NULL, required_size); if(err ESP_OK){ test_data_t* test_data malloc(required_size); ESP_ERROR_CHECK(nvs_get_blob(storage_handle, test_data, test_data, required_size)); ESP_LOGI(TAG, 从NVS读取到的测试数据: ID%d, Name%s, test_data-id, test_data-name); free(test_data); } nvs_iterator_t it; err nvs_entry_find(nvs, storage, NVS_TYPE_ANY, it); if(err ESP_OK){ do { nvs_entry_info_t info; ESP_ERROR_CHECK(nvs_entry_info(it, info)); ESP_LOGI(TAG, 命名空间%s, 找到的键: %s, 类型: %x, info.namespace_name, info.key, info.type); } while (nvs_entry_next(it) ESP_OK); nvs_release_iterator(it); } nvs_stats_t nvs_stats; ESP_ERROR_CHECK(nvs_get_stats(nvs, nvs_stats)); ESP_LOGI(TAG, NVS统计信息 - 已使用条目: %d, 空闲条目%d, 可用条目: %d, 总条目: %d, 命名空间数量: %d, nvs_stats.used_entries, nvs_stats.free_entries, nvs_stats.available_entries, nvs_stats.total_entries, nvs_stats.namespace_count); ESP_ERROR_CHECK(nvs_commit(storage_handle)); nvs_close(storage_handle); //ESP_ERROR_CHECK(nvs_flash_deinit()); }2 nvs_basics.h#ifndef NVS_BASICS_H #define NVS_BASICS_H #include stdio.h // 输入输出函数 #include string.h // 字符串处理函数 #include esp_log.h // ESP32日志函数 #include FreeRTOS/FreeRTOS.h // FreeRTOS函数 #include FreeRTOS/task.h // FreeRTOS任务管理函数 #include FreeRTOS/semphr.h // FreeRTOS信号量管理函数 #include nvs_flash.h // NVS Flash函数 void nvs_init(void); #endif // NVS_BASICS_H3 mian.c#include stdio.h #include user.h #include nvs_basics.h void app_main(void) { CONSOLE_REPL_INIT(); // 初始化控制台REPL环境 nvs_init(); while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); } }以上实现了分区初始化接着向默认分区存入了一个int32、string和blob数据接着读取以上数据查看是否正确最后对分区所有信息进行了一个检索。代码使用比较简单就不讲解了。各种函数的定义去查看官方文档或《ESP32实用API指南3》。三、添加官方nvs控制台命令打开esp安装目录例如我的是《E:\esp\v6.0.1\esp-idf\examples\system\console\advanced\components》可以看到共有三个控制台命令文件夹其中system在第二章新建工程模板中就已经添加了现在继续添加nvs。找到user组件的idf_component.yml新增nvs的路径依赖如下。version: 1.0.0 dependencies: cmd_system: path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_system cmd_nvs: path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_nvs接着在user.c中的控制台初始化程序中注册nvs相关命令。/*--------------------------------------------------------------------------*/ /** * brief console REPL init * param[in] void * note * return void */ /*--------------------------------------------------------------------------*/ void CONSOLE_REPL_INIT(void) { esp_console_repl_config_t repl_config ESP_CONSOLE_REPL_CONFIG_DEFAULT(); // 使用默认REPL配置 esp_console_repl_t *repl NULL; #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG esp_console_dev_usb_serial_jtag_config_t usb_serial_jtag_config ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT(); // 使用默认USB串行JTAG配置 ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(usb_serial_jtag_config, repl_config, repl)); // 创建USB串行JTAG REPL环境 #else esp_console_dev_uart_config_t uart_config ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); // 使用默认UART配置 ESP_ERROR_CHECK(esp_console_new_repl_uart(uart_config, repl_config, repl)); // 创建UART REPL环境 #endif ESP_ERROR_CHECK(esp_console_start_repl(repl)); // 启动REPL环境 esp_console_register_help_command(); // 注册帮助命令 register_system(); // 注册系统常用命令 register_nvs(); // 注册NVS相关命令 // linenoiseSetDumbMode(1); // 设置linenoise为简单模式适用于串行终端 }这样就完成了四、结果展示以上是我们对nvs的测试结果。使用help命令可以发现新增了很多nvs的命令注意这些命令操控的分区首先要在代码中使用nvs_flash_init进行初始化。五、nvs分区生成程序从上文可以看到我们很容易向nvs分区中写入和读取数据。但是如果我们现在准备了大量的键值对需要存入那么这种代码程序的写入方式就很低效。于是我们需要使用NVS 分区生成程序。NVS 分区生成程序根据 CSV 文件中的键值对生成二进制文件。首先需要自定义一个CSV文件储存需要烧录分区的键值对。例如nvs_data.cvs内容如下key,type,encoding,value my_app,namespace,, wifi_ssid,data,string,MyHomeWiFi wifi_pass,data,string,password123 device_id,data,u32,10086关于文件格式见《CSV文件格式》说明分区生成程序不会对重复键进行检查而将数据同时写入这两个重复键中。请注意不要使用同名的键新页面创建后前一页的空白处不会再写入数据。CSV 文件中的字段须按次序排列以优化内存暂不支持 64 位数据类型。注意前面我们说的分区表也是CSV格式文件但是作用不相同。对比维度分区表 (Partition Table)NVS 数据 CSV核心作用定义 Flash 存储空间的物理划分地址、大小、类型定义要写入 NVS 分区的具体键值对数据处理时机编译阶段由构建系统解析生成partition-table.bin烧录前由nvs_partition_gen.py解析生成数据.bin必需列名Name,Type,SubType,Offset,Size,Flagskey,type,encoding,value内容示例nvs, data, nvs, 0x9000, 0x6000,wifi_ssid, data, string, MyWiFi定义好nvs_data.cvs之后需要将其中的键值对烧录进nvs分区提供了两者方法分别是直接使用函数nvs_create_partition_image通过 CMake 创建分区二进制文件和手动调用nvs_partition_gen.py工具5.1使用函数烧录打开组件nvs_basics的中的CMakeLists.txt文件修改如下idf_component_register(SRCS nvs_basics.c INCLUDE_DIRS include REQUIRES nvs_flash) # 分区名nvs分区数据文件nvs_data.csv分区类型FLASH_IN_PROJECT nvs_create_partition_image(nvs nvs_data.csv FLASH_IN_PROJECT)nvs_create_partition_image宏命令原型如下nvs_create_partition_image(partition csv [FLASH_IN_PROJECT] [DEPENDS dep1 dep2 ...])参数必填说明partition✅分区名称必须与partitions.csv中的 Name 字段完全一致如nvs、my_nvscsv✅NVS 数据源 CSV 文件的路径支持绝对路径或相对于当前CMakeLists.txt的路径FLASH_IN_PROJECT❌添加此标志后执行idf.py flash时会自动烧录该 NVS 分区到芯片。不加则只生成.bin文件但不烧录DEPENDS❌指定额外依赖项。当这些文件发生变化时触发 NVS 镜像重新生成。常用于 CSV 引用了外部二进制文件的场景注如果csv文件和CMakeLists.txt文件在同一个路径文件下csv可直接填入csv文件名5.2 使用python工具烧录首先找到nvs_partition_gen.py的地址例如E:\esp\v6.0.1\esp-idf\components\nvs_flash\nvs_partition_generator将nvs_partition_gen.py文件粘贴到工程根目录下接着就可以使用python命令了命令使用见《NVS 分区生成程序 - ESP32-P4 - — ESP-IDF 编程指南 v6.0.1 文档》5.3 结果展示可以看出nvs_data.cvs所有键值对都写入了nvs分区中

相关文章:

ESP-IDF+vscode开发ESP32第十三讲——NVS

目录 一、NVS梳理 1.1 分区 (Partition):NVS 的专属“仓库” 1.2 页面 (Page):仓库里的“货架” 1.3 条目 (Entry):货架上的“最小存储格” 1.4 键值对 (Key-Value Pair):实际存放的“货物” 1.5 命名空间 (Namespace)&…...

数学论文降AI工具免费推荐:2026年数学毕业论文降AI4.8元知网达标免费完整方案

数学论文降AI工具免费推荐:2026年数学毕业论文降AI4.8元知网达标免费完整方案 试过五款降AI工具,价格从4.8元到几十元不等。 性价比最高的是嘎嘎降AI(www.aigcleaner.com)——4.8元,知网AI率从66%降到6.3%&#xff0…...

Python之anonymous包语法、参数和实际应用案例

一、包概述与核心功能 graphs-edjedovi是一个极简Python库(当前版本0.0.2),仅封装Dijkstra单源最短路径算法,专注于带权有向/无向图的最短路径计算,无可视化、拓扑排序等扩展能力。 核心能力:计算单个源节点…...

心理学论文降AI工具免费推荐:2026年心理学毕业论文知网维普降AI4.8元亲测完整方案

心理学论文降AI工具免费推荐:2026年心理学毕业论文知网维普降AI4.8元亲测完整方案 答辩前夕,AI率36%,学校要求15%以下。 用嘎嘎降AI(www.aigcleaner.com),4.8元,两小时搞定,一次过…...

[具身智能-855]:什么是AI应用?AI 应用、AI 模型、AI Agent三者区别?

一、定义AI 应用:搭载人工智能技术,具备智能理解、推理、生成、识别、决策能力,能自主完成人类事务的软件、程序、系统、设备。二、狭义 AI 应用(纯 AI 工具,最常见)专门靠 AI 干活,一眼看出是 …...

如何用嘎嘎降AI处理金融学论文:金融学毕业论文降AI免费完整操作教程

如何用嘎嘎降AI处理金融学论文:金融学毕业论文降AI免费完整操作教程 这篇教程是针对金融学论文降AI教程写的——问得最多的操作细节,都在这里。 主工具:嘎嘎降AI(www.aigcleaner.com),4.8元一篇&#xff…...

TabNet: Attentive Interpretable Tabular Learning——一种具有可解释性的注意力表格学习模型

文章提出了一种名为 TabNet 的新型深度神经网络架构,专门用于处理表格数据。该架构旨在结合决策树(DT)的优势(如可解释性、处理表格数据的高效性)与深度神经网络(DNN)的优势(如端到端…...

Kotlin 跨平台 SqliteNow 全平台数据持久化方案

Kotlin 跨平台 SqliteNow 全平台数据持久化方案1. 环境与依赖配置1.0 创建一个Kotlin 多平台项目1.1 版本声明(libs.versions.toml)1.2 项目级插件配置(build.gradle.kts)1.3 模块级依赖配置(app/shared/build.gradle.…...

5大长期记忆系统终极横评!谁是AI Agent的「最强大脑」

🚀 5大长期记忆系统终极横评!谁是AI Agent的「最强大脑」? AI Agent 的「长期记忆」能力,决定了它能否真正拥有"持续学习"和"深度理解"的核心竞争力。 我们耗时数周,对 虾觅 Xiami、AgentMemory…...

一多操作系统的生命体架构与当前主流开发语言的区别

这套架构与当前主流开发语言的区别,本质上就是**“造物主”与“工匠”**的区别。 目前的编程语言(无论是 C、Java 还是 Python)都是在教计算机**“怎么做”(How),而一多 OS 的生物学构架是在告诉系统“要什…...

7天深度拆解:openpilot自动驾驶系统技术实现与二次开发指南

7天深度拆解:openpilot自动驾驶系统技术实现与二次开发指南 【免费下载链接】openpilot openpilot is an operating system for robotics. Currently, it upgrades the driver assistance system on 300 supported cars. 项目地址: https://gitcode.com/GitHub_Tr…...

戴森球计划工厂蓝图架构深度解析:构建高效星际生产线的核心策略

戴森球计划工厂蓝图架构深度解析:构建高效星际生产线的核心策略 【免费下载链接】FactoryBluePrints 游戏戴森球计划的**工厂**蓝图仓库 项目地址: https://gitcode.com/GitHub_Trending/fa/FactoryBluePrints FactoryBluePrints 项目作为戴森球计划游戏中最…...

BilibiliDown:简单三步掌握B站视频下载的终极指南

BilibiliDown:简单三步掌握B站视频下载的终极指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/bi/Bi…...

华硕笔记本G-Helper显示管理全攻略:从色彩异常到专业校准的5步解决方案

华硕笔记本G-Helper显示管理全攻略:从色彩异常到专业校准的5步解决方案 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivob…...

GDScriptDecomp:让Godot游戏逆向工程变得触手可及

GDScriptDecomp:让Godot游戏逆向工程变得触手可及 【免费下载链接】gdsdecomp Godot reverse engineering tools 项目地址: https://gitcode.com/GitHub_Trending/gd/gdsdecomp 你是否曾遇到过这样的情况:手头有一个Godot引擎开发的游戏&#xff…...

Windows 环境下 NVM 安装与 Node.js 版本管理完全指南

💡 为什么需要 NVM? 作为前端开发者,你是否遇到过这些困扰: 场景痛点新项目要求 Node 20,老项目依赖 Node 16频繁卸载重装,浪费时间团队协作时环境不一致代码在同事电脑上跑不通全局安装的依赖版本冲突升…...

计算机图形学——四、光栅化与消隐

第四章 光栅转化与消隐 重点总结 一、光栅转化(Rasterization) 定义:把用数学描述的图形(如三角形)变成屏幕上一个个像素点。 1. 多边形扫描转换 顶点表示 → 点阵表示:把多边形的顶点坐标,转成…...

c#string字符串

//API 应用程序接口 内置函数 //字符串的属性 string a "abcd";//表示字符串中 字符的个数Console.WriteLine(a.Length);//字符串是可以通过 索引 取值的 因为string类内部顶一个一个索引器char c a[2];Console.WriteLine(c);string s1 "abc";st…...

四大音乐平台一键解析:免费开源music-api打破会员壁垒

四大音乐平台一键解析:免费开源music-api打破会员壁垒 【免费下载链接】music-api Music API 项目地址: https://gitcode.com/gh_mirrors/mu/music-api 在音乐流媒体平台林立的今天,你是否曾被各大平台的会员壁垒所困扰?想听周杰伦的歌…...

有哪些AI论文软件是真的适配学科专业,而不是模板套话?

在 AI 写作技术迅猛发展的今天,各类论文工具层出不穷,看似能快速完成写作任务,实则多数是内容空洞、逻辑混乱、格式随意的“模板复制器”,生成的文章缺乏专业深度,充斥着机械化的表达方式。真正具备学术价值的 AI 论文…...

开发AI应用时利用Taotoken实现多模型聚合与路由策略

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 开发AI应用时利用Taotoken实现多模型聚合与路由策略 应用场景类,面向正在构建复杂AI应用的架构师或开发者,…...

3步掌握React Easy Crop:从零到精通的图像裁剪完整指南

3步掌握React Easy Crop:从零到精通的图像裁剪完整指南 【免费下载链接】react-easy-crop A React component to crop images/videos with easy interactions 项目地址: https://gitcode.com/gh_mirrors/re/react-easy-crop 你是否在为React应用中的图片裁剪…...

FlashAttention 在昇腾NPU上的极致优化

刚接触 FlashAttention 那会,我被一个困惑砸懵了:明明 Attention 机制的计算量已经是 O(n) 了,业界还在拼命优化它,图什么? 直到我看见一组数据才明白——训练一个 1750 亿参数的 GPT-3,光是 Attention 计…...

GeoSeg:突破性混合Transformer架构实现高效遥感图像语义分割

GeoSeg:突破性混合Transformer架构实现高效遥感图像语义分割 【免费下载链接】GeoSeg UNetFormer: A UNet-like transformer for efficient semantic segmentation of remote sensing urban scene imagery, ISPRS. Also, including other vision transformers and C…...

2026第四届“盘古石杯“晋级赛 手机取证 手搓复盘(write up)

手机取证1. 分析黄志远phone.E01检材,黄志远手机总共安装了多少款短视频应用?[答案格式:1]apk 分析里面,4 个。当时把 b 站也算上了2. 分析黄志远phone.E01检材,黄志远手机安装的龙虾应用的包名是什么?[答案…...

当AI推理遭遇通信瓶颈时,NIXL如何重新定义高性能数据传输架构?

当AI推理遭遇通信瓶颈时,NIXL如何重新定义高性能数据传输架构? 【免费下载链接】nixl NVIDIA Inference Xfer Library (NIXL) 项目地址: https://gitcode.com/gh_mirrors/ni/nixl 在大规模分布式AI推理场景中,数据传输和通信瓶颈已成为…...

为什么我总是想很多,却很难开始做?

为什么我总是想很多,却很难开始做? 有一种人,脑子从来停不下来。 走路在想,洗澡在想,睡前还在想。 想人生方向,想技术路线,想项目结构,想商业模式,想内容选题&#xff0c…...

2026年亲测AI论文写作软件指南(高效定稿版)

为解决学术写作中效率与合规两大核心痛点,本文精选8款高适配性 AI 论文写作工具(按综合优先级排序),围绕中文学术规范适配、真实参考文献生成、格式标准化、高性价比四大核心维度进行测评,同时配套分场景精准选型方案与…...

如何在C加加项目中快速接入Taotoken的多模型API服务

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 如何在C项目中快速接入Taotoken的多模型API服务 对于使用C进行开发的工程师而言,直接调用HTTP API是集成第三方服务最灵…...

FastGithub:5分钟告别GitHub龟速访问,开发效率提升3倍的终极方案

FastGithub:5分钟告别GitHub龟速访问,开发效率提升3倍的终极方案 【免费下载链接】FastGithub github定制版的dns服务,解析访问github最快的ip 项目地址: https://gitcode.com/gh_mirrors/fa/FastGithub 你是否经历过这样的场景&#…...