最小化二进制文件大小

[English]

ESP-IDF 构建系统会编译项目和 ESP-IDF 中所有源文件,但只有程序实际引用的函数和变量才会链接到最终的二进制文件中。在某些情况下,需要减小固件二进制文件的总大小,例如,为使固件适配 flash 分区大小。

要减小固件二进制文件总大小,首先要找到导致其大小增加的原因。

测量静态数据大小

为了优化固件二进制文件大小和内存使用,需要测量项目中静态分配的 RAM (data, bss),代码 (text) 和只读数据 (rodata)。idf.py 工具的子命令 sizesize-componentssize-files 可分别用于检查不同级别静态分配的 RAM 使用情况。详情请参阅 IDF Size 工具。

链接器映射文件

备注

这是一种非常有用的进阶分析方法。可以先跳转到 减小总体文件大小,以后再详阅这一部分。

分析工具 idf.py size 通过解析 GNU binutils 的“链接器映射文件”来输出结果,该文件囊括了链接器在创建(即链接到)最终固件二进制文件时的所有操作。

链接器映射文件本身是纯文本文件,因此可以进行读取并准确了解链接器的操作,但这些文件非常复杂冗长,通常有 100,000 行甚至更多。

映射文件分为多个部分,每个部分各有标题,包括:

  • Archive member included to satisfy reference by file (symbol)

    • 该列表显示了链接器链接各个目标文件时所搜寻的特定符号(函数或变量)。

    • 要了解二进制文件包含特定目标文件的原因,可以查看该列表以及文件末尾的 Cross Reference Table

    备注

    请注意,并非每个显示在该列表中的目标文件最后都会出现在最终二进制文件中,有些目标文件可能会列在 Discarded input sections 中。

  • Allocating common symbols

    • 该列表显示了部分全局变量及其大小。常见符号在 ELF 二进制文件中具有特定含义,但 ESP-IDF 并未广泛使用这些符号。

  • Discarded input sections

    • 在链接器读取目标文件时,会将一些输入段作为文件的一部分读取并准备链接到最终的二进制文件中,但由于没有其他部分引用这些输入段,这些段最终会被丢弃。

    • 对于 ESP-IDF 项目来说,这个列表可能会非常长,因为我们将每个函数和静态变量都编译到一个独立的段中,以最小化最终二进制文件的大小。具体而言,ESP-IDF 将使用编译器选项 -ffunction-sections -fdata-sections 和链接器选项 --gc-sections

    • 在这个列表中出现的条目 不会 对最终的二进制文件大小产生影响。

  • Memory ConfigurationLinker script and memory map

    • 这两部分相互关联。输出结果的一部分来自由 构建系统 提供的链接器命令行和链接脚本,部分链接脚本由 ESP-IDF 项目通过 链接器脚本生成机制 功能生成。

    • 在 map 文件的 Linker script and memory map 输出中,会显示链接到最终二进制文件中的每个符号(函数或静态变量)及其地址(以 16 位十六进制数字表示)和长度(也以十六进制表示),还有链接的库和目标文件(可以用于确定组件和源文件)。

    • 在所有占用最终 .bin 文件的输出段之后, memory map 还会显示一些 ELF 文件中用于调试的段,如 .debug_* 等。这些段不会对最终的二进制文件大小产生影响,且这些符号的地址数值很小,从 0x0000000000000000 开始递增。

  • Cross Reference Table

    • 该表格显示了引用了各个符号(函数或静态变量)的目标文件。了解二进制文件包含某个特定符号的原因,可参考该表格以确定引用特定符号的目标文件。

    备注

    Cross Reference Table 不仅包含最终二进制文件中的符号,还包含已丢弃的段内符号。因此,某个符号出现在该表中并不意味着最终二进制文件包含这一符号,需要单独检查。

备注

链接器映射文件由 GNU binutils 的链接器 ld 而非由 ESP-IDF 生成。本快速概览专从 ESP-IDF 构建系统的角度编写而成,建议自行搜索更多关于链接器映射文件格式的信息。

减小总体文件大小

可以通过以下配置选项减小几乎所有 ESP-IDF 项目最终二进制文件的大小:

备注

除了上述众多配置项之外,还有一些配置选项在更改为非默认设置时会增加二进制文件的大小,这些选项未在此列出。配置项的帮助文本中通常会阐明显著增加二进制文件大小的设置。

针对性优化

以下二进制文件大小优化适用于特定的组件或函数:

lwIP IPv6

  • CONFIG_LWIP_IPV6 设置为 false 可以减小 lwIP TCP/IP 堆栈的大小,但将仅支持 IPv4。

    备注

    如果禁用 IPv6,ASIO 端口 等组件将无法使用。

lwIP IPv4

  • 如果不需要 IPv4 连接功能,将 CONFIG_LWIP_IPV4 设置为 false 可以减小 lwIP 的大小,使其成为仅支持 IPv6 的 TCP/IP 堆栈。

    备注

    在禁用 IPv4 支持之前,请注意,仅支持 IPv6 的网络环境尚未普及,必须在本地网络中提供支持,例如,由互联网服务供应商提供支持,或使用受限制的本地网络设置。

使用 Picolibc 替代 Newlib

默认情况下,ESP-IDF 使用 Newlib C 库,同时也对 Picolibc C 库提供实验性支持。

Picolibc C 库提供了更精简的 printf 系列函数,并且根据应用程序,可以将二进制文件大小减少最多 30 KB。

如需切换链接到 Picolibc C 库,请启用配置选项 CONFIG_IDF_EXPERIMENTAL_FEATURESCONFIG_LIBC_PICOLIBC

Newlib Nano 格式化

ESP-IDF 的 I/O 函数( printf()scanf() 等)默认使用 Newlib 的 “完整” 格式化功能。

启用配置选项 CONFIG_LIBC_NEWLIB_NANO_FORMAT 将 Newlib 切换到 Nano 格式化模式。从而减小了代码体积,同时大部分内容被编译到 ESP32-P4 的 ROM 中,因此不需要将其添加至二进制文件中。

具体的二进制文件大小差异取决于固件使用的功能,但通常为 25 KB 到 50 KB。

启用 Nano 格式化会减少调用 printf() 或其他字符串格式化函数的堆栈使用量,参阅 确定栈内存大小

“Nano” 格式化不支持 64 位整数或 C99 格式化功能。请在 Newlib README 文件 中搜索 --enable-newlib-nano-formatted-io 来获取完整的限制列表。

MbedTLS 功能

Component Config > mbedTLS 下有多个默认启用的 mbedTLS 功能,如果不需要,可以禁用相应功能以减小代码大小。

这些功能包括:

每个选项的帮助文本中都有更多信息可供参考。

重要

强烈建议不要禁用所有 mbedTLS 选项。 仅在理解功能用途,并确定在应用程序中不需要此功能时,方可禁用相应选项。请特别注意以下两点:

  • 确保设备连接的任何 TLS 服务器仍然可用。如果服务器由第三方或云服务控制,建议确保固件至少支持两种 TLS 密码套件,以防未来某次更新禁用了其中一种。

  • 确保连接设备的任何 TLS 客户端仍然可以使用支持/推荐的密码套件进行连接。请注意,未来版本的客户端操作系统可能会移除对某些功能的支持,因此建议启用多个支持的密码套件或算法以实现冗余。

如果依赖于第三方客户端或服务器,请密切关注其有关支持的 TLS 功能的公告和变更。否则,当所支持功能变更时,ESP32-P4 设备可能无法访问。

备注

ESP-IDF 并未测试所有 mbedTLS 编译配置组合。如果发现某个组合无法编译或无法按预期执行,请在 GitHub 上报告详细信息。

虚拟文件系统 (VFS)

在 ESP-IDF 中,虚拟文件系统组件 功能允许使用标准的 I/O 函数(如 openreadwrite 等)和 C 库函数(如 fopenfreadfwrite 等)来访问多个文件系统驱动程序和类似文件的外设驱动程序。当应用程序中不需要文件系统或类似文件的外设驱动功能时,可以部分或完全禁用该功能。VFS 组件提供以下配置选项:

  • CONFIG_VFS_SUPPORT_TERMIOS — 如果应用程序不使用 termios 函数族,可以禁用此选项。目前,这些函数仅在 UART VFS 驱动程序中实现,大多数应用程序可以禁用此选项。禁用后可以减小约 1.8 KB 代码大小。

  • CONFIG_VFS_SUPPORT_SELECT — 如果应用程序不使用 select 函数处理文件描述符,可以禁用此选项。目前,只有 UART 和 eventfd VFS 驱动程序支持 select 函数。请注意,当禁用该选项时,仍然可以使用 select 处理套接字文件描述符。禁用此选项将减小约 2.7 KB 代码大小。

  • CONFIG_VFS_SUPPORT_DIR — 如果应用程序不使用与目录相关的函数,例如 readdir (参阅此选项的描述以获取完整列表),可以禁用此选项。如果应用程序只需打开、读取和写入特定文件,而不需要枚举或创建目录,可以禁用此选项,从而减少超过 0.5 KB 代码大小,具体减小多少取决于使用的文件系统驱动程序。

  • CONFIG_VFS_SUPPORT_IO — 如果应用程序不使用文件系统或类似文件的外设驱动程序,可以禁用此选项,这将禁用所有 VFS 功能,包括上述三个选项。当禁用此选项时,无法使用 控制台终端。请注意,当禁用此选项时,应用程序仍然可以使用标准 I/O 函数处理套接字文件描述符。相较于默认配置,禁用此选项可以减小约 9.4 KB 代码大小。

HAL

控制台

对于支持 USB-Serial-JTAG 的目标芯片,默认情况下会同时启用 USB-Serial-JTAG 和 UART 控制台输出。如果只需要使用单个控制台,可以通过以下操作减少二进制文件大小和 RAM 使用量:

  1. 禁用次要控制台:将 CONFIG_ESP_CONSOLE_SECONDARY 设置为 CONFIG_ESP_CONSOLE_SECONDARY_NONE

  2. CONFIG_ESP_CONSOLE_UART 设置为以下选项之一:

    • UART:可减少约 2.5 KB 的二进制文件大小。

    • USB-Serial-JTAG:可减少约 10 KB 的二进制文件大小和约 1.5 KB 的 DRAM 使用量。

请注意,以上空间节省的前提条件是 UART/USB-Serial-JTAG 驱动代码未被应用程序引用。如果因为其他用途使用了这些驱动程序,则节省效果会有所降低。

引导加载程序大小

本文档仅涉及 ESP-IDF 应用程序的二进制文件大小,而不涉及 ESP-IDF 二级引导加载程序

关于 ESP-IDF 引导加载程序二进制文件大小的讨论,请参阅 引导加载程序大小

IRAM 二进制文件大小

如果二进制文件的 IRAM 部分过大,可以通过减少 IRAM 使用来解决这个问题,参阅 IRAM 优化


此文档对您有帮助吗?