固态存储设备固件升级方案
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区分不同系统,主要包括:
- 设备的标识,Windows下以盘符(E:\,F:\等)作为标识,Linux下则以设备路径(/dev/sdb, /dev/sdc等)作为标识。
- 文件路径,Windows下以反斜杠\作为分隔符,Linux下则以斜杠/作为分隔符。
- 和设备通信,和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系统盘
- 制作WinPE需要安装ADK,不同版本WinPE对应不同的ADK,下载合适的ADK版本安装。
- 以管理员身份启动“部署和映像工具环境” 。
- 运行“copype”以创建 Windows PE 文件的工作副本。
// 提取64位的WinPE资源文件
copype amd64 C:\WinPE_amd64
// 提取32位的WinPE资源文件
copype x86 C:\WinPE_x86 - 提取镜像中文件
- 在WinPE中添加固件更新工具。
把fw_update_tool.exe放在mout\Program Files目录。 - 启动WinPE之后自动执行升级工具。
wpeinit
cd …
cd “Program Files\FWupdateTool”
FWupdateTool.exe - 提交修改
将相关的修改提交到新的winpe.wim中,并卸载所有提取的文件。
Dism /unmount-Wim /MountDir:C:\winpe_x86\mount /Commit - 拷贝Winpe.wim至IOS目录
copy winpe.wim C:\winpe_x86\ISO\sources\boot.wim /y - 生成镜像文件
oscdimg -n -bC:\winpe_x86\etfsboot.com C:\winpe_x86\iso C:\winpe.iso - 利用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 firmwareULONG 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. 参考资料
- NVM Express TM Revision 1.4
- Information technology -SCSI / ATA Translation - 5 (SAT-5)
- ATA Command Pass-Through
- Universal Serial Bus Mass Storage Class UFI Command Specification
- Universal Flash Storage (UFS)Version 3.1
- Embedded Multi-Media Card (e•MMC) Electrical Standard (5.1)
- https://github.com/linux-nvme/nvme-cli/
- https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-ioctl_storage_firmware_activate
- https://learn.microsoft.com/zh-cn/windows-hardware/manufacture/desktop/winpe-intro?view=windows-11
- https://github.com/u-boot/u-boot
相关文章:

固态存储设备固件升级方案
1. 前言 随着数字化时代的发展,数字数据的量越来越大,相应的数据存储的需求也越来越大,存储设备产业也是蓬勃发展。存储设备产业中,发展最为迅猛的则是固态存储(Solid State Storage,SSS)。数字化时代,海量…...

Python交通标志识别基于卷积神经网络的保姆级教程(Tensorflow)
项目介绍 TensorFlow2.X 搭建卷积神经网络(CNN),实现交通标志识别。搭建的卷积神经网络是类似VGG的结构(卷积层与池化层反复堆叠,然后经过全连接层,最后用softmax映射为每个类别的概率,概率最大的即为识别…...

基于Selenium+Python的web自动化测试框架(附框架源码+项目实战)
目录 一、什么是Selenium? 二、自动化测试框架 三、自动化框架的设计和实现 四、需要改进的模块 五、总结 总结感谢每一个认真阅读我文章的人!!! 重点:配套学习资料和视频教学 一、什么是Selenium? …...

Python进阶-----高阶函数zip() 函数
目录 前言: zip() 函数简介 运作过程: 应用实例 1.有序序列结合 2.无序序列结合 3.长度不统一的情况 前言: 家人们,看到标题应该都不陌生了吧,我们都知道压缩包文件的后缀就是zip的,当然还有r…...

win10打印机拒绝访问解决方法
一直以来,在安装使用共享打印机打印一些文件的时候,会遇到错误提示:“无法访问.你可能没有权限使用网络资源。请与这台服务器的管理员联系”的问题,那为什么共享打印机拒绝访问呢?别着急,下面为大家带来相关的解决方法…...

深度学习训练营之数据增强
深度学习训练营学习内容原文链接环境介绍前置工作设置GPU加载数据创建测试集数据类型查看以及数据归一化数据增强操作使用嵌入model的方法进行数据增强模型训练结果可视化自定义数据增强查看数据增强后的图片学习内容 在深度学习当中,由于准备数据集本身是一件十分复杂的过程,…...

Tomcat安装及启动
日升时奋斗,日落时自省 目录 1、Tomcat下载 2、JDK安装及配置环境 3、Tomcat配置环境 4、启动Tomcat 5、部署演示 1、Tomcat下载 直接入主题,下载Tomcat 首先就是别下错了,直接找官方如何看是不是广告,或者造假 搜索Tomc…...

【专项训练】排序算法
排序算法 非比较类的排序,基本上就是放在一个数组里面,统计每个数出现的次序 最重要的排序是比较类排序! O(nlogn)的3个排序,必须要会!即:堆排序、快速排序、归并排序! 快速排序:分治 经典快排 def quickSort1(arr...
Android压测测试事件行为参数对照表
执行参数参数说明颗粒度指标基础参数--throttle <ms> 用于指定用户操作间的时延。 -s 随机数种子,用于指定伪随机数生成器的seed值,如果seed值相同,则产生的时间序列也相同。多用于重测、复现问题。 -v 指定输出日志的级别,…...

【观察】亚信科技:“飞轮效应”背后的数智化创新“延长线”
著名管理学家吉姆柯林斯在《从优秀到卓越》一书中提出“飞轮效应”,它指的是为了使静止的飞轮转动起来,一开始必须使很大的力气,每转一圈都很费力,但达到某一临界点后,飞轮的重力和冲力就会成为推动力的一部分…...
QT编程从入门到精通之十四:“第五章:Qt GUI应用程序设计”之“5.1 UI文件设计与运行机制”之“5.1.1 项目文件组成”
目录 第五章:Qt GUI应用程序设计 5.1 UI文件设计与运行机制 5.1.1 项目文件组成 第五章:Qt GUI应用程序设计...
(二分)730. 机器人跳跃问题
目录 题目链接 一些话 切入点 流程 套路 ac代码 题目链接 AcWing 730. 机器人跳跃问题 - AcWing 一些话 // 向上取整 mid的表示要写成l r 1 >> 1即可,向下取整 mid l r >> 1 // 这里我用了浮点二分,mid (l r) / 2,最…...

vue3使用nextTick
发现nextTick必须放在修改一个响应式数据之后,才会在onUpdated之后被调用,如果nextTick是放在所有对响应式数据修改之前,则nextTick里面的回调函数会在onBeforeUpdate方法执行前就被调用了。可是nextTick必须等到onUpdated执行完成之后执行&a…...

传统图像处理之颜色特征
博主简介 博主是一名大二学生,主攻人工智能研究。感谢让我们在CSDN相遇,博主致力于在这里分享关于人工智能,c,Python,爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主,博主会继续更新的,…...
GPS问题调试—MobileLog中有关GPS关键LOG的释义
GPS问题调试—MobileLog中有关GPS关键LOG的释义 [DESCRIPTION] 在mobile log中,有很多GPS相关的log出现在main log和kernel log、properties文件中,他们的意思是什么,通过这篇文档进行总结,以便在处理GPS 问题时,能够根据这些log快速的收敛问题。 [SOLUTION] 特别先提醒…...
【企业管理】你真的理解向下管理吗?
导读:拜读陈老师一篇文章《不会向下负责,你凭什么做管理者?》,引发不少共鸣,“很多管理者有一种错误的观念,认为管理是向下管理,向上负责。其实应该反过来,是向上管理,向…...
Centos7 硬盘挂载流程
1、添加硬盘到Linux,添加后重启系统2、查看添加的硬盘,lsblksdb 8:16020G 0disk3、分区fdisk /dev/sdbmnw其余默认,直接回车再次查看分区情况,lsblksdb1 8:17 0 20G 0 part4、格式化mkfs -t ext4 /dev/sdb15、挂载mkdir /home/new…...

认识vite_vue3 初始化项目到打包
从0到1创建vite_vue3的项目背景效果vite介绍(对比和vuecli的区别)使用npm创建vitevitevuie3创建安装antdesignvite自动按需引入(vite亮点)请求代理proxy打包背景 vue2在使用过程中对象的响应式不好用新增属性的使用$set才能实现效…...
【Go】cron时间格式
【Go】cron时间格式 Minutes:分钟,取值范围[0-59],支持特殊字符* / , -;Hours:小时,取值范围[0-23],支持特殊字符* / , -;Day of month:每月的第几天,取值范…...
leetcode 55. 跳跃游戏
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标。 示例 1: 输入:nums [2,3,1,1,4] 输出:true 解释:可以先跳 1 …...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...