news 2026/10/23 6:42:41

从BIOS到UEFI:引导程序实现的启动流程与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从BIOS到UEFI:引导程序实现的启动流程与避坑指南

简介:《一个UEFI引导程序的实现》是一本面向操作系统学习者与底层开发者的技术书电子版,作者田宇围绕UEFI固件机制,讲清从了解BIOS到实现自研引导程序的完整路径。书中先对比UEFI与BIOS的结构组成与启动流程,说明UEFI取代BIOS的性能、扩展性与安全优势;随后介绍搭建TianoCore EDK2开发环境、编译执行UEFI Shell、GPT磁盘布局与ESP文件系统、Hello World应用、UEFI镜像与系统表、调用约定和协议;最后逐步落实到显示模式配置、物理内存获取、存储介质文件读取、64位处理器平台环境以及系统内核引导启动。资源为1个PDF文件,压缩包仅2.36MB,便携易读;目录按开发递进组织,配合源码思路与运行效果说明,适合具有一定操作系统基础、希望理解固件接口或为Linux内核学习做准备的读者。已有2379人学习浏览,是深入UEFI引导开发的实用参考资料。

1. UEFI引导程序:为什么一个"加载器"要先学会当"服务员"

第一次写出能跑的UEFI引导程序时,我盯着入口函数愣了半天:main变成了UefiMain,入口带着两个参数,第二个是一张叫SystemTable的巨型结构体,里面塞满函数指针、控制台句柄和配置表。这哪里像引导程序,分明是个在操作系统之前运行的小型操作系统。这个反直觉的点,正是UEFI引导程序与传统BIOS引导的分水岭:BIOS只负责把首个扇区读进内存就撒手,UEFI却要求引导程序在接管硬件前,先通过协议向固件"点单"——读文件、查内存、设显示模式,全凭固件服务,而不是自己直接操寄存器。搞懂这套"服务员"机制,才能理解UEFI凭什么取代BIOS、撑起2TB以上磁盘与安全启动,也才看得懂《一个UEFI引导程序的实现》为什么要用整本篇幅讲一条从固件环境到系统内核的链路。

2. 启动流程的七阶段:为什么一次开机要拆成SEC、PEI、DXE三幕

BIOS的开机流程是一条直线:上电、POST、初始化设备、中断读盘、跳进引导扇区。UEFI则把启动过程切成七个阶段,SEC、PEI、DXE、BDS、TSL、RT、AL,每段都有明确的任务边界和可用的服务范围。第一次看到这个分法的人通常会问:一次开机而已,有必要搞这么复杂吗?答案是有必要,因为第一行代码执行时,连内存都是不可用的。

2.1 SEC:没有可用内存时,第一行代码怎么活

处理器上电后,第一条指令落在物理地址0xFFFFFFF0处,也就是4GB以下的最后16字节。这时候DRAM控制器还没有被初始化,RAM碰一下就出异常;CPU还跑在实模式,数据位宽16位。SEC阶段要干的头等大事,是给自己造出一个能用的"内存":把处理器缓存配置成普通内存来用,也就是常说的Cache as RAM。缓存的本质是SRAM,上电即可用、不需要刷新,把它映射成栈之后,C代码才有立足之地。

所以SEC阶段的作用不只是跑个自检,它是整个UEFI链条的第一根支柱:建立临时内存、把CPU切到保护模式或IA-32e模式、再找到PEI的入口。这里有个容易误会的点:切保护模式不是给"用户程序"准备的,而是给固件自己准备的。只有离开实模式,才能访问更大的地址空间,才能用段机制把固件代码映射到安全的位置。SEC阶段的代码量极小,因为缓存当内存用的方案容量有限,这段代码的目标只有一个:活下来,把接力棒传下去。

2.2 PEI:这一阶段的KPI就是"把内存变出来"

PEI的全称是Pre-EFI Initialization Environment,翻译过来是"EFI环境预初始化"。它的任务很直接:初始化内存、CPU、芯片组这些最核心的设备。为什么这种初始化不能放到后面统一做?因为后面的DXE阶段要加载驱动、要把成百上千的固件模块放进内存里执行,没有内存一切都是空谈。PEI是把内存从"不可用"变成"可用"的那个阶段。

PEI阶段有一个容易忽略的产出:HOB(Hand-Off Block)链表。PEI把探测到的内存范围、固件存储位置、引导路径等信息写进一系列HOB,交给DXE阶段。DXE拿到HOB,才知道哪些物理内存可用、哪些被固件保留。这也是"Pre-EFI"的含义——它不是预初始化某个外设,而是在给整个EFI环境"铺地基"。书里特别强调,PEI的代码不能压缩、必须精简。原因很现实:这段代码要在内存还没完全可用的环境下执行,体积越大,对临时内存的要求越高,启动越脆弱。所以PEI的代码风格和后面DXE完全不同——宁可少做,不能做错。

2.3 DXE与BDS:驱动装载器和开机菜单的分工

DXE(Driver Execution Environment)是整个固件最热闹的阶段。DXE Foundation启动后,一个叫DXE Dispatcher的组件会扫描固件卷(FV)里的所有驱动镜像,按照每个驱动声明的依赖表达式(depex)决定加载顺序。比如某个驱动声明"必须等PCI IO协议就绪后才能加载",Dispatcher就会把它排在后面,反复扫描直到所有可加载的驱动都加载完。这个机制让驱动之间可以自由组合,固件厂商不需要硬编码加载顺序。

驱动加载后会向Handle数据库注册自己安装的Protocol。Handle数据库是UEFI世界的"服务注册中心":每个设备、每个服务都是一个EFI_HANDLE,上面挂着若干协议接口。OS加载器后面要做的事,本质上就是在这个数据库里按GUID找协议、调用协议里的函数。这个过程是动态的:驱动可以后装,协议可以后注册,硬件差异在固件层被消化掉。这就是BIOS"静态链接中断处理程序"做不到的事。

BDS(Boot Device Select)阶段则像一个开机菜单:读取NVRAM里的BootOrder变量,逐个尝试启动项Boot####。每个启动项都指向一个设备路径和文件路径,固件按路径加载UEFI应用——可能是OS加载器,也可能是UEFI Shell。如果所有启动项都失败,固件会退回Shell(TSL阶段)让使用者手动排查。直到操作系统调用ExitBootServices,引导服务销毁,系统才进入RT阶段,只剩下运行时服务和系统表还活着。

2.4 七阶段各自解决什么问题

阶段核心任务内存状态关键机制
SEC临时内存、切换模式缓存充当RAMCache as RAM
PEI初始化CPU/芯片组/内存逐步可用HOB链表
DXE加载驱动、安装协议完整可用Dispatcher + depex
BDS选择并启动OS加载器完整可用BootOrder / Boot####
TSLUEFI Shell 环境完整Shell命令排查
RTOS运行期OS接管仅RuntimeServices
AL复位/休眠系统状态ResetSystem

这张表不是拿来背的,它表达的是执行顺序与服务范围:每往后一个阶段,可用的服务变多,但约束也变多。加载器如果要动硬件,必须在RT之前;一旦退出引导服务,连内存分配都只能靠自己。这条边界是理解后面所有代码的关键——为什么引导程序里要先收集一堆信息、最后才交权,答案都在这七个阶段里。

3. 程序入口、系统表与协议:UEFI程序是怎么"活"起来的

在EDK2(开源的UEFI固件开发环境)里新建一个UEFI应用,工程模板生成的入口函数长这样:

#include <Uefi.h> EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { SystemTable->ConOut->OutputString (SystemTable->ConOut, L"Hello, UEFI!\r\n"); return EFI_SUCCESS; }

main变成了UefiMain,还多出两个参数。这背后是UEFI对"可执行文件"的完整定义:入口函数的原型、调用约定、返回语义,都在规范里定死了。

3.1 入口约定:main为什么变成UefiMain

EFIAPI这个修饰符声明的是调用约定——UEFI采用一套非System V的64位调用约定,前四个参数依次走寄存器传递。为什么不用Linux世界常见的SysV约定?因为UEFI规范脱胎于最早的EFI 1.10,PE/COFF镜像格式和对应工具链天然沿用了这套传统,EDK2在GCC下编译时也会自动把参数传递方式处理掉,不需要应用层关心。但你要知道这不是随意的选择:调用约定决定了固件和应用程序之间如何传参,两边的汇编代码必须对齐,否则一次函数调用就能让整个系统崩溃。

ImageHandle是当前应用镜像的句柄。它不是普通数字,而是Handle数据库里的一个条目;借助EFI_LOADED_IMAGE_PROTOCOL可以查到镜像被加载到哪个地址、来自哪个设备路径。SystemTable则直接给出整个固件服务的入口。ConOut指向EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL,OutputString负责把宽字符串打到屏幕上。注意那个L前缀:UEFI内部一律使用CHAR16宽字符,这是规范和硬件无关的编码选择,也是后面写文件路径时最容易翻车的地方。

3.2 系统表:所有引导服务的总闸

typedef struct { EFI_TABLE_HEADER Hdr; CHAR16 *FirmwareVendor; UINT32 FirmwareRevision; EFI_HANDLE ConsoleInHandle; EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; EFI_HANDLE ConsoleOutHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_HANDLE StandardErrorHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; EFI_RUNTIME_SERVICES *RuntimeServices; EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; EFI_CONFIGURATION_TABLE *ConfigurationTable; } EFI_SYSTEM_TABLE;

结构体里两个指针是全书后面所有代码的"总闸":BootServices只在引导期有效,包括内存分配、协议查找、句柄操作、事件和定时器;RuntimeServices在OS运行期依然活着,包括SetVariable、GetTime、ResetSystem。区分这两类服务是写引导程序的必修课——用错一个,轻则拿到空指针,重则在ExitBootServices之后调用一个已销毁的引导服务,直接死机。

ConfigurationTable是GUID加指针的数组,ACPI的RSDP和SMBIOS表都挂在这里。OS加载器必须在退出引导服务之前把这些指针全记下来,否则之后再也拿不到。很多人在引导程序里找不到ACPI表,根源就是忘了系统表里有这么一张配置表。

3.3 协议与GUID:UEFI世界怎么"找人办事"

EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop; EFI_HANDLE *Handles; UINTN Count; Status = SystemTable->BootServices->LocateHandleBuffer ( ByProtocol, &gEfiGraphicsOutputProtocolGuid, NULL, &Count, &Handles ); Status = SystemTable->BootServices->OpenProtocol ( Handles[0], &gEfiGraphicsOutputProtocolGuid, (VOID **)&Gop, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL );

第一个参数ByProtocol表示"按协议类型找人",LocateHandleBuffer返回所有安装了该协议的句柄;拿到句柄后还要OpenProtocol才能取得真正的接口指针。这里的关键是GUID——全球唯一的128位标识,相当于接口的身份证号。UEFI里每个协议都带版本号,固件可以扩展结构而不破坏旧接口,这就是UEFI声称"平滑升级"的底层机制:版本号让新旧实现可以共存,GUID让不同厂商不会撞名。

读到这里就明白了:UEFI程序跟普通C程序最大的区别不是语法,而是它所有能力都来自"问固件要"而不是"自己干"。另外顺带一提,UEFI驱动和UEFI应用在入口处几乎一样,区别在返回语义:应用返回EFI_SUCCESS表示任务完成,固件会卸载它;驱动返回EFI_SUCCESS则是"我还活着,别卸载我",而且驱动通常要额外实现Unload函数。这个语义差异经常被忽略,写出来的"驱动"往往一加载就消失。

4. GPT与ESP:为什么UEFI非要把磁盘改成另一种分区法

第二张章提到UEFI的启动方式与BIOS完全不同,盘面上最直观的差异是分区表。BIOS时代磁盘用MBR,UEFI时代用GPT,同时ESP分区必须存在。这一章拆开这两个机制的底层逻辑。

4.1 protective MBR:假分区是给老工具看的"免责声明"

用某些老牌磁盘工具查看GPT磁盘,会看到第一个分区类型是"EE",那不是真正的分区,而是protective MBR。GPT规范要求在LBA0保留一个MBR样式的扇区,里面只放一条类型为0xEE的分区记录,范围覆盖整块磁盘。老工具不认识GPT,但看到0xEE就会认为"这块盘已经有一个未知分区,动它会丢数据",于是拒绝格式化。这样一来,误把GPT盘当成空盘重新分区的情况就被拦住了。

这个设计还有个副产品:引导扇区兼容。UEFI不看LBA0,BIOS不看LBA1——同一个磁盘,传统BIOS引导时走MBR里的引导代码,UEFI引导时走ESP里的.efi文件。所以一块GPT磁盘可以同时被两类固件引导,这正是书里说的"UEFI不需要对ESP系统分区的第一个扇区进行更改,因此可以在引导媒介中同时引导传统体系结构和UEFI平台"。

4.2 GPT布局:分区表头带CRC,表项用64位LBA

GPT把分区信息放在四个位置:LBA0的protective MBR、LBA1的GPT头、LBA2开始的条目数组,磁盘尾部还有一份备份。看这张布局表:

LBA内容
0protective MBR
1GPT头(签名"EFI PART"、CRC32、条目位置)
2~33分区表项,每项128字节
34~实际分区数据(ESP、数据区等)
磁盘尾部备份GPT头与表项

每个分区表项的关键字段:PartitionTypeGUID表示分区用途(ESP有固定GUID,MSR保留分区也有固定GUID)、UniquePartitionGUID是该分区实例的唯一ID、StartingLBA和EndingLBA。注意这里的LBA是64位,而MBR的分区表项用的是32位——这就是2TB上限的根源:MBR能表示的最大逻辑块号是2的32次方,乘以每扇区512字节就是2TB。GPT把LBA扩展到64位,容量上限直接拉到艾字节级别。

GPT头还带CRC32校验:任何一个分区表项损坏,CRC就对不上,固件或操作系统可以转向备份GPT恢复。这是BIOS时代完全不存在的容错能力,也是大容量存储普及后UEFI必须拿出的方案。

4.3 ESP文件系统:为什么固件偏偏认FAT

ESP是EFI System Partition的缩写,本质是一个FAT格式的分区(FAT16或FAT32,取决于分区大小)。为什么选FAT而不是其他文件系统?三个原因:一是FAT规范简单,固件里实现文件系统驱动只需要几千行代码,占用宝贵的固件存储空间少;二是FAT目录项天然是线性表,查找\EFI\BOOT\xxx.efi这种固定路径非常直接,不需要复杂的索引结构;三是FAT不依赖平台特定的字节序和特性,跨厂商移植成本低。UEFI接口的表现形式是数据表,文件系统就是其中一张挂在BootServices下的表,而FAT让这张表容易实现、容易验证。

引导路径也有约定:对于amd64架构,固件在找不到任何有效启动项时,会去ESP的\EFI\BOOT\BOOTX64.EFI找默认镜像。所以把编译出的Shell.efi改名为BOOTX64.EFI放进这个路径,几乎在任意UEFI机器上都能被固件认出来。还要注意书里强调的分区创建时按"4096扇区(2MB)"对齐——现代SSD的物理扇区是4KB、擦除块是2MB级别,分区起始不对齐,读写会跨物理块,性能损失和磨损都更明显。这个经验不只适用于ESP,所有GPT分区都应该这么做。

为什么不能像BIOS那样直接读分区表里的LBA启动第一个扇区?因为UEFI的启动单位是文件而不是扇区。固件从ESP里加载的是PE/COFF镜像文件,需要文件系统解析、路径查找、镜像重定位。正因为引导逻辑从"跳进固定扇区"变成了"按路径加载文件",引导介质才可以是支持文件系统的任意块设备,磁盘布局由固件和OS加载器协商,而不是把启动代码锁死在第一个扇区里。

5. 避坑排查:从五次启动失败反推UEFI引导机制

下面这五个坑在UEFI开发社区里被反复踩过,每个都对应一个明确的底层机制。按现象、原因、解决的方式来写,看完大概率能少烧几块U盘。

5.1 SetMode成功却花屏:扫描线宽度才是关键

现象:调用GraphicsOutput的SetMode设置显示模式,返回成功,但屏幕输出花屏或者图像错位。

原因:显卡的显存布局里,每行像素占用的字节数并不一定等于分辨率宽度。很多GPU会把每行对齐到某个边界,实际行宽存在Mode信息里的PixelsPerScanLine字段。用HorizontalResolution去算帧缓冲跨度,画出来的每一行都比实际位置偏一点,累积下来就是斜切的花屏。这是典型的"模式信息没读全"导致的翻车。

解决:画任何像素之前,先读Mode->Info->PixelsPerScanLine,用它作为每行的跨度。书里配置显示模式的章节,第一步就是QueryMode拿EFI_GRAPHICS_OUTPUT_MODE_INFORMATION,而不是直接用分辨率填数。

5.2 ExitBootServices报EFI_INVALID_PARAMETER:内存图Key过期

现象:调用ExitBootServices返回无效参数错误,系统直接卡死或重启。

原因:ExitBootServices需要一个内存图Key,表示"当前内存视图"。问题是固件会检查你传入的Key是不是最新一次GetMemoryMap返回的Key。如果在两次GetMemoryMap之间发生了任何内存分配或释放,Key就变了,固件判定"你手里的地图过期了",于是拒绝交权。这个机制是为了防止OS加载器带着一张陈旧的内存图进入系统,导致后续内存管理错乱。

解决:在ExitBootServices之前紧挨着重新调用一次GetMemoryMap,用最新的MemoryMapKey传给ExitBootServices,并且这两次调用之间不要做任何分配操作。

EFI_MEMORY_DESCRIPTOR *MemoryMap; UINTN MemoryMapSize, MapKey, DescriptorSize, DescriptorVersion; Status = SystemTable->BootServices->GetMemoryMap ( &MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion ); // 注意:DescriptorVersion 是描述符接口的版本号,不是结构体大小 // 真正的描述符大小在 DescriptorSize 里,遍历内存图时按它步进 Status = SystemTable->BootServices->ExitBootServices (ImageHandle, MapKey);

注意:DescriptorVersion是接口版本号,DescriptorSize才是内存描述符的实际大小。固件可能扩展描述符结构,遍历内存图时不能用sizeof(EFI_MEMORY_DESCRIPTOR)步进,必须用返回的DescriptorSize。

5.3 路径对、文件也存在,就是打不开:文件系统协议没那么简单

现象:文件确实在ESP分区里,路径看起来也对,但Open返回EFI_NOT_FOUND。

原因:三个常见坑叠在一起。第一,UEFI路径分隔符是反斜杠\而不是正斜杠/;第二,UEFI内部是宽字符,路径字符串必须带L前缀;第三,固件只能访问挂在Handle数据库里的文件系统协议,ESP分区本身是FAT格式且由固件识别,如果文件放在普通数据分区而不是ESP里,固件根本不认。还有一点容易被忽略:某些固件对长文件名的支持不完整,用8.3短文件名反而更稳。

解决:先通过EFI_SIMPLE_FILE_SYSTEM_PROTOCOL的OpenVolume拿到根目录,再逐级Open,路径写成宽字符串。

EFI_FILE_PROTOCOL *Root, *File; FileSystem->OpenVolume (FileSystem, &Root); Status = Root->Open (Root, &File, L"\\EFI\\BOOT\\KERNEL.BIN", EFI_FILE_MODE_READ, 0);

路径开头的\表示根目录,完整路径语义和Linux不同:它不是绝对路径的/,而是从根目录开始的逐级目录链。

5.4 AllocatePages成功,一访问就崩:内存分配类型与地址上限

现象:用AllocateAnyPages分配内存,指针非空,但后续代码一访问就异常,尤其在做16位/32位切换的代码里。

原因:AllocateAnyPages只保证"有内存",不保证地址范围。在UEFI 64位环境下,固件可能返回4GB以上的地址。而你要跳回实模式或者给某个只支持32位地址的硬件传DMA缓冲时,这些地址根本不可用。问题不在分配失败,而在分配成功但类型选错。

解决:给这类代码用AllocateMaxAddress,把上限限制在4GB以下。书里实现64位处理器引导时,给实模式代码分配临时GDT/IDT缓冲,就是用这种方式限定地址范围。如果确实需要固定地址,再用AllocateAddress指定,但前提是这个地址没有被HOB标记为已占用。

5.5 虚拟机正常、真机黑屏:固件实现差异与过度依赖默认模式

现象:在开放虚拟机固件(OVMF)环境下一切正常,换到真实机器上黑屏或者根本进不了引导菜单。

原因:UEFI规范定义了接口,但真机固件是各家自己实现的。不同固件对可选协议的支持不一样,GOP模式列表的排列顺序不一样,安全启动的默认状态也不一样。虚拟机环境太"标准",掩盖了这些差异。

解决:不要硬编码模式号,用QueryMode遍历所有模式,挑一个最接近目标分辨率的;日志同时输出到串口调试口而不是只依赖屏幕;把Shell.efi通过Boot####变量显式登记,而不是只靠BOOTX64.EFI默认路径。UEFI规范给了灵活性,代价就是实现方必须用"探测"而不是"假设"来写代码。

6. 设计取舍:为什么UEFI选择了"服务契约"而不是一次跳转

BIOS引导的本质是一次跳转:引导扇区被读进内存,CPU跳进去,BIOS从此退场。UEFI选择的是另一条路——固件不下场,而是变成一套活着的东西,尤其引导期的BootServices,OS加载器通过协议按需调用硬件能力。这等于把"引导"从一段孤立代码变成了一个契约:固件承诺提供接口,加载器承诺在退出引导服务前把要的东西都拿齐。

这个取舍的收益是可移植性。OS加载器不需要知道显卡寄存器怎么配、磁盘控制器怎么发命令,只要找得到协议、参数符合规范,硬件差异就被固件消化掉了。代价则是ExitBootServices这个不可逆的commit点——一旦调用,固件的内存管理、驱动、事件全被销毁,加载器必须在之前收集好内存图、ACPI表、GOP帧缓冲。没有这个"一刀切",固件和操作系统就会争夺内存控制权,局面比谁都难收拾。

验证这套设计的方法很朴素:用EDK2编译一套带调试输出的OVMF,跑一遍引导流程,在串口日志里观察每个阶段的打印;再故意在ExitBootServices之后调用一次AllocatePages,立刻能看到崩溃——固件已经不管你了。这比任何文档都更能说明"服务契约"的边界在哪。

我第一次完整跑通引导时,把ACPI表的获取放在了ExitBootServices之后,结果RSDP怎么都找不到,只能重启重来。从那以后,我每次写加载器都强制走一遍清单:先枚举协议,再取内存图,顺手把配置表指针存好,设置显示模式,最后才调用ExitBootServices。这个顺序本身就是UEFI设计者留给使用者的"正确打开方式",书里从配置图形显示、获取物理内存、读取存储文件到跳进64位内核的章节安排,走的正是这条链路。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 6:42:35

本地部署DeepSeek全栈指南:Ollama与Open WebUI集成

简介&#xff1a;这份资源面向具备基础命令行与容器化概念、希望将预训练语言模型落地到本地环境的开发者与研究人员&#xff0c;提供DeepSeek-R1从Ollama安装到模型运行、再到WebUI集成的完整部署指引。内容围绕机器学习与AI模型部署展开&#xff0c;覆盖不同硬件显存下的模型…

作者头像 李华
网站建设 2026/10/23 6:42:27

线性回归Java实现:梯度下降与正态方程从原理到避坑

简介&#xff1a;面向Java初学者与机器学习入门者的线性回归实现&#xff0c;涵盖梯度下降与正态方程两种求解方式。项目基于Andrew Ng公开课中的经典示例数据&#xff0c;通过拟合yMxB展示从训练集学习参数的过程&#xff0c;同时支持高阶多项式特征扩展&#xff0c;帮助理解监…

作者头像 李华
网站建设 2026/10/23 6:42:22

手动部署DeepSeek:Linux下CUDA环境配置与避坑指南

简介&#xff1a;这份PDF面向需要在Linux环境下部署DeepSeek的开发者与深度学习爱好者&#xff0c;系统梳理了从环境准备到模型运行的手动部署流程&#xff0c;适合具备一定Linux操作基础、希望独立完成本地部署的中级用户参考。资源包共1个PDF文件&#xff0c;大小约215KB&…

作者头像 李华
网站建设 2026/10/23 6:41:31

Carrier-in-Carrier:卫星通信载波重叠与自适应抵消技术解析

简介&#xff1a;CDM-625高级卫星调制解调器汉译版技术手册&#xff0c;面向卫星通信工程师、系统集成商及运维人员&#xff0c;系统讲解Comtech EF Data CDM-625的核心能力。内容涵盖双端对讲载波内载波带宽压缩技术的自适应抵消原理&#xff0c;VERSAFEC、LDPC、TPC等前向纠错…

作者头像 李华
网站建设 2026/10/23 6:40:18

分布式锁的代码实现:从原理到实战

1. 引言在分布式系统中&#xff0c;多个服务实例可能同时操作共享资源&#xff0c;如果缺乏互斥机制&#xff0c;就会出现超卖、重复扣款、数据覆盖等问题。分布式锁正是为了解决这类跨进程、跨节点的并发竞争问题而设计的通用方案。本文围绕分布式锁的核心原理&#xff0c;给出…

作者头像 李华
网站建设 2026/10/23 6:39:40

Thread 类的基本用法

一.线程创建第一方式&#xff1a;创建子类继承Thread 重写run方法package Thread_star;public class Dome1 extends Thread {Overridepublic void run() {while (true){System.out.println("hello thread");try {Thread.sleep(1_000);} catch (InterruptedException …

作者头像 李华