固态存储设备固件升级方案

news2024/11/27 9:40:40

1. 前言

随着数字化时代的发展,数字数据的量越来越大,相应的数据存储的需求也越来越大,存储设备产业也是蓬勃发展。存储设备产业中,发展最为迅猛的则是固态存储(Solid State Storage,SSS)。数字化时代,海量的数据,需要海量的存储设备。可以说,固态存储设备是数字化时代最重要的基础设施。
为了解决发现的Bug,安全漏洞,或者为了提升性能,固态存储设备也有升级其固件的需求。固态存储设备种类繁多,有可以随身携带的U盘、TF卡,也有手机中的eMMC,UFS,还有电脑中的固态硬盘(SSD),更有各种云背后的分布式存储系统中大量使用的各种固态存储设备。不同应用场景,其使用方式也不同,那么相应的升级方式也有可能不同。本文尝试全面介绍一下不同应用场景下的固态存储设备的固件升级方案。

2. 固态存储设备

固态存储设备的主要构成为存储介质,控制芯片和一些外置电子元器件。固态存储设备的主要存储介质为非易失性存储器,其主要为NAND Flash。控制芯片的主要作用是连接NAND Flash和主机存储接口,并管理存储在NAND Flash上的数据。
主机存储接口不同,应用的场景也不同。其主要分为:
● USB接口,主要用于移动存储;
● SD接口,主要用于小型电子设备,如相机,监控设备等;
● eMMC接口,主要用于手机、平板和一些嵌入式设备,如智能电视,车机等;
● UFS接口,主要用于手机;
● SATA接口,主要用于个人电脑;
● PCIe接口,主要用于对性能有更高要求的企业级存储,如分布式存储,云存储等。

3. 升级方案

存储设备是否支持固件升级,需要两个方面的支持,存储设备固件算法支持升级,并且有升级工具。

3.1. 固件算法

存储设备升级成功,或者万一在升级过程中断电,都不能影响存储设备上之前存放的用户数据。升级算法设计要点:
● 用户数据的正确性,即L2P表(存储设备逻辑到NAND Flash物理地址映射表)不能发生改变。
○ FTL算法设计的时候,需要将L2P表等影响用户数据的算法数据结构和其他算法数据结构分块存储。因为升级固件,必须重新写入算法二进制文件和重新配置Boot信息,因为NAND Flash的块必须擦除了才能写,所以固件二进制文件和Boot信息必须和L2P表分块存储。
● 异常行为的安全性,即升级过程中,发生异常,重新上电时,能够恢复原有的状态。
○ 升级的操作过程是日志型的,每一步操作都有记录,只有最后升级成功,并且有CheckSum校验机制来保证操作的完整性。
○ 升级算法的设计,新旧算法都保留,每次升级成功之后,将新算法头中索引加1,这样每次启动后,检测到两个算法,并通过CheckSum校验算法的完整性,最后比较算法头中的索引值,启动索引较大的算法。

3.2. 升级流程

3.2.1. 移动存储设备

移动存储设备U盘/TF卡,基本都可以通过USB接口接入电脑,电脑的操作系统主要包括Windows和Linux。所以针对移动存储设备,主要考虑制作系统软件来应对此类升级。为了减少软件开发工作,建议使用跨平台方案,保证最大限度的复用代码。为了更好地操作底层API,并且有效率的开发软件,采用C++作为软件开发语言,选用Qt作为跨平台的开发框架,并且都采用g++编译器。无论是在Linux平台,还是在Windows平台,升级的基本流程基本是相同的。如下图:
在这里插入图片描述

抽象出Linux和Windows下不同点,统一抽象的接口,然后复用宏WIN32区分不同系统,主要包括:

  1. 设备的标识,Windows下以盘符(E:\,F:\等)作为标识,Linux下则以设备路径(/dev/sdb, /dev/sdc等)作为标识。
  2. 文件路径,Windows下以反斜杠\作为分隔符,Linux下则以斜杠/作为分隔符。
  3. 和设备通信,和USB设备通信,数据层是采用SCSI协议,传输层采用的USB协议。应用软件直接采用SCSI协议与设备进行通信即可。
    a. Windows层的通信主要代码:
// Open device
char szLetter[] = "\\.\G:;
HANDLE hDev = CreateFile((LPCSTR)_devFile, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

// Transport CMD to device
if (!DeviceIoControl(hDev,
                    IOCTL_SCSI_PASS_THROUGH,
                    pt,
                    sizeof(buf),
                    buf,
                    sizeof(buf),
                    &bytes,
                    NULL)) {
                printf("IOCTL failed %d\n", GetLastError());
            }

// Close device
CloseHandle(hDev);

b. Linux下通信的主要代码:

// Open device
int fd = open("/dev/sde", O_RDWR);

// Transport CMD
unsigned char buff[1024] = {0};
unsigned char inq_cmd[] = {WRITE_10, 00, 0, 0, 0, 0, 0, 0, 0x2, 0};
unsigned char sense[32] = {0};
struct sg_io_hdr io_hdr = {};
io_hdr.interface_id = 'S';
io_hdr.cmdp = inq_cmd;
io_hdr.cmd_len = sizeof(inq_cmd);
io_hdr.dxferp = buff;
io_hdr.dxfer_len = 32;
io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
io_hdr.sbp = sense;
io_hdr.mx_sb_len = sizeof(sense);
io_hdr.timeout = 5000;
ioctl(fd, SG_IO, &io_hdr);

// Close device
close(fd);

3.2.2. eMMC/UFS

eMMC/UFS主要用于手机、平板和一些嵌入式设备,手机和平板基本是Andriod系统,嵌入式也多是Andriod和Linux。而Andriod的内核也基本上就是Linux内核了。Andriod因为安全机制,不允许应用程序直接与存储储备通信,所有的数据传输都是加密的,所以Andriod也不方便通过应用程序来进行升级固件。
无论是Andriod还是嵌入式Linux,都是通过u-boot来启动系统的。u-boot中都已经携带有eMMC/UFS的驱动代码,并且u-boot中都会初始化eMMC/UFS,然后读取存放在eMMC/UFS上的系统进行引导启动。所以在u-boot中进行固件升级,不仅可以绕开Andriod的安全机制,升级方式也可以与Linux统一。
eMMC自从协议4.0版之后,协议提出统一的固件更新规则FFU(Field Firmware Update,现场固件更新)。其基本流程为Host发送命令进入FFU模式,通过写命令将固件bin文件写入设备存储在NAND Flash相应位置,然后Host发送激活命令、或硬件复位或者断上电操作,就可以完成固件更新。
其协议流程为:
在这里插入图片描述在这里插入图片描述

u-boot流程,修改完u-boot之后,编译成二进制文件,然后与Andriod文件一起推送到手机终端,等待用户升级系统过程中,先完成对eMMC/UFS的升级。

// 在mmc_inti之后执行如下流程
mmc_set_clock(mmc, mmc->tran_speed,MMC_CLK_ENABLE);
mmc_switch(mmc, FFU_MODE);
mmc_write(mmc, addr, buff, size);
mmc_switch(mmc, NORMAL_MODE);
mmc_power_cycle(mmc);

UFS协议继承了eMMC的FFU,其流程基本一样,只不过在激活新固件时,简化了eMMC原有的方式,只支持HW Reset或Power Cycle。
在这里插入图片描述

3.2.3. SATA SSD

SATA接口的SSD主要用于个人电脑。如果SSD作为电脑的从盘(非操作系统盘),则可以直接使用系统软件。如果SSD作为电脑的主盘(操作系统盘),由于操作系统的限制,无法直接与主盘通信。操作系统有MAC、Windows、Linux等,而且cpu内核也有x86/x86-64/Arm等,如果要编译系统软件,会有很多版本。有没有一种通用的方法兼容所有情况呢?
为了解决主盘无法直接升级,需要另外接入系统,让主盘作为从盘。

3.2.3.1. UEFI应用

当前电脑系统的启动都是通过UEFI来引导启动操作系统的,可以考虑编写UEFI应用程序,通过UEFI程序来完成对SSD固件的更新。

3.2.3.2. WinPE应用

现在安装系统,都是使用U盘来完成系统,先在U盘中安装一个Win PE的启动系统来引导安装操作系统。Win PE是一个简化版的Windows系统,可以运行基本的Windows应用程序。这种方式可以不用管电脑原来是什么操作系统,只需要针对不同CPU制作不同的Win PE启动盘即可。
● 升级工具:
SATA接口的SSD使用ATA协议来通信,并且兼容SCSI的通用命令。
可以通过SCSI的3种操作码,来配置3种ATA通信协议。

主要通信代码:

SCSI_PASS_THROUGH spt = {0};
spt.Length = sizeof(SCSI_PASS_THROUGH);
spt.TimeOutValue = 2;
spt.CdbLength = 12; // 16,32
spt.Cdb[0] = 0xA1;  // 0x85,0x7F
spt.Cdb[1] = 3 << 1;
memcpy(&spt.Cdb[3], aptex.CurrentTaskFile, 8);
ret = DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH,
                      &spt, sizeof(SCSI_PASS_THROUGH), NULL, 0, &nRet, NULL);
也可以直接通过ATA协议来与设备通信,其主要代码:
ATA_PASS_THROUGH_EX aptex = {0};
aptex.Length = sizeof(ATA_PASS_THROUGH_EX);
aptex.TimeOutValue = 2;
aptex.CurrentTaskFile[6] = 0xEF;
aptex.CurrentTaskFile[0] = 0x05;
aptex.CurrentTaskFile[1] = 0x80;
ret = DeviceIoControl(handle, IOCTL_ATA_PASS_THROUGH, &aptex, 
                      sizeof(ATA_PASS_THROUGH_EX), NULL, 0, &nRet, NULL);

● WinPE系统盘

  1. 制作WinPE需要安装ADK,不同版本WinPE对应不同的ADK,下载合适的ADK版本安装。
  2. 以管理员身份启动“部署和映像工具环境” 。
  3. 运行“copype”以创建 Windows PE 文件的工作副本。
    // 提取64位的WinPE资源文件
    copype amd64 C:\WinPE_amd64
    // 提取32位的WinPE资源文件
    copype x86 C:\WinPE_x86
  4. 提取镜像中文件
  5. 在WinPE中添加固件更新工具。
    把fw_update_tool.exe放在mout\Program Files目录。
  6. 启动WinPE之后自动执行升级工具。
    wpeinit
    cd …
    cd “Program Files\FWupdateTool”
    FWupdateTool.exe
  7. 提交修改
    将相关的修改提交到新的winpe.wim中,并卸载所有提取的文件。
    Dism /unmount-Wim /MountDir:C:\winpe_x86\mount /Commit
  8. 拷贝Winpe.wim至IOS目录
    copy winpe.wim C:\winpe_x86\ISO\sources\boot.wim /y
  9. 生成镜像文件
    oscdimg -n -bC:\winpe_x86\etfsboot.com C:\winpe_x86\iso C:\winpe.iso
  10. 利用Ultraiso写入镜像
    利用Ultraiso打开Winpe.iso,然后菜单选择启动->写入硬盘映像,选择指定U盘,点击确认,等待完成,winpe启动盘即制作完成。
    也可以使用命令行制作WinPE系统:
set PEPath=C:\win10PE_x86
call "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\DandISetEnv.bat"
if exist %PEPath%  rmdir /s /q %PEPath%
call CopyPE.cmd x86 %PEPath%
Dism /Mount-Image /ImageFile:%PEPath%\media\sources\boot.wim /index:1 /MountDir:%PEPath%\mount
pause
Dism /Unmount-Image /MountDir:%PEPath%\mount /commit
rem MakeWinPEMedia /ISO %PEPath% %PEPath%\Win10PE_x86.ISO
MakeWinPEMedia /ufd %PEPath% g:

3.2.4. PCIe SSD

PCIe接口的SSD主要用高性能场景,如高价个人电脑,还有云存储、企业分布式存储等。PCIe接口的SSD采用NVMe通信协议,协议中也有规定固件升级流程,和FFU流程大同小异,下载固件,激活固件。
● Windows下的实现流程:

BOOL DeviceStorageFirmwareUpgrade(int _nPhyNo, BYTE _slotID)
{
    QString strDeviceName = QString("%1%2").arg("\\\\.\\Physicaldrive").arg(_nPhyNo);
    HANDLE deviceHandle = CreateFile(strDeviceName.toStdWString().data(),
                              GENERIC_READ | GENERIC_WRITE,
                              FILE_SHARE_READ | FILE_SHARE_WRITE,
                              nullptr,
                              OPEN_EXISTING,
                              FILE_FLAG_NO_BUFFERING,
                              nullptr
                              );
    if (deviceHandle == INVALID_HANDLE_VALUE)
    {
        return  FALSE;
    }

    // Setup header of firmware download data structure.
    const DWORD dwBuffSize = FIELD_OFFSET(STORAGE_HW_FIRMWARE_DOWNLOAD, ImageBuffer) + 4*1024;
    QScopedPointer<char> scopeBuff(new char[dwBuffSize]());
    PSTORAGE_HW_FIRMWARE_DOWNLOAD firmwareDownload = reinterpret_cast<PSTORAGE_HW_FIRMWARE_DOWNLOAD>(scopeBuff.get());

    firmwareDownload->Version = sizeof(STORAGE_HW_FIRMWARE_DOWNLOAD);
    firmwareDownload->Size = dwBuffSize;
    firmwareDownload->Flags = STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER;
    firmwareDownload->Slot = 0x11;

    // Open image file and download it to controller.
    ULONGLONG imageBufferLength = dwBuffSize - FIELD_OFFSET(STORAGE_HW_FIRMWARE_DOWNLOAD, ImageBuffer);

    QString strFilePath = ":/libra_cpu01_sysfw.bin";
    QFile file(strFilePath);
    if (!file.open(QIODevice::ReadOnly))
    {
        qDebug()<<"Open "<<strFilePath<<" failed.";
        return  FALSE;
    }

    ULONG imageOffset = 0;
    BOOL moreToDownload = TRUE;
    while (moreToDownload)
    {
        RtlZeroMemory(firmwareDownload->ImageBuffer, imageBufferLength);
        qint64 readLength = file.read(reinterpret_cast<char*>(firmwareDownload->ImageBuffer), static_cast<qint64>(imageBufferLength));
        if (readLength == 0)
        {
            file.close();
            break;
        }

        firmwareDownload->Offset = imageOffset;
        firmwareDownload->BufferSize = min(imageBufferLength, static_cast<ULONG>(readLength));
        ULONG returnedLength = 0;
        BOOL result = DeviceIoControl(deviceHandle,
                                 IOCTL_STORAGE_FIRMWARE_DOWNLOAD,
                                 scopeBuff.get(),
                                 dwBuffSize,
                                 scopeBuff.get(),
                                 dwBuffSize,
                                 &returnedLength,
                                 nullptr);

        if (!result)
        {
            return FALSE;
        }

        imageOffset += static_cast<ULONG>(firmwareDownload->BufferSize);
    }

    // Activate the newly downloaded image.
    RtlZeroMemory(scopeBuff.get(), dwBuffSize);

    PSTORAGE_HW_FIRMWARE_ACTIVATE firmwareActivate = reinterpret_cast<PSTORAGE_HW_FIRMWARE_ACTIVATE>(scopeBuff.get());
    firmwareActivate->Version = sizeof(STORAGE_HW_FIRMWARE_ACTIVATE);
    firmwareActivate->Size = sizeof(STORAGE_HW_FIRMWARE_ACTIVATE);
    firmwareActivate->Slot = _slotID;
    firmwareActivate->Flags = STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER;

    // activate firmware
    ULONG returnedLength = 0;
    BOOL result = DeviceIoControl(deviceHandle,
                             IOCTL_STORAGE_FIRMWARE_ACTIVATE,
                             scopeBuff.get(),
                             dwBuffSize,
                             scopeBuff.get(),
                             dwBuffSize,
                             &returnedLength,
                             nullptr
                             );

    return result;
}

● Linux下有开源工具NVME-cli,可以直接使用:

> nvme fw-download /dev/nvme0 -f allBinary.bin nvme fw-commit /dev/nvme0
> -s 2 -a 1
> # 参数-s代表slot。标准定义SSD支持7个slot,slot 1 是只读权限,用于存放出厂固件,slot 2和3 可用于固件下载。
> # 参数-a代表不同的升级方法,常用的有两个。001b(向指定slot下载固件,需要reset后完成激活操作);
> # 011b(向指定slot下载固件,激活立即生效,固件升级完成)

4. 参考资料

  1. NVM Express TM Revision 1.4
  2. Information technology -SCSI / ATA Translation - 5 (SAT-5)
  3. ATA Command Pass-Through
  4. Universal Serial Bus Mass Storage Class UFI Command Specification
  5. Universal Flash Storage (UFS)Version 3.1
  6. Embedded Multi-Media Card (e•MMC) Electrical Standard (5.1)
  7. https://github.com/linux-nvme/nvme-cli/
  8. https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-ioctl_storage_firmware_activate
  9. https://learn.microsoft.com/zh-cn/windows-hardware/manufacture/desktop/winpe-intro?view=windows-11
  10. https://github.com/u-boot/u-boot

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402690.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(新的开篇)”】

目录【ESP32freeRTOS学习笔记】系列新的开篇ESP-IDF对FreeRTOS的适配ESP-IDF环境中使用FreeRTOS的差异性简介关于FreeRTOS的配置关于ESP-IDF FreeRTOS Applications结语【ESP32freeRTOS学习笔记】系列新的开篇 ESP-IDF对FreeRTOS的适配 FreeRTOS是一个可以适用于多个不同MCU开…

JavaWeb开发(三)3.6——代理模式

一、代理模式概述 1.1、代理模式的理解 参考人家的举例&#xff0c;感觉挺形象&#xff0c;容易理解&#xff1a; 就拿明星与经纪人的关系来说&#xff0c;明星就好比被代理类&#xff0c;明星只需要负责唱歌&#xff0c;表演或给粉丝签名等事务&#xff0c;而类似签合同&…

Jenkins从配置到实践

Jenkins从配置到实践 1 持续集成 Continuous integration&#xff08;CI&#xff09; 1.1 什么是持续集成&#xff1f; 持续集成Continuous integration&#xff08;CI&#xff09;是一种软件开发实践&#xff0c;即团队开发成员经常集成他们的工作&#xff0c;通常每个成员…

COLMAP

简介&#xff1a;在使用instant-ngp过程中需要使用COLMAP得到模型的必要输入&#xff0c;比如模型需要的相机外参我们就可以通过COLMAP中的sparse reconstruction稀疏重建得到&#xff1b;而对于depth map深度图我们则需要dense reconstruction稠密重建得到&#xff0c;下面我们…

STM32Cube STM32MP157 M4端CAN通讯实战

1、环境 开发系列&#xff1a;STM32MP157 开发软件&#xff1a;STM32CubeIDE 1.4.0 例程目的&#xff1a;在M4端实现CAN通讯 2、目的 近日&#xff0c;有客户需要在STM32MP157中的M4端实现CAN通讯&#xff0c;我也是初次在M4端编写CAN通讯代码&#xff0c;上网研究了其他人写…

从LeNet到ResNet:深入探索卷积神经网络

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

论文解读15——LightGBM: A Highly Efficient Gradient Boosting Decision Tree

目录1、文章贡献2、直方图算法Histogram&#xff08;减少分裂点&#xff09;3、基于梯度的单边采样算法GOSS&#xff08;减少样本量&#xff09;4、互斥特征捆绑算法EFB&#xff08;减少特征&#xff09;在上篇中提到&#xff0c;XGBoost算法的局限是它在寻找最优分裂点算法中需…

AidLux AI应用案例悬赏选题 | 电路板表面瑕疵检测

AidLux AI 应用案例悬赏征集活动 AidLux AI 应用案例悬赏征集活动是AidLux推出的AI应用案例项目合作模式&#xff0c;悬赏选题将会持续更新。目前上新的选题涉及泛边缘、机器人、工业检测、车载等领域&#xff0c;内容涵盖智慧零售、智慧社区、智慧交通、智慧农业、智能家居等…

正点原子裸机开发之C语言点灯程序

一. 简介 本文针对 IMX6ULL 的裸机开发的&#xff08;即不带Linux操作系统的开发&#xff09;。 主要分两部分的工作&#xff1a; 1. 配置 C语言运行环境 2. C 语言编写及运行 二. 配置C语言运行环境 配置 C 语言运行环境的工作分 三部分。如下&#xff1a; 1. 设置…

Nginx配置实例-反向代理案例一

实现效果&#xff1a;使用nginx反向代理&#xff0c;访问 www.suke.com 直接跳转到本机地址127.0.0.1:8080 一、准备工作 Centos7 安装 Nginxhttps://liush.blog.csdn.net/article/details/125027693 1. 启动一个 tomcat Centos7安装JDK1.8https://liush.blog.csdn.net/arti…

YonBuilder 应用构建全新入门指南

用友开发者中心以 YonBuilder 低代码开发为核心&#xff0c;提供可视化 低代码 全代码的一站式开发能力&#xff0c;企业组织和个人开发者可实现业务应用的快速开发。YonBuilder 基于用友 BIP 强大的中台支撑能力&#xff0c;在元数据驱动和运行框架的统一模型架构下&#xf…

Ethercat学习-GD32以太网学习

文章目录1、GD32F4以太网简介2、以太网模框图简介3、以太网主要模块介绍SMI接口RMII接口与MII接口DMA控制器4、以太网配置流程5、其他1、GD32F4以太网简介 GD32F4系列以太网模块包含10/100Mbps以太网MAC&#xff0c;数据的收发都通过DMA进行操作&#xff0c;支持MII&#xff0…

一个C#开发的开源的快速启动工具

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 平常计算机安装软件比较多、或者工作涉及的文件比较多&#xff0c;很多人都会直接放在桌面&#xff0c;一方面不安全&#xff0c;还不容易查找&#xff0c;这时候我们往往&#xff0c;都会放在其他硬盘内&#x…

springboot项目yml文件中${}的使用

作用 项目启动时可以灵活的通过修改环境变量来替换配置中的值&#xff0c;如果没有传该环境变量时&#xff0c;就是用默认值&#xff1b; 格式&#xff1a;${自定义参数名:默认值} 代码举例&#xff0c;已开启应用的端口号为例&#xff1a; server:port: ${SERVER_PORT:960…

AI+人类,实现高效网络安全

导语 聊天机器人和生成式人工智能&#xff08;如 ChatGPT&#xff09;突然成为主流让很多人感到担忧。很多人开始担忧&#xff0c;人工智能取代人的时代已经到来。 幸运的是&#xff0c;事实并非如此。 更有可能的情况是&#xff0c;人类将与 AI 合作创建工作角色的混合模型。…

Mercedes-Benz EDI需求分析

作为奔驰的仓储服务提供商&#xff0c;企业A需要借助EDI实现仓储流程的自动化。奔驰将车存放在企业A处&#xff0c;由企业A将货物提供给4S店。整体业务流程中将通过EDI系统来进行业务数据的传输&#xff0c;今天的文章主要从奔驰EDI需求概览以及EDI项目业务流程两方面展开介绍。…

646. 最长数对链——【Leetcode每日刷题】

646. 最长数对链 给你一个由 n 个数对组成的数对数组 pairs &#xff0c;其中 pairs[i][lefti,righti]pairs[i] [left_i, right_i]pairs[i][lefti​,righti​] 且lefti<rightileft_i < right_ilefti​<righti​。 现在&#xff0c;我们定义一种 跟随 关系&#xff…

MySQL查询索引原则

文章目录 等值匹配原则最左前缀匹配原则范围查找规则等值匹配+范围查找Order By + limit 优化分组查询优化总结MySQL 是如何帮我们维护非主键索引的等值匹配原则 我们现在已经知道了如果是【主键索引】,在插入数据的时候是根据主键的顺序依次往后排列的,一个数据页不够就会分…

保姆级使用PyTorch训练与评估自己的MobileViT网络教程

文章目录前言0. 环境搭建&快速开始1. 数据集制作1.1 标签文件制作1.2 数据集划分1.3 数据集信息文件制作2. 修改参数文件3. 训练4. 评估5. 其他教程前言 项目地址&#xff1a;https://github.com/Fafa-DL/Awesome-Backbones 操作教程&#xff1a;https://www.bilibili.co…

2023最新ELK日志平台(elasticsearch+logstash+kibana)搭建

去年公司由于不断发展&#xff0c;内部自研系统越来越多&#xff0c;所以后来搭建了一个日志收集平台&#xff0c;并将日志收集功能以二方包形式引入自研系统&#xff0c;避免每个自研系统都要建立一套自己的日志模块&#xff0c;节约了开发时间&#xff0c;管理起来也更加容易…