ESP-IDF 链接器脚本生成机制(Linker Script Generation)完全指南:从.lf片段到自定义内存布局
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
导读
在 ESP-IDF 中,代码与数据默认被放置到固定位置:可执行代码与只读数据在 Flash 中,可写数据在 RAM 中。但在实际开发中,我们经常需要打破这种默认布局——例如把性能关键代码搬进 IRAM、把深睡眠唤醒存根或 ULP 协处理器代码放进 RTC 内存。链接器脚本生成(Linker Script Generation)机制正是 ESP-IDF 提供的、在组件级别声明这种放置意图的方案:组件通过.lf链接器片段文件描述自己的放置规则,构建系统在编译时收集、解析并处理这些信息,最终动态生成链接脚本用于链接应用。读完本文,你将掌握在 ESP-IDF 中创建链接器片段文件、在 CMake 中注册片段、以对象/符号/归档三级粒度指定放置位置、编写基于 Kconfig 的条件放置规则,并理解其底层实现原理(片段类型、Scheme、链接脚本模板与标记语法)。
1. 为什么需要链接器脚本生成机制
ESP-IDF 芯片拥有多个内存区域(可参考 memory-layout 相关文档),代码和数据可以放置到不同区域中。默认情况下:
- 代码和只读数据(rodata)放在 Flash;
- 可写数据(data/bss)放在 RAM;
- 某些特殊数据放在 RTC 内存。
但以下场景要求改变默认放置:
- 性能关键代码需要放入 RAM(IRAM/DRAM)以提升执行速度;
- 可执行代码放入 IRAM,以便在 Cache 被禁用时依然可以运行(如 panic handler);
- RTC 内存中的代码,用于深睡眠唤醒存根(wake stub,在支持该特性的芯片上);
- RTC 内存中的代码,供 ULP 协处理器使用(在支持 ULP 的芯片上)。
链接器脚本生成机制使得这些放置需求可以在组件级声明:组件说明希望如何放置自己的符号(symbol)、目标文件(object)或整个归档(archive)。构建期间,ESP-IDF 收集各组件提交的信息,进行解析与处理,将生成的放置规则用于链接应用。
核心工作流:组件提供 .lf 片段 → 构建系统收集解析 → 动态生成链接脚本 → 链接器按脚本放置代码/数据。
2. 快速开始:将代码/数据放入 RAM 与 RTC 内存
本节演示 ESP-IDF 开箱即用的放置方案(noflash与rtc),假定存在如下组件:
components └── my_component ├── CMakeLists.txt ├── Kconfig ├── src/ │ ├── my_src1.c │ ├── my_src2.c │ └── my_src3.c └── my_linker_fragment_file.lf前提条件:
- 组件
my_component在构建时归档为库libmy_component.a; - 三个源文件
my_src1.c、my_src2.c、my_src3.c分别编译为my_src1.o、my_src2.o、my_src3.o; my_src1.o中定义了函数my_function1,my_src2.o中定义了函数my_function2;- 组件的 Kconfig 中有布尔型配置
PERFORMANCE_MODE(y/n)和整型配置PERFORMANCE_LEVEL(取值范围 0-3)。
2.1 创建并注册链接器片段文件
链接器片段文件就是一个以.lf为扩展名的纯文本文件,将期望的放置规则写在其中。创建后需要告知构建系统:
在组件CMakeLists.txt的idf_component_register调用中指定LDFRAGMENTS参数。其值可以是绝对路径,也可以是从组件目录到片段文件的相对路径:
# 文件路径相对于 CMakeLists.txt idf_component_register(... LDFRAGMENTS "path/to/linker_fragment_file.lf" "path/to/another_linker_fragment_file.lf" ... )ESP-IDF 自身大量使用该机制。例如 esp_system/CMakeLists.txt 中注册了linker.lf与app.lf两个片段文件;freertos/CMakeLists.txt 则根据 FreeRTOS 的配置(SMP/单核等)动态拼接linker_common.lf、linker_smp.lf、linker.lf列表后通过LDFRAGMENTS传入。这印证了LDFRAGMENTS是组件与链接器脚本生成器之间的标准接口。
2.2 放置粒度
放置可以在以下粒度层面指定:
- 目标文件级(
.obj/.o文件) - 符号级(函数/变量)
- 归档级(
.a文件)
放置整个目标文件
假设my_src1.o整体是性能关键代码,需要放入 RAM;my_src2.o整体包含深睡眠唤醒所需符号,需要放入 RTC 内存。在片段文件中写:
[mapping:my_component] archive: libmy_component.a entries: my_src1 (noflash) # 将 my_src1 的所有代码/只读数据放入 IRAM/DRAM my_src2 (rtc) # 将 my_src2 的所有代码/数据/只读数据放入 RTC 快速内存/RTC 慢速内存未被指定的my_src3.o将使用默认放置(详见下文 "[default] 放置" 一节)。
放置单个符号
如果my_src1.o中只有my_function1是性能关键,my_src2.o中只有my_function2需要在芯片从深睡眠唤醒后执行:
[mapping:my_component] archive: libmy_component.a entries: my_src1:my_function1 (noflash) my_src2:my_function2 (rtc)my_src1.o与my_src2.o中的其余函数以及整个my_src3.o使用默认放置。放置数据与此类似,只需把函数名换成变量名:
my_src1:my_variable (noflash)警告:符号粒度放置存在局限性,为保证正确放置,更推荐将相关代码/数据组织到独立源文件中,然后使用对象粒度放置。
放置整个归档
将整个组件归档放入 RAM:
[mapping:my_component] archive: libmy_component.a entries: * (noflash)将整个组件放入 RTC 内存:
[mapping:my_component] archive: libmy_component.a entries: * (rtc)2.3 配置相关的条件放置
只有当某条件成立时才需要特殊放置。例如当CONFIG_PERFORMANCE_MODE == y时才将整个库放入 RAM:
[mapping:my_component] archive: libmy_component.a entries: if PERFORMANCE_MODE = y: * (noflash) else: * (default)更复杂的场景:CONFIG_PERFORMANCE_LEVEL == 1时仅my_src1.o入 RAM;== 2时my_src1.o与my_src2.o;== 3时归档内所有对象入 RAM;以上均不成立时整个库放入 RTC 内存:
[mapping:my_component] archive: libmy_component.a entries: if PERFORMANCE_LEVEL = 1: my_src1 (noflash) elif PERFORMANCE_LEVEL = 2: my_src1 (noflash) my_src2 (noflash) elif PERFORMANCE_LEVEL = 3: my_src1 (noflash) my_src2 (noflash) my_src3 (noflash) else: * (rtc)条件判断支持嵌套,下面的写法与上面等价:
[mapping:my_component] archive: libmy_component.a entries: if PERFORMANCE_LEVEL <= 3 && PERFORMANCE_LEVEL > 0: if PERFORMANCE_LEVEL >= 1: object1 (noflash) if PERFORMANCE_LEVEL >= 2: object2 (noflash) if PERFORMANCE_LEVEL >= 3: object2 (noflash) else: * (rtc)(注意:上述等价写法中的object1/object2为文档示例的原始命名,实际应使用my_src1/my_src2。)
2.4 "default" 放置
此前一直用"默认放置"指代未指定rtc/noflash规则时的回退方案。需要强调:noflash与rtc并非普通关键字,而是被称为scheme(方案)的片段实体。与它们类似,还存在一个defaultscheme,定义默认放置规则——代码/常量放 Flash、变量放 RAM 等。
提示:ESP-IDF 中使用链接器脚本生成机制的典型示例可参考 freertos/CMakeLists.txt 及其配套的 linker_common.lf。FreeRTOS 通过它把对象文件放入指令 RAM 以获得性能提升。
3. 链接器脚本生成机制的内部原理
链接是 C/C++ 源文件变成可执行文件的最后一步,由工具链的链接器执行,链接器接受指定代码/数据放置方式的链接脚本。在链接器脚本生成机制下,过程并无不同,唯一区别是传给链接器的链接脚本是动态生成的,其来源是:(1) 收集到的链接器片段文件与 (2) 链接器脚本模板。
实现工具:实现该机制的工具位于 tools/ldgen(入口为
ldgen.py),其中包含解析片段、生成规则的完整 Python 实现与测试用例。
3.1 链接器片段文件
快速开始中提到"片段文件是以.lf为扩展名、包含期望放置规则的纯文本文件"——这是简化描述。片段文件实际包含的是片段(fragment):片段是携带信息的实体,这些信息组合起来形成放置规则,告诉链接器将目标文件的各个 section 放置到输出二进制的何处。共有三种片段类型:sections、scheme与mapping。
语法
三种片段类型共享同一语法:
[type:name] key: value key: value value value ...- type:片段类型,可取
sections、scheme或mapping; - name:片段名称,对指定片段类型而言必须唯一;
- key, value:片段内容;每种片段类型支持不同的 key 与不同的取值语法:
- 对于
sections与scheme,唯一支持的 key 是entries; - 对于
mapping,同时支持archive与entries。
- 对于
注意:若遇到多个同类型同名的片段,将抛出异常。
注意:片段名称与 key 的合法字符仅为字母数字与下划线。
条件检查
条件检查使链接器脚本生成具备配置感知能力。根据涉及配置值的表达式是否为真,决定使用 key 的某一组取值。求值使用 kconfiglib 包的eval_string,遵循其要求的语法与限制。支持的运算符:
- 比较:
<(LessThan)、<=(LessThanOrEqualTo)、>(MoreThan)、>=(MoreThanOrEqualTo)、=(Equal)、!=(NotEqual) - 逻辑:
||(Or)、&&(And)、!(Negation) - 分组:
()(Parenthesis)
条件检查的行为与常规语言中的if...elseif/elif...else块一致。条件既可用于key 值,也可用于整个片段。下面两个片段等价:
# key 的值取决于配置 [type:name] key_1: if CONDITION = y: value_1 else: value_2 key_2: if CONDITION = y: value_a else: value_b# 整个片段定义取决于配置 if CONDITION = y: [type:name] key_1: value_1 key_2: value_a else: [type:name] key_1: value_2 key_2: value_b注释
片段文件中的注释以#开头,处理时被忽略,用于提供说明与文档。
3.2 片段类型详解
Sections(段)
Sections 片段定义 GCC 编译器生成的目标文件 section 列表。可以是默认 section(如.text、.data),也可以是通过__attribute__关键字定义的用户自定义 section。
可选的+后缀表示包含该 section 以及以它开头的所有 section(如.text+表示.text与.text.*)。这是比同时显式列出两者更推荐的做法。
[sections:name] entries: .section+ .section ...示例:
# 非推荐写法 [sections:text] entries: .text .text.* .literal .literal.* # 推荐写法,与上面等价 [sections:text] entries: .text+ # 表示 .text 与 .text.* .literal+ # 表示 .literal 与 .literal.*Scheme(方案)
Scheme 片段定义某个 sections 片段被分配到哪个target(目标)。
[scheme:name] entries: sections -> target sections -> target ...示例:
[scheme:noflash] entries: text -> iram0_text # 名为 text 的 sections 片段中的条目将进入 iram0_text rodata -> dram0_data # 名为 rodata 的 sections 片段中的条目将进入 dram0_data特殊的 "default" scheme
存在名为default的特殊 scheme,其特殊之处在于会从它的条目生成兜底放置规则(catch-all placement rules)。例如,若其条目之一是text -> flash_text,则生成针对目标flash_text的放置规则:
*(.literal .literal.* .text .text.*)这些兜底规则实际上充当那些未指定 mapping的实体的回退规则。
defaultscheme 定义于 esp_system/app.lf,快速开始中引用的内置 scheme(noflash、rtc等)也定义在该文件中。下面摘录app.lf中noflash与rtc的定义,与文档示例完全一致:
[scheme:rtc] entries: text -> rtc_text data -> rtc_data rodata -> rtc_data bss -> rtc_bss common -> rtc_bss [scheme:noflash] entries: text -> iram0_text rodata -> dram0_data此外,app.lf还定义了noflash_data(仅 rodata → dram0_data)、noflash_text(仅 text → iram0_text)、spm等方案,并通过:
[mapping:default] archive: * entries: * (default)把所有未显式映射的归档实体收归default方案。app.lf中的defaultscheme 还演示了如何结合配置条件:当APP_BUILD_USE_FLASH_SECTIONS = y时text -> flash_text、rodata -> flash_rodata,否则回退到iram0_text与dram0_data;当ESP_ALLOW_BSS_SEG_EXTERNAL_MEMORY = y时extram_bss -> extern_ram,否则进入dram0_bss。
Mapping(映射)
Mapping 片段定义可映射实体(目标文件、函数名、变量名、归档)使用哪个 scheme 片段:
[mapping:name] archive: archive # 输出归档文件名,按构建结果(即 libxxx.a) entries: object:symbol (scheme) # 符号粒度 object (scheme) # 对象粒度 * (scheme) # 归档粒度三种放置粒度:
- symbol:同时指定目标文件名与符号名,符号名可以是函数名或变量名;
- object:仅指定目标文件名;
- archive:指定
*,是所有归档下目标文件的简写。
理解条目的含义:以对象粒度放置为例,展开后为:
object (scheme)再把 scheme 片段从其条目定义展开:
object (sections -> target, sections -> target, ...)继续展开 sections 片段:
object (.section, # 给定该目标文件 .section, # 将其此处列出的 section 放到 ... -> target, # 该 target .section, .section, # 这些 section 同样处理 ... -> target, ...) # 依此类推示例:
[mapping:map] archive: libfreertos.a entries: * (noflash)除了实体与 scheme,条目中还可以指定 flags(标志),详见下一节。
3.3 Mapping 条目中的 Flags(标志)
支持以下标志(<>为参数名,[]为可选):
1.ALIGN(<alignment>[, pre, post])
按alignment指定的量对齐放置。在映射片段条目生成的输入 section 描述之前和/或之后(取决于是否指定pre、post,或两者都指定)生成:
. = ALIGN(<alignment>)若pre与post均未指定,对齐命令生成在输入 section 描述之前。顺序敏感。
2.SORT([<sort_by_first>, <sort_by_second>])
在输入 section 描述中发出SORT_BY_NAME、SORT_BY_ALIGNMENT、SORT_BY_INIT_PRIORITY或SORT。sort_by_first与sort_by_second的取值可为:name、alignment、init_priority。若两者都未指定,输入 sections 按名称排序;若都指定,则嵌套排序遵循 GNU ld 文档 "Input Section Wildcards" 中的规则。
3.KEEP()
通过在输入 section 描述外包一层 KEEP 命令,防止链接器丢弃该放置。详见 GNU ld 文档 "Input Section Keep"。
4.SURROUND(<name>)
在放置前后生成符号,生成的符号命名遵循_<name>_start与_<name>_end。例如name == sym1时生成:
_sym1_start = ABSOLUTE(.) ... _sym2_end = ABSOLUTE(.)这些符号可在 C/C++ 代码中引用。顺序敏感。
Flags 的组合写法:添加 flags 时,需要指定 scheme 中具体的section -> target;多个section -> target用逗号分隔:
# 说明: # A. 实体-scheme 后跟分号 # B. section2 -> target2 前有逗号 # C. section1 -> target1 与 section2 -> target2 需定义在 scheme1 的 entries 中 entity1 (scheme1); section1 -> target1 KEEP() ALIGN(4, pre, post), section2 -> target2 SURROUND(sym) ALIGN(4, post) SORT()综合示例:以下 mapping 片段:
[mapping:name] archive: lib1.a entries: obj1 (noflash); rodata -> dram0_data KEEP() SORT() ALIGN(8) SURROUND(my_sym)在链接脚本中生成:
. = ALIGN(8) _my_sym_start = ABSOLUTE(.) KEEP(lib1.a:obj1.*( SORT(.rodata) SORT(.rodata.*) )) _my_sym_end = ABSOLUTE(.)由于 ALIGN 与 SURROUND 均顺序敏感,若调换二者位置,生成的输出变为:
_my_sym_start = ABSOLUTE(.) . = ALIGN(8) KEEP(lib1.a:obj1.*( SORT(.rodata) SORT(.rodata.*) )) _my_sym_end = ABSOLUTE(.)仓库中的真实 Flags 用法:esp_system/linker.lf 为系统事件(esysev_shdn、esysev_initc等)生成的映射条目组合了ALIGN(4, pre)、KEEP()、SORT(init_priority)与SURROUND(esysev_shdn)等标志,用于在 Flash 只读数据区构建带边界符号、按初始化优先级排序且不可被 GC 丢弃的系统事件表。
4. 仓库实例:esp_system与freertos的真实放置规则
4.1 esp_system 的条件与符号粒度映射
esp_system/linker.lf 是理解真实用法的绝佳样本。它展示了:
- 基于配置的条件映射:当
ESP_PANIC_HANDLER_IRAM = y时,将panic、panic_handler、panic_arch、cache_err_int等对象放入 IRAM(noflash),并进一步嵌套条件(ESP_SYSTEM_HW_STACK_GUARD = y时再放置debug_assist相关函数); - 符号粒度映射:
reset_reason:esp_reset_reason_get_hint (noflash)、freertos_hooks:esp_vApplicationTickHook (noflash)等,将单个函数放入 IRAM; - 多条件逻辑:
if ESP_PANIC_HANDLER_IRAM = y || ESP_BROWNOUT_USE_INTR = y:使用逻辑或组合条件。
例如其中一段(节选):
[mapping:esp_system] archive: libesp_system.a entries: if ESP_PANIC_HANDLER_IRAM = y: panic (noflash) panic_handler (noflash) panic_arch (noflash) cache_err_int (noflash) reset_reason:esp_reset_reason_get_hint (noflash) ... # These functions are called when the cache is disabled system_internal:esp_restart_noos (noflash) system_internal:esp_system_reset_modules_on_exit (noflash) ...这些规则与文档描述完全对应:panic 处理函数必须能在 Cache 被禁用时于 IRAM 中运行,esp_restart_noos等复位路径函数同样如此。
4.2 freertos 的对象/归档级映射
freertos/linker_common.lf 展示了归档级与对象级映射的组合:
[mapping:freertos_common] archive: libfreertos.a entries: if FREERTOS_IN_IRAM = y: * (noflash_text) # 所有 FreeRTOS 函数放入 IRAM else: * (default) # 所有 FreeRTOS 函数放入 Flash if FREERTOS_SMP = n && FREERTOS_UNICORE = n: tasks:xTaskIncrementTickOtherCores (noflash_text) if FREERTOS_PLACE_ISR_FUNCTIONS_INTO_FLASH = n: tasks:vTaskGetSnapshot (noflash_text) if ESP_PANIC_HANDLER_IRAM = y: tasks:vTaskGetSnapshot (noflash_text) tasks:uxTaskGetSnapshotAll (noflash_text) tasks:xTaskGetNext (noflash_text)这里既用* (noflash_text)实现归档级整体放置,又用tasks:xTaskIncrementTickOtherCores (noflash_text)等符号级规则把特定 FreeRTOS 内核函数钉在 IRAM 中——这正是文档中"符号粒度用于少量关键符号、对象/归档粒度用于批量放置"的最佳实践。
5. 符号粒度放置的原理与限制
符号粒度放置之所以可行,依赖编译器标志-ffunction-sections与-fdata-sections,ESP-IDF 默认开启这两个标志。如果用户移除它们,符号粒度放置将失效。即便保留标志,由于依赖编译器生成的输出 section,仍存在其他限制:
- 在
-ffunction-sections下,每个函数被发出为独立 section,且 section 名可预测地构造为.text.{函数名}与.literal.{函数名};但函数内的字符串字面量不遵循此规律,它们进入合并或生成的 section 名,无法按符号精确放置。 - 在
-fdata-sections下,全局作用域数据被可预测地发出为.data.{变量名}、.rodata.{变量名}或.bss.{变量名},因此Type I映射条目对这些数据有效。 - 然而,函数作用域内声明的 static 数据不适用:其生成 section 名由变量名与其他信息混合(mangling)而来,无法通过符号粒度放置定位。
因此文档给出的建议是:将相关代码与数据组织进独立源文件,改用对象粒度放置,以确保放置可靠。
6. 链接器脚本模板与标记语法
链接器脚本模板是承载生成放置规则的"骨架"。它本质上是一个普通链接脚本,通过特定标记语法指明生成规则插入的位置。
引用某个target标记下收集到的放置规则时,语法如下:
mapping[target] /* 引用除 SURROUND 之外的所有数据 */对于 SRAM 非连续(
SOC_MEM_NON_CONTIGUOUS_SRAM)的芯片,还额外支持:arrays[target] /* 引用 SURROUND 关键字下的对象 */ mapping[target] /* 引用所有其他数据 */
示例:下面是一个可能的链接器脚本模板节选,定义输出 section.iram0.text,内部有引用目标iram0_text的标记:
.iram0.text : { /* Code marked as running out of IRAM */ _iram_text_start = ABSOLUTE(.); /* Marker referencing iram0_text */ mapping[iram0_text] _iram_text_end = ABSOLUTE(.); } > iram0_0_seg(非连续 SRAM 芯片的模板中还包含arrays[iram0_text]标记。)
假设生成器收集到以下片段定义:
[sections:text] .text+ .literal+ [sections:iram] .iram1+ [scheme:default] entries: text -> flash_text iram -> iram0_text [scheme:noflash] entries: text -> iram0_text [mapping:freertos] archive: libfreertos.a entries: * (noflash)则生成的链接脚本对应节选为:
.iram0.text : { /* Code marked as running out of IRAM */ _iram_text_start = ABSOLUTE(.); /* Placement rules generated from the processed fragments, placed where the marker was in the template */ *(.iram1 .iram1.*) *libfreertos.a:(.literal .text .literal.* .text.*) _iram_text_end = ABSOLUTE(.); } > iram0_0_seg逐条解读生成规则:
*libfreertos.a:(.literal .text .literal.* .text.*):由freertosmapping 片段的* (noflash)条目生成。归档libfreertos.a下所有目标文件的所有textsections 按noflashscheme 收集到目标iram0_text,并放置在模板中任何引用iram0_text标记的位置。*(.iram1 .iram1.*):由 default scheme 的iram -> iram0_text条目生成。由于来自 default scheme,它在同一 target 下收集的所有其他规则中排在最前。
当前模板与产物路径:当前使用的链接器脚本模板为 esp_system/ld/{IDF_TARGET_PATH_NAME}/sections.ld.in,生成的sections.ld位于构建目录下。在 esp_system/CMakeLists.txt 中可以看到:
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/${target}/memory.ld.in") # 生成 sections.ld.in 并交由链接器脚本生成器处理 target_linker_script(${COMPONENT_LIB} INTERFACE "ld/${target}/sections.ld.in" PROCESS "${CMAKE_CURRENT_BINARY_DIR}/ld/sections.ld")sections.ld.in模板内部通过宏(如SECTION_MAPPINGS(rtc_text)、SECTION_MAPPINGS_WITH_PADDING(flash_rodata)、ALIGNED_SYMBOL(...))引用 target。例如 esp32 的模板 中:
.rtc.text : { ALIGNED_SYMBOL(4, _rtc_text_start) SECTION_MAPPINGS(rtc_text) *rtc_wake_stub*.*(.literal .text .literal.* .text.*) _rtc_text_end = ABSOLUTE(.); } > rtc_iram_segSECTION_MAPPINGS(rtc_text)即展开为所有映射到rtc_texttarget 的放置规则;.flash.rodata中则使用SECTION_MAPPINGS_WITH_PADDING(flash_rodata)(模板 323-327 行)在规则间插入填充以满足 Flash 只读数据段的地址对齐要求。整个流程的输入输出关系为:
组件 .lf 片段(LDFRAGMENTS)+ sections.ld.in 模板 │ 收集、解析、处理(tools/ldgen) ▼ sections.ld(构建目录下的最终链接脚本)→ 链接器7. 总结:何时使用链接器脚本生成机制
| 需求场景 | 推荐做法 |
|---|---|
| 少量关键函数进 IRAM(性能/中断/Cache 禁用路径) | 符号粒度:src_file:func (noflash) |
| 整个源文件批量放置 | 对象粒度:src_file (noflash)/(rtc) |
| 整个组件/归档统一放置 | 归档粒度:* (noflash)/* (rtc) |
| 放置随 Kconfig 配置变化 | 条件放置:if CONFIG_X = y: ... elif ... else ... |
| 放置区域需要对齐/排序/防裁剪/边界符号 | flags:ALIGN()、SORT()、KEEP()、SURROUND() |
| 涉及函数内 static 数据或字符串字面量的精确放置 | 避免符号粒度,改用对象粒度组织源文件 |
链接器脚本生成机制让 ESP-IDF 的内存布局控制从"手写整个链接脚本"降维为"组件自描述放置意图"。掌握.lf片段的语法、三种粒度、条件求值与 flags,再结合 esp_system/app.lf、esp_system/linker.lf、freertos/linker_common.lf 这些真实样本,即可为自己的组件定制高性能、低功耗场景下的精确内存布局。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考