阅读本文前,建议先阅读《esp8266 内存分布》和《esp8266 段的概念》。
通过《esp8266 段的概念》,我们已经知道 .rodata 会占用 dRAM 的空间,从而减小实际可用的 heap 空间大小。因此我们当前的优化目标就是减小 .rodata 的使用量。
os_printf
为了减小打印字符串所占用的 dRAM 空间,在 sdk 中一直存在着一个看上去有些奇怪的打印宏 os_printf
,我们来看看它是怎么定义的:
#define os_printf(fmt, ...) do { \
static const char flash_str[] ICACHE_RODATA_ATTR STORE_ATTR = fmt; \
printf(flash_str, ##__VA_ARGS__); \
} while(0)
- 1
- 2
- 3
- 4
首先是 ICACHE_RODATA_ATTR 的定义,如下:
#define ICACHE_RODATA_ATTR __attribute__((section(".irom.text")))
- 1
再在 ld/eagle.app.v6.common.ld 中看看 .irom.text 被放在哪儿了:
.irom0.text : ALIGN(4)
{
...
*(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text)
...
} >irom0_0_seg :irom0_0_phdr
- 1
- 2
- 3
- 4
- 5
- 6
ok,os_printf
里就是把打印的字符串,通过 .irom.text 这个 attribute 强制放到 .irom0.text 段,从而节省了部分 dRAM 空间。
下面我们就来找找,这个字符串具体在哪,还是以 rtos 的 openssl_demo 为例,编译完之后:
cd user/.output/eagle/debug/lib
xtensa-lx106-elf-size -A libuser.a
- 1
- 2
我们会看到如下信息:
.irom.text 48 0
- 1
通过以下命令:
xtensa-lx106-elf-objdump -s -j .irom.text libuser.a
- 1
可看到具体的 .irom.text:
Contents of section .irom.text:
0000 53444b20 76657273 696f6e3a 25732025 SDK version:%s %
0010 640a0000 73746120 676f7420 6970202c d...sta got ip ,
0020 20637265 61742074 61736b20 25640a00 creat task %d..
- 1
- 2
- 3
- 4
结合 user_main.c 看一看,清晰了。我们再通过 *.dump,可以找到这段字符串的具体位置:
402295b0 63206970 210a0000 53444b20 76657273 c ip!...SDK vers
402295c0 696f6e3a 25732025 640a0000 73746120 ion:%s %d...sta
402295d0 676f7420 6970202c 20637265 61742074 got ip , creat t
402295e0 61736b20 25640a00 7461736b 20657869 ask %d..task exi
- 1
- 2
- 3
- 4
ld 文件的修改
esp8266 rtos sdk 从 commit:17c49717 开始,支持将 rodata 放在 spi flash 中,从而增加可使用的 dRAM 空间,即可增加应用可使用的 heap 空间。这是咋实现的?细心的同学可能在提交中发现 ld/eagle.app.v6.common.ld 中的 .irom0.text 段变了。但这次提交也不仅仅只是修改了 ld,在 sdk 中也做了相应的修改,从而实现了该功能。
.irom0.text : ALIGN(4)
{
_irom0_text_start = ABSOLUTE(.);
*libuser.a:(.rodata.* .rodata)
*libcirom.a:(.rodata.* .rodata)
*libmbedtls.a:(.rodata.* .rodata)
*libssl.a:(.rodata.* .rodata)
*libopenssl.a:(.rodata.* .rodata)
*(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text)
*(.literal.* .text.*)
_irom0_text_end = ABSOLUTE(.);
} >irom0_0_seg :irom0_0_phdr
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
多了些 *libuser.a:(.rodata.* .rodata)
这样的行,干啥滴?其实就是把如 libuser.a 中的所有 .rodata.* 和 .rodata 放到 .irom0.text 段里。
实验一下,我们先把这些行删除,编译后,查看 *.dump 文件,.rodata 信息是这样的:
1 .irom0.text 0005375c 40201010 40201010 00009170 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .rodata 00001138 3ffe88b0 3ffe88b0 00000990 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
- 1
- 2
- 3
- 4
.rodata 占用了 0x1138 字节。
恢复,编译后查看 *.dump 文件,.rodata 信息是这样的:
1 .irom0.text 000546ec 40201010 40201010 000081e0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .rodata 000001a8 3ffe88b0 3ffe88b0 00000990 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
- 1
- 2
- 3
- 4
.rodata 占用了 0x1a8 字节,相较之前的,节省了约 4KB 空间。对比 .irom0.text,则增加了约 4KB 空间。
当前 ld 文件中仅将部分库中的 .rodata 置于 .irom0.text 段,如果用户需要将其他库中的 .rodata 置于 .irom0.text 段,可以通过修改 ld 文件来实现。
例如 rtos 的 openssl_demo 中,programs 中的 .rodata 默认并未被放在 .irom0.text 中,可以在 ld 文件中添加如下行:
*openssl_demo.a:(.rodata.* .rodata)
- 1
为啥是这个名字的库,结合 openssl_demo/programs/Makefile 看,如果你要修改,还得具体看实际情况了。
重新编译后,查看 *.dump 文件:
1 .irom0.text 000546fc 40201010 40201010 000081d0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .rodata 00000190 3ffe88b0 3ffe88b0 00000990 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
- 1
- 2
- 3
- 4
结合上面的数据,.rodata 近一步减小。
注意:
我们不建议将 sdk 中的其他库加入进来。
总结
rtos sdk 在 commit 17c49717 之后,可以通过修改 ld 文件的方法将特定库中的 .rodata 数据置于 spi flash,在这个库中可以直接使用 printf 来进行打印。
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
</div>