精简 RT-Thread 工程并查找占用空间大的模块
要精简 RT-Thread 工程并查找占用空间大的模块,你可以按照以下步骤操作:
🧩 一、查看占用空间大的模块(重点)
方法 1:使用 arm-none-eabi-nm
+ sort
arm-none-eabi-nm -S --size-sort rt-thread.elf | tail -n 30
这条命令可以列出占用空间最大的 函数、变量、段,方便你定位谁占了最多 Flash。
输出示例:
0801a7d0 00001c00 t lcd_draw_image
0801b3d0 00001800 t lvgl_render_task
0800d000 00001500 T rt_kmalloc
...
含义:
地址 | 大小 | 类型 | 符号名 |
---|---|---|---|
0801a7d0 | 0x1c00 | t | lcd_draw_image |
T/t
: 函数(大写是全局函数,小写是局部)D/d
: 已初始化数据(在 Flash 中)B/b
: 未初始化数据(在 RAM 中)
方法 2:使用 arm-none-eabi-objdump
arm-none-eabi-objdump -h rt-thread.elf
可以看到段(section)大小:
Idx Name Size VMA LMA File off Algn0 .isr_vector 000001c0 08000000 08000000 00010000 2**21 .text 0002d1fc 080001c0 080001c0 000101c0 2**22 .rodata 00001a3c 0802d3c0 0802d3c0 0003d3c0 2**23 .data 00000600 20000000 0802ee00 0003ee00 2**24 .bss 00002500 20000600 20000600 00000000 2**2
你可以重点关注 .text
和 .rodata
,它们会占 Flash。
✂️ 二、RT-Thread 的典型精简方法
1. 裁剪组件(使用 menuconfig)
scons --menuconfig
关闭:
RT_USING_FINSH
(如果你不需要 shell)RT_USING_DEVICE
下不使用的设备驱动RT_USING_CONSOLE
(无串口输出时)RT_USING_COMPONENTS_INIT
(控制自动初始化)
特别大户组件:
组件 | 特点 | 是否可裁剪 |
---|---|---|
finsh | shell,代码大 | ✅ 可裁剪 |
libc | 默认 newlib,大 | ✅ 替换为 mini libc |
DFS 文件系统 | 需要块设备支持 | ✅ 可裁剪 |
LVGL / GUI | 非常大(几十 KB) | ✅ 不用就关 |
2. 使用 mini libc 替代 newlib
如果你用的是 newlib
,它默认会占用大量 Flash 和 RAM。
可以在 menuconfig
里:
RT-Thread Components -> C Library -> Use Minimal libc (RT_USING_MINILIBC)
或者选择 Newlib Nano
(更小的版本)。
3. 使用链接器优化参数(推荐)
在 SConstruct
或 board/SConscript
里添加编译优化参数:
CFLAGS += ['-ffunction-sections', '-fdata-sections']
LDFLAGS += ['-Wl,--gc-sections']
这可以自动去掉未被引用的函数和数据段。
4. 静态资源(字体/图片)尽量不编译进 ELF
如果你使用了 GUI / 显示框架,不要直接把图片/font 编译进 .rodata
,可以使用文件系统加载。
5. 控制线程栈大小(RAM 优化)
在 rtconfig.h
中找到类似:
#define RT_MAIN_THREAD_STACK_SIZE 2048
#define IDLE_THREAD_STACK_SIZE 512
调小主线程或其他任务的 stack size
(慎重,调太小会栈溢出)。
🧪 三、推荐操作流程
arm-none-eabi-size rt-thread.elf
看整体使用arm-none-eabi-nm -S --size-sort
看哪个函数最大- 进入
menuconfig
精简组件 - 开启链接裁剪:
--gc-sections
- 改小任务栈大小(RAM 优化)
- 减少静态资源(如 GUI 字体、图像)
(rtenv) link@link-PC:~/rt401$ arm-none-eabi-nm -S --size-sort rt-thread.elf | tail -n 30
08000660 000002de T strcmp
0800c1e8 000002e0 t lfs_dir_commitcrc
08012f3c 00000308 T HAL_GPIO_Init
08012498 00000314 T HAL_DMA_IRQHandler
080075fc 00000336 t print_number
0801d68c 0000033c t hardware_init
0801431c 00000352 T HAL_SPI_TransmitReceive
08010d48 00000360 t lfs_fs_deorphan
0801b2f4 00000370 t rt_serial_open
08022f7c 00000378 t _rt_mutex_take
0801e7b8 0000037e t read_basic_table
0800e368 000003a0 t lfs_file_opencfg_
0800c780 000003ec t lfs_dir_compact
08002ddc 0000041c t rt_hw_spi_bus_init
20001158 00000434 b dfs_fm
0800f4c8 0000043c t lfs_rename_
080100e4 00000440 t lfs_mount_
0800faa4 00000494 t lfs_init
080028dc 00000498 t spixfer
080161d4 000004e8 t UART_SetConfig
080134a0 000004f0 W HAL_RCC_OscConfig
08015510 00000524 T HAL_UART_IRQHandler
0800cd9c 00000574 t lfs_dir_relocatingcommit
0800d310 000005f0 t lfs_dir_orphaningcommit
08021860 000005f8 t finsh_thread_entry
08007934 00000626 T rt_vsnprintf
0800b4c4 000007a0 t lfs_dir_fetchmatch
0800ad00 000007c4 t lfs_dir_traverse
0801cd88 0000087c t sf
08004f90 000008dc t fal
贴出的内容是通过 arm-none-eabi-nm -S --size-sort
得到的函数空间占用清单,下面是分析结果和建议,帮助你判断可以精简哪些模块,进一步缩减程序大小。
🧩 一、当前最大空间占用函数(Top 函数分析)
地址 | 大小(字节) | 函数名 | 所属模块 |
---|---|---|---|
08004f90 | 0x8dc = 2268 | fal | 🔶 FAL(Flash抽象层) |
0801cd88 | 0x87c = 2172 | sf | 🔶 SFUD(SPI Flash) |
0800ad00 | 0x7c4 = 1988 | lfs_dir_traverse | 🔶 LittleFS |
0800b4c4 | 0x7a0 = 1952 | lfs_dir_fetchmatch | 🔶 LittleFS |
08007934 | 0x626 = 1574 | rt_vsnprintf | ✅ RT-Thread 内核 |
08021860 | 0x5f8 = 1528 | finsh_thread_entry | 🔶 Finsh shell |
0800d310 | 0x5f0 = 1520 | lfs_dir_orphaningcommit | 🔶 LittleFS |
0800cd9c | 0x574 = 1396 | lfs_dir_relocatingcommit | 🔶 LittleFS |
08015510 | 0x524 = 1316 | HAL_UART_IRQHandler | ✅ HAL UART |
080134a0 | 0x4f0 = 1264 | HAL_RCC_OscConfig | ✅ HAL RCC |
080161d4 | 0x4e8 = 1256 | UART_SetConfig | ✅ HAL UART |
080028dc | 0x498 = 1176 | spixfer | ✅ SPI 驱动 |
0800faa4 | 0x494 = 1172 | lfs_init | 🔶 LittleFS |
080100e4 | 0x440 = 1088 | lfs_mount_ | 🔶 LittleFS |
🔍 二、占用大户模块总结
模块名 | 占用情况 | 精简建议 |
---|---|---|
LittleFS | 多个函数 > 1KB,总占用 10KB+ | ✅ 如果不用文件系统,强烈建议关闭 |
SFUD | sf 函数 > 2KB | ✅ 如果不用 SPI Flash,关闭 |
FAL | fal 函数 > 2KB | ✅ OTA 方案可替代实现精简版 |
Finsh | finsh_thread_entry 1.5KB+ | ✅ 不需要 Shell 时禁用 |
HAL 驱动 | 含 UART、SPI、GPIO 等 | ✅ 不使用的外设禁用 HAL 模块 |
rt_vsnprintf | 打印格式化支持 | ✅ 如果调试信息不需要格式化,可以替换成简化版或禁用调试输出 |
✂️ 三、精简建议清单
✅ 1. 关闭文件系统(LittleFS)
如果你不需要 LFS 文件系统(例如 OTA 用原始 Flash 映射),建议关闭:
RT_USING_DFS=n
RT_USING_LITTLEFS=n
或者在 menuconfig
:
RT-Thread Components → Device Virtual File System (DFS) → 取消勾选 LittleFS
可节省 >10KB Flash
✅ 2. 禁用 SFUD、FAL(如不用 OTA 或 SPI Flash)
如果你后期不打算启用 OTA / 外部 Flash:
RT_USING_SFUD=n
RT_USING_FAL=n
这两者大约可释放 >4KB Flash
✅ 3. 禁用 Finsh shell
RT_USING_FINSH=n
节省 约 3~5KB Flash + RAM
✅ 4. HAL 驱动精简
如只用 UART1,就关闭 SPI、I2C、ADC 等 HAL 驱动模块:
Hardware Drivers Config → HAL Drivers → 关闭未使用的外设
✅ 5. 格式化打印替换或精简
#define rt_kprintf printf → 精简为裸串口输出函数
或者直接在 menuconfig
:
RT-Thread Components → Console settings → Disable "Show more log"
🔧 附加建议:手动实现 OTA(免 FAL)
你可以完全不使用 FAL、DFS、SFUD,而是:
- 自定义 OTA 区域(在外部 Flash 固定地址)
- 程序下载直接写入指定地址
- 验证通过后重启、切换入口地址(或设置标志位)
这种方式体积最小,仅需 Flash 驱动和 bootloader 一小段跳转逻辑。