Hands-On Lab: Linux 6.12 跑进 ESP32-S31——单片机里的完整MMU内核
一句话结论: 只要芯片有 MMU,Linux 就存在跑起来的可能——ESP32-S31 用一颗 RISC-V 单片机验证了这条路径:Linux 6.12 在 Core 1 上以 Sv32 虚拟内存 + XIP 片外执行方式运行,Core 0 继续跑 ESP-IDF/FreeRTOS 应用,开机 1.25 秒进入 BusyBox shell。代价是外设驱动大面积未适配,项目明确定位为”实验性硬件 bring-up,绝不适合生产”。
1. 为什么”单片机跑Linux”这件事值得认真看
一句话: 传统认知里,“跑Linux”是应用处理器的专利(树莓派、RK3588、i.MX);而 ESP32-S31 是带 MMU 的 RISC-V 单片机,这次移植证明了 MCU 与 Linux 之间并不存在物理界限,只存在”值不值得”的工程权衡。
对机器人开发者来说,这个问题的价值在于成本结构:应用处理器(如树莓派级)单价与功耗是 ESP32 级的数倍到数十倍,但 Linux 生态提供的成熟网络栈(TCP/IP、TLS)、文件系统、进程模型与海量现成软件(BlueZ、Dropbear、tcpdump),是裸机 RTOS 难以替代的。带 MMU 的 RISC-V MCU,恰好卡在”RTOS 太裸、应用处理器太贵”的中间地带。
项目数据速览:
- Linux 6.12.0,riscv32-unknown-linux-musl 工具链(GCC 16.1.0 / Binutils 2.46)
- GitHub 98 stars,Hacker News 89 分
- 硬件:ESP32-S31-WROOM-3 模组(16MB Flash),Core Board/Korvo 开发板
- 开机时间:1.25s 完成 initrd 释放并执行
/init
2. 硬件基础:S31 凭什么能跑 Linux
ESP32-S31 的与众不同在于双 MMU 设计:
| MMU | 用途 |
|---|---|
| Espressif 私有 MMU | 把 Flash/PSRAM 映射进地址空间(XIP 的基础) |
| Sv32 MMU | 标准 RISC-V 虚拟内存,SATP CSR 可写,Linux 就靠它 |
CPU 架构为 RV32IMAFBCNSUX:RV32 基础 + M/A/F/B/C 扩展 + N/S/U 特权架构 + X(Espressif 私有扩展,含 xesploop 与 xespv 2p2)。注意:S31 没有声明 xespdsp、Zcb、Zcmp、Zcmt,这些扩展被有意关闭。
- 双核 HP CPU(Core 0 / Core 1)+ 低功耗 LP CPU
- Linux 使用 soft-float ilp32 ABI(F 扩展可在用户态使用,但保持与 ilp32 的 syscall/libc ABI 兼容)
- N/S/U 特权级由 S31 专用 OpenSBI 与 Linux 特权/陷阱路径实现,不追加到 GCC 的 -march
📌 关键洞察: “跑 Linux”的真正门槛不是 CPU 性能,而是虚拟内存(MMU)+ 特权模式(S/U)+ 中断架构三件套。S31 用 Sv32 满足了前两者,CLIC/CLINT 中断驱动则由 OpenSBI 桥接。
3. 移植架构:双核分工 + OpenSBI XIP 布局
这是整个项目最精彩的部分——一颗芯片同时运行两套系统:
┌─────────────────────────────────────────────────┐
│ ESP32-S31 │
│ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Core 0 │ │ Core 1 │ │
│ │ ESP-IDF │ │ OpenSBI → Linux 6.12 │ │
│ │ factory app │ │ (Sv32, XIP, S-mode) │ │
│ │ (FreeRTOS) │ │ Buildroot userspace │ │
│ └──────────────┘ └────────────────────────┘ │
│ 单核模式启动,负责加载固件、映射分区 │
└─────────────────────────────────────────────────┘
启动链: ESP-ROM → ESP-IDF 2nd stage bootloader → factory app(hello_world,Core 0 单核)→ OpenSBI(Core 1)→ Linux 6.12 → /init → BusyBox shell
OpenSBI Flash XIP 布局(地址契约,核心工程决策):
- OpenSBI 的 text/rodata 留在 Flash 里,通过 Flash MMU 窗口直接执行(FW_TEXT_START=0x40030000)
- OpenSBI 的可写数据/BSS/scratch/stack/heap 放在 PSRAM 缓存别名顶部 1MB(FW_RW_START=0x50F00000..0x51000000)
- Linux 只拿到缓存别名的低 15MB(0x50000000..0x50F00000,DT 内存节点)
- OpenSBI 分区固定 512KB
- factory app 用
CONFIG_SPIRAM_USE_MEMMAP,显式禁用 USE_CAPS_ALLOC/USE_MALLOC
📌 为什么这样设计: XIP 意味着内核代码不用先拷进 RAM——对只有 16MB Flash + 有限 PSRAM 的 MCU,这是把”能跑”变成”跑得下”的关键。而把 OpenSBI 的 RW 数据挤进顶部 1MB,是为了给 Linux 让出完整连续内存。
实际启动日志(实测):
[0.000000] Linux version 6.12.0-gb4777fc146b9-dirty (riscv32-unknown-linux-musl-gcc)
[0.000000] Kernel command line: earlycon=esp32s3uart,mmio32,0x2038a000 console=ttyS0,115200 rdinit=/init
[0.057390] Memory: 8744K/16384K available (1870K kernel code ...)
[1.254583] Run /init as init process
~ # ← BusyBox shell,登录成功
4. 构建与烧录:make 一键搞定
项目用统一 Makefile 管理全离树构建,所有产物集中在 build/:
# 一键构建:下载校验预编译工具链 → opensbi → linux → rootfs
make all
# 工具链(预编译,SHA256 校验)
make toolchain # riscv32-esp-linux-musl-gcc (GCC 15.2 / binutils 2.45 / musl)
# 组件构建
make opensbi # OpenSBI + 动态编译DTB → build/fw_payload.bin
make linux # 内核 xipImage + esp32s31_generic.dtb(out-of-tree)
make rootfs # Buildroot: BusyBox/BlueZ/Dropbear/iproute2/tcpdump/memtester/CoreMark → rootfs.sqfs
# 烧录(esptool 动态解析分区表)
pip install esptool
esptool -p /dev/ttyUSB0 -b 2000000 erase-flash
esptool -p /dev/ttyUSB0 -b 2000000 write-flash \
--flash-mode dio --flash-freq 80m --flash-size 16MB \
0x2000 bootloader.bin \
0x8000 partition-table.bin \
0x17000 ota_data_initial.bin \
0x20000 hello_world.bin \
0x220000 fw_payload.bin \
0x2A0000 xipImage \
0xA20000 rootfs.sqfs
分区表(16MB Flash, QIO 80MHz):nvs → otadata → phy_init → factory(2MB)→ opensbi(512KB)→ linux(7.5MB)→ rootfs(~6MB)
用户空间亮点: BusyBox(HTTPS wget,内置小型 TLS 客户端)+ BlueZ 蓝牙工具 + Dropbear SSH + iproute2 + tcpdump + memtester + CoreMark——一个功能相当完整的嵌入式 Linux 发行版。
5. 移植进度与”劝退”清单
| 模块 | 状态 |
|---|---|
| Buildroot rootfs / reboot / poweroff / UART0 控制台 | 🟢 稳定 |
| 无线(ESP-Hosted) | 🟡 未测试 |
| AXI/AHB GDMA、Cache、TRNG、eFuse、Watchdog、PWM、CLIC/CLINT 中断驱动 | 🟡 未测试 |
| 定时器、时钟树、安全加速器 | 🟠 WIP |
| USB | 🟠 WIP |
| LP 子系统与 IPC、PMP/APM | 🔴 未实现 |
| GPIO、pinctrl/GPIO矩阵、SDMMC、GMAC 以太网 | 🟡 未测试 |
| I2C | 🔴 未实现 |
| 双核 SMP | ⚫ 不计划(Core 1 被 FreeRTOS 占用) |
🚧 诚实评估: 这是一个研究型 bring-up 项目,不是可上产品的方案。当前实际可用的外设只有 UART 控制台;Wi-Fi/蓝牙依赖未测试的 ESP-Hosted;GPIO/I2C 尚未打通——意味着”跑起来”和”能用”之间还有大量驱动工作量。作者本人明确警告:“Definitely not something you want for production.”
6. 对机器人开发者的启发
结论先行: 短期别指望用 ESP32-S31 量产机器人控制器,但这条路线指出了三个趋势:
-
MMU 下探是确定方向: 带 Sv32 MMU 的 RISC-V MCU 会越来越多,Linux 与 RTOS 的边界将从”芯片类型”变为”应用需求”。需要成熟网络栈/文件系统/多进程隔离时选 Linux,需要硬实时与低功耗时选 RTOS——同一颗芯片两边都能跑。
-
XIP 是 MCU 跑 Linux 的关键钥匙: 代码直接片外执行,省掉 RAM 拷贝,16MB Flash 就能装下内核+rootfs。这对成本敏感的边缘节点(传感器网关、关节控制器上位机)意义重大。
-
双核异构(FreeRTOS + Linux)是实用架构: Core 0 管硬实时(电机控制、编码器读取),Core 1 跑 Linux(通信、OTA、日志、AI 推理调度)——这正是机器人控制器的典型分工。S31 已经用 bootloader + OpenSBI 把这条路铺通,后续芯片只需把外设驱动补齐。
对 DIY 极客的实操建议:如果想低成本体验”单片机上的 Linux”,这是一个绝佳的学习项目——make all 一条命令构建完整工具链+内核+rootfs,esptool 一根 USB 线烧录,1.25 秒后看到 BusyBox shell 的成就感,是任何模拟器都给不了的。
🔗 GitHub: GrieferPig/esp32-s31-linux | HN 讨论
本 Hands-On Lab 由 SinoBot 编辑部制作 | 双润色待补(Gemini + ChatGPT)