本文分享我如何通过 .NET 10 Native AOT 和交叉编译技术,将一个原本动辄 100MB 的应用压缩到 16MB,并在资源极度受限的环境中实现流畅运行的实战经验。
在嵌入式领域,C/C++ 一直是绝对的主角。但随着 .NET 的演进,Native AOT让 C# 开发者也能在资源极度受限的 SoC 上大展身手。
本文记录了如何将一个 ASP.NET Core 管理后台,完美塞进瑞芯微 RK3506(224MB 内存 / 128MB Flash)仅有的 38MB 可用分区中。
RK3506 是一款高性价比的国产工业芯片,但在我们的实战场景中,环境极其苛刻:
/userdata 仅剩 38MB。常规的 .NET Self-contained 发布包动辄 60MB-100MB,显然是“超重”了。
为了解决体积和跨平台编译的依赖冲突,我选择了 Docker + Native AOT 的方案。
在 WSL2 或 Linux 宿主机上配置 armhf 交叉编译链时,经常会遇到 glibc 版本冲突或 apt 源架构 404 的问题。使用官方的 .NET 10 SDK 镜像 可以获得一个纯净的编译沙盒。
以下便是核心的编译指令,这里有一个硬核的技巧:通过 -p:LinkerFlavor=lld 强制使用 LLVM 的链接器,解决 x64 宿主机对 ARM 链接模式不识别的问题。
# 核心编译指令
docker run --rm -v "$(pwd):/app" -w /app mcr.microsoft.com/dotnet/sdk:10.0 bash -c " \
apt-get update -qq && \
apt-get install -y -qq clang gcc-arm-linux-gnueabihf lld && \
dotnet publish ./bweb/bweb.csproj -c Release -r linux-arm \
-p:PublishAot=true \
-p:InvariantGlobalization=true \
-p:LinkerFlavor=lld -p:ObjCopyName=arm-linux-gnueabihf-objcopy \
-o ./dist-aot"
第一个方案是避坑的首选,省的折腾环境了。
如果你是一名追求极致性能的“强迫症”开发者,不希望依赖 Docker,也可以直接在 WSL2 (这里用的是 Ubuntu 24.04) 中硬核出包。
当然,在开始前,需要先安装好交叉编译工具链:
# 基础构建工具与 Clang
sudo apt install clang lld zlib1g-dev -y
# ARM32 交叉编译器 (提供库搜索路径)
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
# 安装目标架构的 C 库支持
sudo apt install libc6-dev-armhf-cross binutils-arm-linux-gnueabihf -y
由于 AOT 交叉链接需要手动“指路”,我们需要显式指定交叉编译库的搜索路径。以下是核心的编译指令:
dotnet publish ./bweb/bweb.csproj -c Release -r linux-arm \
-p:PublishAot=true \
-p:PublishTrimmed=true \
-p:InvariantGlobalization=true \
-p:CppCompilerAndLinker=clang \
-p:LinkerFlavor=lld \
-p:ObjCopyName=arm-linux-gnueabihf-objcopy \
-p:SysRoot=/ \
-p:CustomLinkerArgs="--target=armv7-linux-gnueabihf -L/usr/lib/gcc-cross/arm-linux-gnueabihf/$(ls /usr/lib/gcc-cross/arm-linux-gnueabihf/ | head -n 1) -L/usr/arm-linux-gnueabihf/lib" \
-o ./dist-aot
这里使用了 CustomLinkerArgs 来指定链接器的目标架构和库搜索路径,确保编译器能够正确找到 ARM 版本的 C 库。ls /usr/lib/gcc-cross/arm-linux-gnueabihf/ | head -n 1 是为了动态获取安装的交叉编译器版本,避免硬编码路径。一般来说,这个版本号会是 13。
为了让程序在 38MB 的分区里住得舒服,我又做了三层裁剪:
InvariantGlobalization。在嵌入式 Web 后台场景中,我们通常不需要复杂的国际化 ICU 库,这一项就能省下约 25MB。Trimmed。它会扫描代码树,未被引用的库(如某些未使用的 Json 序列化程序)将不会被编译进二进制。.dbg (调试符号):AOT 生成的符号文件往往比程序还大。appsettings.Development.json。.gz 和 .br 文件可以让低频的 RK3506 避免实时压缩 CPU 损耗,也算是性能平衡的艺术吧。由于没有了 JIT,启动时的内存和CPU抖动消失了。通过 top 观察,程序的 RSS(实际驻留内存)非常稳定。
最终,我们的 ASP.NET Core 程序在 RK3506 上跑出了如下成绩:
.NET 10 Native AOT 已经完全具备了在国产工业芯片上取代传统嵌入式开发语言的实力。它让我们可以用高效的 C# 语法,写出 C++ 级别的性能。更有强大的 LINQ、异步编程、完善的 NuGet 生态支持,真正实现了“高效开发,极致性能”的双赢。