电子电器架构刷写方案——General Flash Bootloader
电子电器架构刷写方案——General Flash Bootloader
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。
注:文章1万字左右,深度思考者入!!!
老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师:
屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节能减排。
无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事.而不是让内心的烦躁、焦虑、毁掉你本就不多的热情和定力。

文章大体有如下内容:
1、简介
2、刷写系统概述
3、Flash Bootloader刷写流程
4、Flash Bootloader架构介绍
5、关于刷写的思考
正文如下:
一、Bootloader简介
首先基于现在整车电子电气架构中ECU类型,大体分为两类:
MCU(常规MCU)具备硬实时,应用Classic AUTOSAR软件架构;
MPU,具备操作系统,应用上多使用Adaptive AUTOSAR软件架构或者自研;
那相应对于Bootloader介绍也分为:
1、MCU Bootloader是微控制器(MCU)中的引导程序,它的设计旨在解决程序升级问题,尤其是在产品稳定投产、程序烧录后,MCU被外壳等材料覆盖,无法通过烧录口进行升级的情况下。Bootloader可以通过一定方式触发运行,比如按键触发或UDS协议指令(在汽车行业中)触发。一旦Bootloader运行,它可以通过串口接收新的代码并写入Flash,从而在不使用烧录器的情况下实现程序升级。
2、Bootloader,中文名称为系统启动加载器,是计算机系统从开机上电到操作系统启动过程中所需要的一个引导程序。在嵌入式Linux系统中,Bootloader也扮演着重要的角色。它是一段在操作系统内核运行之前运行的程序,主要负责初始化硬件设备、建立内存空间映射图,将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。
Bootloader的具体作用如下:
-> 1、提供初始的硬件设备的初始代码,以及检测和初始化单板;
-> 2、引导操作系统,包括定位、解压和加载操作系统到内存空间,然后将其控制权交给操作系统;
-> 3、当操作系统获取控制权后,嵌入式系统中的Bootloader就不复存在了。
此外,U-Boot是一款流行的功能强大的开源Bootloader项目。它支持多种嵌入式操作系统内核,如Linux、NetBSD、VxWorks等,也支持多种处理器系列,如PowerPC、ARM、x86、MIPS。U-Boot有较高的可靠性和稳定性,以及高度的灵活的功能设置,适合U-Boot调试、操作系统的不同引导要求、产品发布等。它还包含丰富的设备驱动源码和较为丰富的开发调试文档与强大的网络技术支持。
首先关于ECU刷写的软件架构Flash Bootloader并没有在AUTOSAR规范中有明确定义。原因是每家刷写策略基于自身需求不一致,没有必要在AUTOSAR规范中特定定义函数接口等信息。
基于整车不同域,使用场景不一样,就会有不同的总线类型。因此在做ECU刷写时,也是通过不同的总线类型CAN,LNFlexRay or Ethernet)来完成软件的下载更新。
整车所有件基于相同的刷写流程,只是数据传输的总线载体不一样而已。
在研发阶段,可以随时进行应用程序(Application)的更新和下载可以随时为研发工程师随时进行下载调试更新等功能在车辆下线阶段,进行应用程序的刷写和灌注:在车辆售后阶段进行售后维护和升级
目前对于ECU更新方式:
1、刷写上位机通过OBD口,相当于传统的Flash更新方式进行更新;
2、另外是通过OTA,基于OTAMaster 对车内节点进行更新;

上述刷写方式不需要通过更换硬件ECU来完成软件的更新,对于软件bug或者功能升级,在硬件不需要更新的情况下,可以直接通过更新Application功能实现功能升级对于量产的ECU产品,可以不通过传统的调试接口UTAGDAP2)来完成Fash 的刷写这个时候调试口已经关闭,通过OBD口升级提供一种更好的方案对于车内的ECU来讲,通过一套协议(UDS)即可完成全部ECU的刷写需求在一定程度上,保证了刷写过程中的安全性和合法性。

通过上位机加载待刷写的Flash Data/Driver,不用通过烧录口就可以进行software update,对用户而言更加友好。
二、系统概述
诊断仪通过OBD诊断口和网关ECU进行连接,实现诊断刷写和路由功能:每个ECU的内存布局都是基本类似,包含了三大部分 Flash Bootloader.Flash Driver 和Application.

Flash Bootloader实现总线通信基于ISO标准和OEM企标,实现诊断服务功能Application调用Flash Driver 接口来对Fash进行擦写操作一般情况下,代码仅在轮询模式下运行的中断仅在一些特殊需求下才会用到,一般代码精简短小,占用空间资源不大.

用于擦除和写入数据的Driver,通常是在RAM中执行调用的,是片内片外的驱动程序可对Hash 进行擦除和写入,可将数据写入Flash中。
-> 通过驱动调用擦除接口,可以对内存地址进行擦除,将地址上旧数据进行擦掉;
-> 在数据写入阶段可以调用驱动的写入接口,实现对新数据在擦除后的地址进行写入操作,不同的芯片厂家和硬件平台,flash driver 是有所不同的。
根据 Flash 芯片不同,不同点主要体现如下两点:
1、不同的擦写参数(Sectors/Pages): 擦除和写入的Sector 和page 大小不一样2、存储保护机制(ECC);
对于第一点(擦写参数(Sectors/Pages)是指在擦除或重写存储设备中的数据时所使用的单位。不同的存储设备可能有不同的擦写参数,这些参数可能会影响擦写性能和寿命。
在闪存设备中,擦写参数通常是以扇区(Sector)或页(Page)为单位进行操作的。扇区通常是一个存储单元,可以容纳一定数量的数据,而页是闪存设备中一个最小的可擦除单位。
对于不同的闪存设备,擦写参数可能会有所不同。例如,一些设备可能有 2KB的页大小,而另一些设备可能有 4KB 或 8KB 的页大小。此外,一些设备可能支持不同的擦写策略,例如一次擦写一个扇区或一次擦写多个扇区。
这些不同的擦写参数可能会影响闪存设备的性能和寿命。例如,如果一个设备的页大小较小,那么在执行写入操作时,可能会需要进行更多的读-改-写操作,这可能会导致性能下降。此外,如果一个设备的擦写策略过于频繁地擦除同一区域的数据,那么这个区域的寿命可能会比其他区域更短。因此,选择正确的擦写参数对于优化闪存设备的性能和寿命非常重要。这需要根据具体的应用需求和设备特性进行选择。
对于第二点:(存储保护机制(ECC)是一种用于Nand Flash 的差错检测和修正算法,它能够确保数据的可靠性。
在Nand Flash的每个页面上,包括额外的存储空间,即64个字节的空闲区(每512 字节的扇区有 16 字节)。这个空闲区能存储 ECC 代码及其它信息,例如磨损评级或逻辑到物理块映射。在编程操作期间,ECC单元根据扇区中存储的数据来计算误码校正代码。数据区的ECC代码然后被分别写入到各自的空闲区。当数据被读出时,ECC代码也被读出,运用反操作可以核查读出的数据是否正确

如果有数据错误,ECC 算法可以校正数据错误。能校正的错误的数量取决于所用算法的校正强度。软件通常负责磨损评级或逻辑到物理映射,并且如果处理器不包含ECC 硬件的话,软件还需要提供 ECC 码。
芯片内部对数flash driver存放位置:
-> 1、UTSAR转化为加密的二进制数组文件并对文件进行加密存储在FIashBootloader 中的指定Flash 区域,调用前先解密目的是防止flash误操作,误调用,比如用户程序(Application 调用flashdriver 接口,对某-块区域进行调用擦写),添加加密策略,就保证了只有 Bootloader 具备解密能力,才可以调用Flash driver;
-> 2、刷写Application之前通过诊断仪载入到 RAM中上述操作是先通过诊断仪将 flash driver 下载到指定的区域(RAM 区域),下载完成后Bootloader 才可以调用相应的接口实现对内存的擦除和写入。ECU 复位或者断电后 flashdriver就不再存在,避免安全问题。

三、Flash Bootloader刷写流程
本文仅描述通用性刷写流程,不同OEM或多或少有一定的差异如上图,当前假设只有FBL程序,没有Application(用户程序)。ECU上电后运行在Bootloader当中,Tester 通过相应的接口卡
举例常用的:
CAN总线用VN16 系列;
DoIP用VN56系列。

这样刷写上位机与待刷写 ECU 建立连接,通过UDS 协议刷写上位机与 ECU 进行相应的数据交互,主要读取一些所需要的诊断版本信息数据。接下来就是将用户程序数据通过刷写上位机传到ECU相应的地址处进行刷写。数据传输并写入完毕,Bootloader 会对数据进行校验,其中包含对单独每一个 block 以及整体的数据进行校验上述完毕后,对App有效标志位进行操作置位,后续复位。复位操作首先在FBL中查阅应用数据flag 位是否置位,判断有效后就会跳到用户程序中在Application中运行正常。后续有新的刷写请求,会继续运行至FBL中。对于刷写流程大体分为三步:

A:Pre programming
首先切换至编程会话模式,进行Programming Pre-condition状态检查(车速、挡位等);
关闭DTC功能
对常规通信报文进行设置,只接收诊断报文
目的是让ECU全负荷为刷写服务。

切换至编程会话模式写入一些DID和读取DID操作这些操作与OEM企业标准强相关。在定义刷写流程中明确每一步执行的内容和目的。信息安全要求过Service27,表示通过争取途径获取访问权限。

1、Request Service 34报文格式中会体现下载数据长度、是否压缩等信息;、

2、通过Service36/37完成数据的整体传输3、数据传输完成后,会执行 Service 31,对下载数据做完整性校验,数据丢失、错位等失真情况。

Flash Driver后会通过Service 31RID(猜出FF00)调用对内存擦除的接口实现对ECU内存的擦除操作。想要写入新的数据之前,必须对数据做擦除操作后才可以进行写入。


如上图,Vector 中国关于Fash Bootloader 产品手册的截图关于 Flash Bootloader 工程主要分为几大部分:
1、通信协议栈主要用于实现不同总线的通信需求,如图支持车载CAN 总线/LIN 总线/Flexray总线/车载以太网
2、OEM Download Manager主要是基于OEM企标要求做代码功能实现:A:OEM 定义的刷写流程(在 Bootloader 模式下)所使用到的诊断服务、子服务所支持的DID/RID/NRC信息都在这个模块做功能实现;
3、Fash bootloader 用户程序读取一些软件版本号、NVM存储应用,小模块上述流程也完全符合AUTOSAR开发流程。包含CAN、LIN早期协议栈是GENY做配置,现在是用Dawinciconfigurator定义pdur、CANdRIVER。当前 Bootloader 中也包含了一些非AUTOSAR定义的内容,这个时候需要由 Bootloader软件服务商自己OEM特定需求做定制化开发。也就是日常所说的购买成熟协议栈使用。
4、Bootmanager为芯片一级boot,是ECU 上电后第一个执行的代码,由该模块负责ECU该往那个代码模块跳比如进入 Bootloader还是Application(常见刷写成功后置位Flash位作为下次启动的判定标准)当需要对FlashBootloader做更新时,就需要由Bootloader Updater 来实现,其实这个逻辑也可以对照SBL/PBL,当有Super SBL时,也可以允许对PBL做擦除。
5、为了完成刷写功能,用于UI界面加载Flash Driver/Flash Data,也执行刷写template定义刷洗序列。

FBL和Application之间的跳转ECU上电后,代码运行会进入到FBL中,做相应的一些ECU初始化,初始化完成后会进入判断刷写标志位的一个步骤。若刷写标志位有效,进入到Boot 中,若标志位无效,进入App 中。FBL跳转至ApplicationFBL初始化
A:检查刷写标志位
B:检查Application的有效标志位
C:跳转至Application的入口
D:执行Application的启动代码

Application跳转至FBL
Application 运行中接收到诊断仪发出的诊断刷写请求
Application 将刷写请求标志位存入NVM 或者RAM中
Application 执行复位操作
FBL检查诊断刷写标志位

另外关于Stay In Boot功能:
在Application无法接收和处理诊断请求时,避免ECU无法进行刷写更新并不是所有的OEM都会用到,通常情况下,用于研发阶段,量产中可能也会用(影响启动时间)。该功能应用场景是在某一次更新当中把新的APP更新到ECU中。此时APPdata没有经过很充分的认证导致APP 软件中有 bug,比如有死循环 (While)。每一次ECU 上电,先进入boot,在进入app,死循环无法与诊断仪(刷写上位机)做诊断数据交互,发送请求也没有响应。
这个时候通过其他方式来规避这个风险,vFlash也支持,对ECU启动时间有影响。

当收到Stay in boot message进入Stay in boot状态,避免ECU编程板砖。



关于ECU安全启动
对于Flash Data会存在片内或片外,会有可能被不法分子做篡改的风险。
通过安全启动获知该ECU 是否App data 是否被篡改、是否无效等,保证车辆安全ECU一上电,通过HSM,HSM 可以理解位信息安全的加密引擎,对FBL做源码校验,通过特定算法和 key 计算出一个 MAC 值(消息认证码)。这个消息认证码与原本存在芯片中的消息认证码进行比较:
若一样,FBL valid。不一样:FBL被算改了!
同样策略也可以对APP做mac检验,开启ECU时计算一个MAC值对比ECU内部存储的就可以确认APP是否有效安全启动每一家芯片供应商都有自己的做法
HSM(硬件安全模块)对于实现AUTOSAR系统的加密保护起着重要作用。随着汽车电子控制单元(ECU)连通度的提高,安全和防范外部威胁的重要性日益增加。HSM具有适当的固件,即使在资源不足的情况下,也能保证系统的加密。此外,HSM还允许在单独的处理器上计算密码,以满足实时需求。因此,HSM在AUTOSAR系统中发挥着关键作用,确保了系统的安全性和实时性能。

为了传输数据增加速度,会在刷写过程中支持压缩数据传输。这点功能的本质是降低数据传输时间,在Server端会有还原步骤,整体刷写时间增加还是降低主要看还原时间。

数据传输步骤如下图:

原则上对于Flash Bootloader只能在供应商下线时候做烧录下载后便不再更新,但是极少特殊情况也是可以对Flash Bootloadr进行更新。

更新步骤如下:

搁笔分享完毕!

愿你我相信时间的力量
做一个长期主义者!
相关文章:
电子电器架构刷写方案——General Flash Bootloader
电子电器架构刷写方案——General Flash Bootloader 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 注:文章1万字左右,深度思考者入!!! 老规矩,分享一段喜欢的文字,避免…...
【Linux】僵尸与孤儿 进程等待
目录 一,僵尸进程 1,僵尸进程 2,僵尸进程的危害 二,孤儿进程 1,孤儿进程 三,进程等待 1,进程等待的必要性 2,wait 方法 3,waitpid 方法 4,回收小结…...
Java小案例-Sentinel的实现原理
前言 Sentinel是阿里开源的一款面向分布式、多语言异构化服务架构的流量治理组件。 主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 核心概念 要想理解一个新的技…...
【Leetcode Sheet】Weekly Practice 21
Leetcode Test 1901 寻找峰值Ⅱ(12.19) 一个 2D 网格中的 峰值 是指那些 严格大于 其相邻格子(上、下、左、右)的元素。 给你一个 从 0 开始编号 的 m x n 矩阵 mat ,其中任意两个相邻格子的值都 不相同 。找出 任意一个 峰值 mat[i][j] 并 返回其位置 [i,j] 。 …...
C语言使用qsort和bsearch实现二分查找
引言 在计算机科学领域,查找是一项基本操作,而二分查找是一种高效的查找算法。本博客将详细解释一个简单的C语言程序,演示如何使用标准库函数qsort和bsearch来对一个整数数组进行排序和二分查找。 代码解析 包含头文件 #include <stdi…...
MySQL的替换函数及补全函数的使用
前提: mysql的版本是8.0以下的。不支持树形结构递归查询的。但是,又想实现树形结构的一种思路 提示:如果使用的是MySQL8.0及其以上的,想要实现树形结构,请参考:MySQL数据库中,如何实现递归查询…...
2022第十二届PostgreSQL中国技术大会-核心PPT资料下载
一、峰会简介 本次大会以“突破•进化•共赢 —— 安全可靠,共建与机遇”为主题,助力中国数据库基础软件可掌控、可研究、可发展、可生产,并推动数据库生态的繁荣与发展。大会为数据库从业者、数据库相关企业、数据库行业及整个IT产业带来崭…...
2024 年 10大 AI 趋势
2025 年,全球人工智能市场预计将达到惊人的 1906.1 亿美元,年复合增长率高达 36.62%。 人工智能软件正在迅速改变我们的世界,而且这种趋势在未来几年只会加速。 我们分析了未来有望彻底改变 2024 年的 10 个AI趋势。从生成式人工智能的兴起到…...
Uboot
什么是Bootloader? Linux系统要启动就必须需要一个 bootloader程序,也就说芯片上电以后先运行一段bootloader程序。 这段 **bootloader程序会先初始化时钟,看门狗,中断,SDRAM,等外设,然后将 Linux内核从f…...
ECMAScript 的未来:预测 JavaScript 创新的下一个浪潮
以下是简单概括关于JavaScript知识点以及一些目前比较流行的比如:es6 想要系统学习: 大家有关于JavaScript知识点不知道可以去 🎉博客主页:阿猫的故乡 🎉系列专栏:JavaScript专题栏 🎉ajax专栏&…...
代码随想录算法训练营第十三天 | 239. 滑动窗口最大值、347.前 K 个高频元素
239. 滑动窗口最大值 题目链接:239. 滑动窗口最大值 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 文章讲解…...
推荐五个免费的网络安全工具
导读: 在一个完美的世界里,信息安全从业人员有无限的安全预算去做排除故障和修复安全漏洞的工作。但是,正如你将要学到的那样,你不需要无限的预算取得到高质量的产品。这里有SearchSecurity.com网站专家Michael Cobb推荐的五个免费…...
Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记
Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记 Abstract 无人机在各种应用中得到了广泛使用,例如航拍和军事安全,这得益于它们与固定摄像机相比的高机动性和广阔视野。多无人机追踪系统可以通过从不同视角收集互补的…...
【LeetCode刷题笔记】动态规划(二)
647. 回文子串 解题思路: 1. 暴力穷举 , i 遍历 [0, N) , j 遍历 [i+1, N] ,判断每一个子串 s[i, j) 是否是回文串,判断是否是回文串可以采用 对撞指针 的方法。如果是回文串就计数 +1...
(十七)Flask之大型项目目录结构示例【二扣蓝图】
大型项目目录结构: 问题引入: 在上篇文章讲蓝图的时候我给了一个demo项目,其中templates和static都各自只有一个,这就意味着所有app的模板和静态文件都放在了一起,如果项目比较大的话,这就非常乱…...
蓝牙技术在物联网中的应用
随着蓝牙技术的不断演进和发展,蓝牙已经从单一的传统蓝牙技术发展成集传统蓝牙。高速蓝牙和低耗能蓝牙于一体的综合技术,不同的应用标准更是超过40个越来越广的技术领域和越来越多的应用场景,使得目前的蓝牙技术成为包含传感器技术、识别技术…...
宝塔面板Linux服务器CentOS 7数据库mysql5.6升级至5.7版本教程
近段时间很多会员问系统更新较慢,也打算上几个好的系统,但几个系统系统只支持MYSQL5.7版本,服务器一直使用较低的MYSQL5.6版本,为了测试几个最新的系统打算让5.6和5.7并存使用,参考了多个文档感觉这种并存问题会很多。…...
掌握常用Docker命令,轻松管理容器化应用
Docker是一个开源的应用容器引擎,它可以让开发者将应用程序及其依赖打包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器或Windows机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。下面介…...
【数据结构1-2】P5076 普通二叉树(简化版)(c++,multiset做法)
文章目录 一、题目【深基16.例7】普通二叉树(简化版)题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1基本思路: 一、题目 【深基16.例7】普通二叉树(简化版) 题目描述 您需要写一种数据结构,来维…...
Linux系统安装及管理
目录 一、Linux应用程序基础 1.1应用程序与系统命令的关系 1.2典型应用程序的目录结构 1.3常见的软件包装类型 二、RPM软件包管理 1.RPM是什么? 2.RPM命令的格式 2,1查看已安装的软件包格式 2.2查看未安装的软件包 3.RPM安装包从哪里来? 4.挂…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...
