尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
LLVM项目全景指南:从架构原理到Pass开发实战
我第一次在GitHub上点开llvm-project这个仓库时说实话是有点懵的。三十多个顶层目录其中一半以上的名字我都是第一次见Clang、LLD、Polly、Bolt、MLIR……我当时脑子里只有一个问题这不是一个编译器项目吗怎么东西这么多后来折腾了大半年才慢慢摸清楚这个仓库的脾气。LLVM 不是一个编译器它是一个编译器零件的生产工厂而llvm-project就是官方把所有零件打包在一起的那个总仓库。如果你刚接触编译原理相关内容或者已经在用 Clang 但你写代码、构建工程却说不清 Clang 和 LLVM 到底是什么关系那这篇文章就是给你准备的一份全景导览。我会从整体架构讲到子项目分工从 IR 的最基本概念讲到如何亲手跑通一个 Pass最后再分享我这段时间踩过的一些实在坑。这篇文章里不会堆教科书概念所有内容大多是从实际折腾中发现的经验之谈看完之后你至少能明白三个问题llvm-project里到底装了什么、每样东西是干嘛的、以及你自己上手时第一步该怎么走。1. llvm-project 到底是干什么的先纠正一个常见误解1.1 它不是一个编译器而是编译器基础设计的集合大多数人听到 LLVM都默认它和 GCC 一样是一个编译器。这个说法有道理但不够准确。GCC 的特点是整机交付你给我一份 C 代码我给你一份目标平台的可执行文件中间过程你不需要关心。而 LLVM 走的是另一条路它把编译这件事拆成了若干个相对独立的环节然后把每个环节都做成可以被复用的库和工具。举一个我印象很深的例子。本科课程作业要求写一个静态分析器我当时直接用了 LLVM 的 API 去解析代码然后做数据流分析。如果用 GCC 生态我得自己写插件去对接 GCC 内部的数据结构那真是一段痛苦的回忆。但在 LLVM 生态里你只需要拿到一个模块的中间表示IR在 pass 里注册你的分析过程框架帮你处理调度问题。这种每个环节都可以独立替换的设计就是llvm-project整个仓库存在的基本前提。1.2 从 SVN 到 GitHub为什么这些子项目要装进同一个仓库历史上有段时间 LLVM 核心、Clang、LLDB 等是分开的仓库但你会发现它们的版本发布节奏必须严格同步因为 Clang 依赖核心库的某个 API而这个 API 在下个版本里可能就被改了。分仓管理很快变成灾难你得同时盯几个仓库的 commit才能保证组合出来的工具链能正常工作。后来官方把所有子项目统一迁移进了一个建立于 GitHub 上的 monorepo也就是你现在看到的llvm-project。仓库里每个顶层目录基本就是一个子项目目录也包含一些支撑目录统一用一套 CMake 构建逻辑管理。这样做的最大好处是版本对齐变得极其简单——你 checkout 的任何一个提交各子项目之间的 API 兼容性已经是经过 CI 验证的。注意在官方仓库里子项目并不是全部默认开启构建的。如果你只是cmake .. make而不配置任何开关默认构建的主体其实只有 LLVM 核心库和部分工具如opt、llc并不会带上 Clang 和 LLDB。这一点和下载 GCC 就能编译 C的体验完全不同我第一次尝试时就在这里吃了亏。2. 拆解 LLVM 全家桶各子项目实际解决什么问题2.1 顶层目录就是一份角色分工表我第一次看这些目录时没有头绪是因为不了解每个子项目在整个编译链路里负责什么。这里我用我实践经验中形成的理解把最常见的项目梳理成一张表。这张表不涉及太细节的内部 API主要是帮你建立宏观认知。顶层目录子项目在编译链路中的角色谁在为它买单llvm核心基础库与工具提供 IR、优化器、代码生成、pass 框架等基础设施所有其他子项目都依赖它clangC/C/Objective-C 前端把源代码翻译成 LLVM IR苹果、Google、华为、各大芯片厂商clang-tools-extra额外 Clang 工具链提供 clangd、clang-tidy、include-what-you-use 等开发辅助工具做 IDE 插件和静态检查的人lld链接器替代 GNU ld 的快速链接实现移动端、嵌入式、大型 C 工程lldb调试器构建在 LLVM 库之上、而非 GDB 之上的调试器苹果 Xcode 的一整套调试体验都靠它compiler-rt运行时支持库提供 sanitizer、builtins、profile 运行库跑 ASan/UBSan 时你就在用它libc / libcabi / libunwindC 标准库实现可替换 libstdc 的 C 标准库实现苹果生态、众多现代 C 项目MLIR多层中间表示框架用一套统一的基础设施搭建多级 IR覆盖编译优化与机器学习算子编译TensorFlow、IREE、各种 AI 芯片编译器flangFortran 前端把 Fortran 翻译成 LLVM IR科学计算、HPC 场景openmpOpenMP 运行时库提供#pragma omp对应的运行时库实现HPC、并行计算项目polly多面体优化器在 LLVM IR 层面做循环变换与数据局部性优化偏向研究、某些高性能计算场景bolt二进制优化与布局工具在二进制文件层面做性能优化Meta、大型服务端程序lldb调试器构建在 LLVM 库之上、而非 GDB 之上的调试器苹果 Xcode 的调试体验你会发现每个子项目覆盖的几乎都是编译工具链的某一环而它们都建立在同一个基石上llvm目录提供的核心库。2.2 为什么这些项目要放在一起互不依赖但需同步你可能会问MLIR 和编译器到底有什么关系Bolt 为什么也放在这里我的理解是这些项目虽然定位不同但都基于 llvm 核心库的 API并且会随着 LLVM 核心的 API 变化持续更新。如果一个项目长期不跟进上游接口变化它很快就会从可用变成编译不过。举我自己的一次经历有段时间我为了做实验把llvm-project的早期版本LLVM 9 时代和较新的 MLIR 代码混在一起尝试编译结果因为llvm::make_unique被移除这类 API 变更报错信息刷了几百行。后来我学乖了直接用同一个版本的 monorepo再没出过这种莫名其妙的问题。所以llvm-project这个全家桶仓库的价值不只在于让用户一下子下载所有零件而是保证了零件之间的匹配。对做研究的人来说这也是最可靠的复现环境checkout 这个 commit结合文档里的配置你的环境就大概率能搭起来。2.3 构建时如何选择子项目官方用LLVM_ENABLE_PROJECTS这个 CMake 变量来控制要构建哪些子项目。该配置的设计意图是默认只构建核心需要哪个加哪个。我个人常用的策略如下cmake -G Ninja ../llvm \ -DLLVM_ENABLE_PROJECTSclang;lld;clang-tools-extra \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_TARGETS_TO_BUILDX86这里选择clang是为了编译 C/C选择lld是因为我经常需要快速链接大型测试程序选择clang-tools-extra则是为了在 IDE 里用 clangd。如果只是为了做编译优化实验我通常只加上clang其他一律不开。提示LLVM_ENABLE_PROJECTS的值是分号分隔的项目名。这个变量在 CMake 缓存里一旦配置好后续想增删项目最好重新配置构建目录而不是直接改缓存否则容易产生残留目标别问我怎么知道的。3. 三层架构前端、IR、后端在现代编译器里如何协作3.1 和 GCC 的路线差异从单行道到立交桥在 GCC 的传统模型里每一个语言前端都有一套自己的中间表示体系优化与代码生成部分虽然有些抽象但整体上仍然是一个前端一方面对语言特性、一方面对后端机器的完整编译器形态。也就是说C 语言的 GCC 和 Fortran 语言的 GCC很难在不同语言间分享很大一块优化基建。LLVM 的思路是把编译过程分割成三段——前端负责把语言翻译成统一的 LLVM IR中端只做 IR 级的优化后端再把优化后的 IR 变成目标机器代码。这个设计最直接的效果是你想支持一门新语言只需要写一个把该语言翻译成 LLVM IR 的前端你想支持一款新 CPU只需要实现一个把 IR 转换到该 CPU 指令集的后端。中间那套优化器、pass 调度器、寄存器分配逻辑都可以通吃。这就好比传统编译器是每种语言各建一条专用道路车辆语言不能换路LLVM 则修了一座立交桥各条路的车都汇聚到同一条主干道IR调度好了之后再导向不同的出口。这座立交桥的修建方法就是 LLVM 在过去这么多年里最核心的贡献。3.2 前后端之间靠什么衔接pass 就是流水线上的一道道工序中端的优化并不是一次完成的而是由一个一个 pass 按固定顺序组成 pipeline。你写下-O2之后Clang 传给 LLVM 的其实是一组标准 pass 序列。我举一个具体例子一个简单的 C 函数里有a 0如果我们开优化这个0的操作在InstCombinepass 里会被化简掉。InstCombine之后可能进入LoopUnroll处理循环再交给GVN处理冗余计算。这些 pass 都只认 IR 指令不关心源代码是 C、Rust 还是 Swift。用生活化的类比整个编译优化过程好比一条汽车生产线pass 是每个工位上固定的工序。前端提供的是毛坯车架IR第一个工位做冲压第二个做焊接第三个做涂装。只要毛坯形状统一都符合 IR 规范来做的是什么型号的车哪门语言并不影响后续工序。这也是为什么 Rust、Swift、Kotlin/Native 最终都用 LLVM 作为编译后端——它们共享着同一套零件加工能力。3.3 后端的复杂度从 IR 到真正机器码很多人以为后端就是把 IR 翻译成汇编但实际情况要复杂得多。后端要处理指令选择、寄存器分配、指令调度、目标特定优化等问题。比如对于一个x86目标后端需要从一个抽象的add i32操作中决定是使用add eax, imm32还是lea指令来达到同样的效果。这个决策需要考虑目标 CPU 的微架构特性、指令延迟与吞吐量、寄存器压力等因素。LLVM 后端为此设计了 SelectionDAG 和 GlobalISel 两套指令选择框架后者比较新主要目标是支持更好的优化策略和新的目标架构。前端的复杂度在于语义分析后端的复杂度在于机器特性两者都很庞大。但编译器生态最大的收益在于中间的优化基础设施被极大复用了。这份复用带来的规模效应在llvm-project这种体量的代码库上体现得尤其明显一门语言前端加一个后端就能获得整套编译器世界已经沉淀的优化能力。4. LLVM IR 初见一条指令从 C 代码到汇编的旅行4.1 先看一段最简单的 IR任何一门语言的代码经过 Clang 处理之后会生成 LLVM IR。这个 IR 是一种和具体机器无关的、近似的 RISC 风格三地址码。来看一个最基础的例子我们有一个 C 文件foo.cint add(int a, int b) { return a b; }用下面的命令生成文本形式的 IRclang -S -emit-llvm foo.c -o foo.ll你会得到类似这样的内容这里按新的 LLVM 版本格式简化说明; ModuleID foo.c source_filename foo.c target triple x86_64-unknown-linux-gnu define i32 add(i32 %a, i32 %b) { entry: %add add nsw i32 %a, %b ret i32 %add }拆开看i32是 32 位整数类型add是函数符号%a、%b是虚拟寄存器不过 LLVM IR 里的%开头代表局部值add nsw i32 %a, %b执行加法并生成一个新值%addret返回该值。这个 IR 平台无关既可以被翻译成 x86 汇编也可以被翻译成 ARM 汇编还可以被各种分析工具直接读入。也就是说它对上层语言和下层硬件两方都不偏袒从而成为中间最稳定的契约。4.2 从 C 到可执行文件的三个重要形态LLVM IR 不是只存在于内存里也不是只以文本形式展示。实际上IR 有三种形态形态说明对应状况内存表示编译过程中由各个类对象构成的图结构opt工具加载 IR、运行 pass 时就处于这个形态Bitcode序列化之后的二进制格式扩展名.bc苹果的模块缓存、跨编译单元优化时经常用文本表示人类可读的.ll格式调试、分析 IR、写文档、写测试时用在写编译相关实验时文本 IR 是最容易查问题的。比如我跑一个 pass 之前会先用opt -passes...跑一遍看看 pass 的输出和预期是否一致会直接去读.ll文本。如果文本层面输出就不对就没必要再去调后续工具链。4.3 关于 SSA 与虚拟寄存器LLVM 的底层设计核心理解 LLVM IR 时绕不开一个概念SSAStatic Single Assignment静态单赋值。它的核心约束是每个变量只能被赋值一次每次使用都能静态地找到唯一的定义点。我当初第一次听说 SSA 时觉得这是个很刻意、很绕的限制我写 C 代码时变量可以反复重新赋值IR 凭什么不让我这么做但后来明白了正是因为每个值都有一个唯一的定义点数据流分析才会变得简单高效。比如你要找一个变量的所有定义不需要去考虑哪个定义覆盖了哪个定义这种复杂问题每个变量天然就对应一个定义。LLVM IR 里的寄存器即%开头的虚拟寄存器天然遵循 SSA。真实的物理寄存器分配是后端的事情IR 的虚拟寄存器只是符号化的名字数量几乎无限给优化带来了极大自由度。这也是为什么 LLVM 的优化 pass 写起来比 GCC 的 GIMPLE 更直觉化你不需要显式维护使用-定义链因为 SSA 形式下局部值的使用点天然可见每次变换之后只要保证 SSA 约束不被破坏分析结果就能保持有效pass 之间的并发与复用也更安全不必处理到处弥漫的副作用状态。4.4 版本差异为什么文档里的 IR 经常不完全一致如果你去看不同年份的 LLVM 文档会发现同一份示例 IR 里可能有细微出入。比如旧版里add i32 %a, %b这种写法在很多版本中仍能直接对应上但某些指令在较新版本里增加了显式类型限定、快速数学标志编程方式也有变化。官方在 IR 层面遵守基本稳定但有演进的原则并不会隔几个版本就大改。但由于整个llvm-project迭代得很快不少 API 在 minor release 之间就会发生 break change这也是为什么做 LLVM 相关开发时尽量使用与你想复现的教程相同版本的仓库。提示opt -passes...里的 pass 名字在不同版本之间的命名和别名系统区别很大。LLVM 老版本用opt -loop-unroll这种带连字符的参数新版本则统一用-passesloop-unroll。遇到 No such pass 报错时先确认你手上 LLVM 版本和教程是否一致。5. 自己动手构建 llvm-project我实测的配置与要点5.1 构建之前先算清楚成本和收益构建llvm-project是一个比较吃资源的事情我这里给出我实测过的一组数字仅代表个人经历仅供参考项目建议配置说明磁盘空间至少 100GB 可用源码加构建产物如果开 Debug 和全量测试可能更多内存16GB 起步链接大型工具时内存不足会直接导致 OOMCPU核心越多越好但内存不够时核心多反而容易把机器卡死时间20 分钟到数小时与构建配置、子项目数量、机器性能强相关这组数字可能会劝退部分只在笔记本上做小实验的初学者。别慌构建可以很小你可以选择只构建核心库和opt、llc这类工具不构建 Clang这样无论是体积还是时间都会大幅下降。只是在后续做实验时你可能还得有一份 Clang 来生成 IR。5.2 一份可复制的 CMake 配置我在本地最常用的构建命令如下git clone --depth 1 https://github.com/llvm/llvm-project.git cd llvm-project mkdir build cd build cmake -G Ninja ../llvm \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang;lld \ -DLLVM_TARGETS_TO_BUILDX86 \ -DLLVM_ENABLE_ASSERTIONSON \ -DBUILD_SHARED_LIBSOFF ninja几个参数的含义和我的选择理由-G Ninja比 Unix Makefiles 更快且支持并行任务的粒度更好-DCMAKE_BUILD_TYPERelease做实验时编译优化器本身开 Release 即可Debug 构建会慢很多产物也会大很多。但很多 LLVM 开发者会故意用Debug或RelWithDebInfo来抓断言那是另外的玩法-DLLVM_TARGETS_TO_BUILDX86默认会构建所有目标后端比如 ARM、RISCV、PowerPC 等构建时间会飙到极长。如果你只是在 x86 上做实验只留 X86 是最合理的-DLLVM_ENABLE_ASSERTIONSON我是为了 debug pass 时能及时抓出 IR 构造错误。代价是 release 构建里的断言会拖慢性能但如果只是实验用途影响不大-DBUILD_SHARED_LIBSOFF静态库链接出的二进制单文件巨大但链接一次之后运行效率高如果开 ON编译快一点但运行时需要处理动态库加载问题。注意如果你的内存只有 8GB建议把-DLLVM_PARALLEL_LINK_JOBS1也传给 CMake否则多个大型目标同时链接时内存会被瞬间打爆机器直接卡死。这是我在旧笔记本上被教育过的一个教训。5.3 构建完成之后先跑什么构建完成之后我的习惯是先跑ninja check-llvm-unit或直接运行一个小测试确认工具链可用但这一步在完整的构建里可能需要额外下载测试依赖。如果只是为了自己使用直接验证bin/clang --version、bin/opt --version能正常输出即可。这里有一点值得提醒不要直接拿系统自带的 Clang 和构建出的llvm-project工具混用版本不同会导致 IR 模块加载时报错比如 bitcode file is older version 之类。我自己曾吃过这个亏最后就是老老实实统一用仓库内构建出来的build/bin/clang和build/bin/opt问题就消失了。5.4 构建太慢怎么办只编译你需要的目标ninja默认会构建所有默认 target。这在项目全开时会是一个很漫长的过程。我实际工作中更常用的是精准构建ninja clang opt llc lld或者直接在构建后只生成某个工具比如只构建optninja opt因为很多实验只需要clang生成 IR和opt跑 pass完全不需要lld、lldb这些东西。先建小目标等需要的时候再补。万一某个目标编译到一半内存不够Ninja 支持断点续跑不至于前功尽弃。6. 动手写第一个 Pass新老框架到底怎么选6.1 你可能听过的 Legacy Pass 和现在的新 Pass 框架Pass 是 LLVM 优化器里最重要的扩展点之一。以前大家写 pass 用的是 Legacy Pass Manager入口是run(Function F)通过宏如INITIALIZE_PASS注册。但现在尤其 LLVM 17 之后Legacy Pass 的 C API 被彻底移除官方主推 New Pass Manager。因此我建议你直接学新框架老教程里的代码在最新版本上大概率编译不过。新框架下写一个最简单的函数 pass只需继承llvm::PassInfoMixin并提供run方法。下面这个例子在模块的每个函数入口打印函数名#include llvm/IR/Function.h #include llvm/IR/LegacyPassManager.h // 不再推荐 #include llvm/Passes/PassBuilder.h #include llvm/Passes/PassPlugin.h #include llvm/IR/Module.h using namespace llvm; namespace { struct MyFirstPass : public PassInfoMixinMyFirstPass { PreservedAnalyses run(Function F, FunctionAnalysisManager AM) { errs() Function: F.getName() \n; return PreservedAnalyses::all(); } }; } // namespace这段代码并不会自动被opt用上你还需要注册插件。一种做法是通过PassPlugin机制让opt在启动时加载.so插件extern C ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, MyFirstPass, LLVM_VERSION_STRING, [](PassBuilder PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager FPM, ArrayRefPassBuilder::PipelineElement) { if (Name my-first-pass) { FPM.addPass(MyFirstPass()); return true; } return false; }); }}; }然后这样使用clang -S -emit-llvm foo.c -o foo.ll opt -load-pass-plugin./libMyFirstPass.so -passesmy-first-pass foo.ll6.2 为什么我建议用 opt 而不是 clang 来跑 pass很多初学者会把 pass 集成到clang -O2的流水线里跑这可以做到但调试期我认为没必要。先用opt直接加载 IR 文件、运行 pass、打印输出可以最快地验证 pass 逻辑是否正确也能避开集成到 Clang 里可能遇到的构建复杂度和调试困难。等你确认 pass 本身没有问题了再考虑把它注册进 Clang 的 pipeline。初次调试时可以配一个最小的 Makefile 或 CMake 项目用add_llvm_pass_plugin这种 LLVM 提供的 CMake 函数来构建插件。官方文档里的 example 目录llvm/examples/Bye有现成的可参考样例。6.3 几个常见的报错及排查思路跑 pass 时最容易碰到的几个问题我直接列成一张表现象可能原因处理方式No such passpass 名没注册对或-passes里名字拼错先确认回调里那个字符串再检查插件是否加载Unable to load plugin.so路径不对或 ABI 版本不匹配确认插件是用当前llvm-project同一份源码构建的Assertion failed或段错误IR 修改破坏了约束不要在 pass 里随便改函数内容用IRBuilder生成新指令并遵守 SSAopt: Unknown command line argument参数写法对应老版本 API切换到新框架命名-passesfunction-name我最常犯的错误是插件名称和-passes里的名字不一致。-load-pass-plugin加载的是一个.so的路径而-passes里传的是你在回调函数里注册的那个字符串名字两者不是同一个东西。没搞清这点之前我一度以为 pass 没编译对。7. 折腾 llvm-project 这一年多我踩过的几个值得说的坑7.1 构建半天发现没编 Clang默认配置范围之殇这是我最开始遇到的一个比较典型的坑以为cmake .. make就能得到 clang 可执行文件但实际构建完只看到opt、llc等一堆库和工具没有clang。后来才意识到LLVM_ENABLE_PROJECTS里不写clang它就不会被构建。这个设计对新手确实不太友好所以我现在给人提建议时总会强调先确认要不要 Clang如果要必须在配置阶段就写明。7.2 Debug 构建的存储与速度噩梦有段时间为了深入看 pass 内部状态我开了一把 Debug 构建外加全量测试。结果整个构建目录膨胀到近乎失控链接clang可执行文件时内存经常飙满最后不得不删掉重来。后来我学到一个折中方案用Release构建加-DLLVM_ENABLE_ASSERTIONSON。这样既有优化器的运行速度又能保留断言检查对绝大多数实验场景足够了。7.3 新旧 API 混用导致的拒绝编译LLVM 社区推进 API 演进的速度是比较快的。你在网上看到的教程可能是三年前写的里面的legacy::Pass、createXxxPass()等写法在今天很可能编译不过。我的建议是优先看当前版本llvm-project里的 example 和 unittests那是最新鲜的用法在 GitHub 上搜别人的插件代码时注意看对方记录的分支/版本实在需要老接口时可以切换到一个对应的旧 release 分支用 git 做实验而不是在新版里硬凑。7.4 IR 文本加载不兼容版本不一致造成误判如果你日常用 Clang 生成.ll再用另一个版本的opt去读很可能报错expected top-level entity 或 Invalid record 之类。这不是你的代码有问题而是版本之间的 IR 格式有微小变化。遇到这种情况最好的做法是用同一个构建目录里的clang和opt。这也是llvm-projectmonorepo 设计给使用者的一个明确信号把版本统一起来能省去大量无意义的调试时间。7.5 尝试验证修改 IR 的正确姿势刚开始写 pass 时我喜欢在 pass 里直接创建Instruction对象来修改函数结果经常触发各种断言失败。后来我明白LLVM 提供了一整套 IRBuilder 模式专门用来在指定位置创建指令。造索引、构造常量等操作都要走ConstantInt::get、IRBuilder这些工厂方法而不是裸 new 一个指令。遵循框架的设计习惯等于把如何正确修改 IR地雷阵都绕开了。举个例子在函数入口插入一个打印值的指令我会这样使用 IRBuilderIRBuilder Builder(*F.getEntryBlock().getFirstInsertionPt()); Value *zero ConstantInt::get(Type::getInt32Ty(F.getContext()), 0); // 用 Builder 创建指令并插到 entry 块里这样生成的指令自动满足 SSA 约束比如把操作数节点连接好也省去手写指令构造的繁琐。写在最后的一点建议我个人的体会是llvm-project更像一个庞然大物类型的工具箱而不是一个开箱即用的普通编译器。把它跑通、把 IR 玩熟需要一个相对明确的最小目标你是想写静态分析还是想做代码优化实验还是想学后端移植目标不同探索路径差异很大。如果让我从自身经历里提炼一条最重要的小技巧那就是永远瞄准某个最新 release 版本基于它去读官方文档、找示例代码、搜索社区讨论。LLVM 的迭代速度决定了如果你在一个很旧的稳定分支上积累经验迁移到新版时部分经验可能会随时失效。反过来只要盯着一个较新的版本持续折腾每一块新学到的东西都能派上用场这类复用会带来很大的正反馈。希望这篇文章能帮你把llvm-project这棵参天大树看清楚一点。接下来就撸起袖子构建一次吧先跑通一个 Hello World 级别的 pass再去摸索你真正想做的那片领域。
RELATED

相关推荐

Win11 C盘爆满?从入门到进阶的清理与扩容全攻略

Win11 C盘爆满?从入门到进阶的清理与扩容全攻略

平时找我帮忙最多的事,除了给电脑杀毒,就是C盘爆红。尤其在Win11上,系统更新包、组件缓存、虚拟内存、休眠文件、各种软件的临时数据全往C盘堆,今天清完明天又满,烦不烦?这篇帖子我把自己在Win11下清理C盘的…

📅 2026/9/20 1:49:04
minikube 启动提示(Tips)功能增强提案深度解析:从静态 YAML 到随机展示的设计与实现

minikube 启动提示(Tips)功能增强提案深度解析:从静态 YAML 到随机展示的设计与实现

云原生容器编排CLI开发工具 【免费下载链接】minikube Run Kubernetes locally 项目地址: https://gitcode.com/gh_mirrors/mi/minikube 点击查看 免费下载 本文基于仓库内增强提案 tips.md 展开。该提案(首次提出于 2021-06-18,作者 Peixua…

📅 2026/9/20 1:49:04
读透组合树,TaoToken 换 DSH 的 Key

读透组合树,TaoToken 换 DSH 的 Key

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/20 1:49:04
MORE NEWS

更多资讯

📰

SQL注入靶场实战:从报错注入到盲注的核心思路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Karukan KV cache管理全解析:llama.cpp序列上限与Beam的工程边界

Karukan KV cache管理全解析:llama.cpp序列上限与Beam的工程边界 【免费下载链接】karukan Japanese Input Method System for Linux, macOS, Neural Kana-Kanji Conversion Engine 项目地址: https://gitcode.com/GitHub_Trending/ka/karukan Karukan 是一款…

📰

软件工程术语解析:编码与设计的多层含义与实战应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

内网信息收集实战指南:网络层、主机层与服务层命令详解

1. 内网信息收集之前:先定边界,再谈命令1.1 授权范围决定了你能做什么做内网安全评估这些年,我最大的一个体会是:信息收集这条线,最容易被误解的就是"拿到命令就能用"。刚入行的时候我也犯过这个错误&#x…

📰

Raspberry Pi Pico入门:MicroPython开发环境搭建与GPIO/PWM/串口实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

两级式光伏并网Simulink仿真要点:MPPT与逆变器双闭环全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬