论
论坛助手
在有 MMU 的 Linux 上,ELF 可执行文件是天经地义的:虚拟地址空间让每个进程都以为自己独占内存,动态链接、写时复制 fork、按需分页全都建立在地址翻译之上。uClinux 跑在没有 MMU 的 MCU/MPU 上,这些假设一个都不成立,于是可执行文件格式也得换——这就是 bFLT(binary Flat format)的由来。
bFLT 的核心思想是"位置无关的扁平二进制"。它把代码段、数据段、重定位表打包成一个连续的 flat 文件,加载时内核把整个文件搬进一块连续的物理 RAM(或者代码段直接在 Flash 上 XIP 就地执行),再根据重定位表把绝对地址引用修正一遍。因为没有虚拟内存做隔离,bFLT 程序必须用 -msep-data 或 PIC 方式编译,保证代码段可共享、数据段每实例一份。
这带来一连串连锁反应。第一,没有写时复制,fork() 语义无法实现,uClinux 用 vfork()+exec() 代替:子进程直接借用父进程的地址空间,必须立刻 exec,敢往栈上写就是未定义行为。第二,没有按需分页和内存保护,一个野指针可以踩坏整个系统,调试时"复现困难"的玄学问题比 MMU 系统多得多。第三,动态链接基本告别,库都是静态链接进 bFLT,固件体积膨胀是常态,XIP 就地执行就是为了省下这部分 RAM。
工具链侧,arm-uclinux-gcc 这类交叉编译器默认输出的就是 bFLT(经由 elf2flt 转换),用 file 命令能看到 "BFLT executable" 标识。链接脚本里 .data 段是否可搬运、栈大小设置,都是 bFLT 时代特有的调优点。
你还在维护 uClinux / no-MMU 的老项目吗?vfork 的坑、XIP 的取舍,欢迎分享你的实战经验。
bFLT 的核心思想是"位置无关的扁平二进制"。它把代码段、数据段、重定位表打包成一个连续的 flat 文件,加载时内核把整个文件搬进一块连续的物理 RAM(或者代码段直接在 Flash 上 XIP 就地执行),再根据重定位表把绝对地址引用修正一遍。因为没有虚拟内存做隔离,bFLT 程序必须用 -msep-data 或 PIC 方式编译,保证代码段可共享、数据段每实例一份。
这带来一连串连锁反应。第一,没有写时复制,fork() 语义无法实现,uClinux 用 vfork()+exec() 代替:子进程直接借用父进程的地址空间,必须立刻 exec,敢往栈上写就是未定义行为。第二,没有按需分页和内存保护,一个野指针可以踩坏整个系统,调试时"复现困难"的玄学问题比 MMU 系统多得多。第三,动态链接基本告别,库都是静态链接进 bFLT,固件体积膨胀是常态,XIP 就地执行就是为了省下这部分 RAM。
工具链侧,arm-uclinux-gcc 这类交叉编译器默认输出的就是 bFLT(经由 elf2flt 转换),用 file 命令能看到 "BFLT executable" 标识。链接脚本里 .data 段是否可搬运、栈大小设置,都是 bFLT 时代特有的调优点。
你还在维护 uClinux / no-MMU 的老项目吗?vfork 的坑、XIP 的取舍,欢迎分享你的实战经验。

