AliOS Things内核延迟加载技术

一、概述

在某些应用场景中,要求系统能快速启动。从用户视角看,只有当系统的应用逻辑开始运行的时候,才算启动完成。所以,启动时间并不只是从系统上电到操作系统完成初始化,还包从初始化完成到基本功能开始运行的时间。比如IP Camera要求从上电到第一帧稳定出图的时间小于250ms,这个启动时间包含了第一帧图像的采集与显示。

本文阐述了AliOS Things上的内核延迟加载技术,可有效降低系统启动时间。

二、操作系统可执行程序的组成

操作系统的可执行程序主要包含text、rodata、data、bss段。其中text存放代码;rodata段存放只读数据,比如字符串;data段存放初始化为非0的全局变量;bss段存放未初始化和初始化为0的全局变量。

编译生成的可执行程序bss段上并没有实质性内容,只需在OS初始化时将其对应的内存区域全部清0即可,因此生成的系统镜像只包含text、rodata、data段。

AliOS Things内核延迟加载技术

编译生成系统镜像后,用烧录器将镜像烧写到Flash上。当系统上电或RESET时,首先运行BootLoader,它从FLASH读取系统镜像,并将其写到指定的RAM区域,然后跳转到系统入口。

为了尽早暴露问题,提高系统的稳定性,操作系统通常根据段的特点设置其所在内存的属性。text段设置为只读可执行,这样往该区域写数据将直接触发异常,避免指令被误修改引发不可预知的问题。rodata段也类似,操作系统只会从该段读取数据不会往该段写数据,因此设置为只读属性。

三、延迟加载

读取Flash较为耗时,以读速度20MB/s的flash为例,BootLoader读取2MB内核镜像需要约100ms。对于需在250ms内出图的IP Camera应用来说,这个时间已经耗去40%,必须降低该时间。

从操作系统初始化到启动完成并不需要所有的代码段与数据段。因此可以把内核的加载操作分为两个阶段,第一阶段由BootLoader加载,包括操作系统启动需要的代码和数据。第二阶段在操作系统完成启动后,由操作系统自己把镜像剩余部分加载到内存。各个阶段的如下:

AliOS Things内核延迟加载技术

具体而言,延迟加载技术通过链接脚本将OS镜像分为启动加载和延迟加载两部分。从原始的text、rodata、data段中分离出延迟加载的post_text、post_rodata、post_data段。其中text、rodata、data段由BootLoader加载,post_text、post_rodata、post_data段由操作系统加载。具体划分如下图所示:

AliOS Things内核延迟加载技术

链接脚本有两种方式来支持上述内核镜像布局。

3.1、正向选择

链接脚本语法如下:
archive:file (.segment)
其中,archive是库名,通常是.a为后缀的库。file是库内文件,通常为.o后缀的目标文件,file也可以省略,表示链接archive内的所有文件。archive:file也可以用通配符*,表示链接所有的文件。通过该语句可以指定只链接某个库的段或链接所有库的段。因此可在链接脚本中先链接启动加载部分的代码与数据,然后再链接剩余部分。示例如下:

/* 链接启动加载部分 */

  1. __text_start = .;
  2. .text :
  3. {
  4. kernel.a:(.text .text*)
  5. }
  6. .rodata :
  7. {
  8. kernel.a:(.rodata .rodata*)
  9. }
  10. .data :
  11. {
  12. kernel.a:(.data .data*)
  13. }

/* 链接剩余部分 */

  1. .post_text :
  2. {
  3. __post_text_start = .;
  4. *(.text .text*)
  5. __post_text_end = .;
  6. }
  7. .post_rodata :
  8. {
  9. __post_rodata_start = .;
  10. *(.rodata .rodata*)
  11. __post_rodata_end = .;
  12. }
  13. .post_data ALIGN(0x1000):
  14. {
  15. __post_data_start = .;
  16. *(.data .data*)
  17. __post_data_end = .;
  18. }
  19. .bss :
  20. {
  21. *(.bss .bss*)
  22. }

3.2、反向选择

如果启动加载部分的库比较多,且很难清理出一个完整的列表。那么可以利用链接脚本提供的EXCLUDE_FILE关键字反向选择。即把可以放到延迟加载部分的库梳理出来,然后从第一部分剔除。EXCLUDE_FILE语法如下:
EXCLUDE_FILE (archive0 archive1 archive2) *(.segment)
EXCLUDE_FILE语句一次可以指定排除多个库。示例如下:

/* 链接启动加载部分 */

  1. __text_start = .;
  2. .text :
  3. {
  4. EXCLUDE_FILE(post.a) *(.text .text*)
  5. }
  6. .rodata :
  7. {
  8. EXCLUDE_FILE(post.a) * (.rodata .rodata*)
  9. }
  10. .data :
  11. {
  12. EXCLUDE_FILE(post.a) * (.data .data*)
  13. }

/* 链接剩余部分 */

  1. .post_text :
  2. {
  3. __post_text_start = .;
  4. *(.text .text*)
  5. __post_text_end = .;
  6. }
  7. .post_rodata :
  8. {
  9. __post_rodata_start = .;
  10. *(.rodata .rodata*)
  11. __post_rodata_end = .;
  12. }
  13. .post_data ALIGN(0x1000):
  14. {
  15. __post_data_start = .;
  16. *(.data .data*)
  17. __post_data_end = .;
  18. }
  19. .bss :
  20. {
  21. *(.bss .bss*)
  22. }

链接脚本中定义了符号text_start,同时为延迟加载的段定义了三组符号:post_text_start/post_text_endpost_rodata_start/post_rodata_endpost_data_start/post_data_end。系统启动后可以根据这些符号确定加载到内存的位置,以及设置相应的读写属性。可以根据post_text_start-__text_start的偏移值计算出指定延迟加载段在flash中的偏移。

在实际项目中,通过上述反向选择方式,BootLoader只需加载原镜像的60%,BootLoader耗时从114ms降为72ms。

在代码和数据被加载前,存放延迟加载段的内存上的值是随机值,因此该内存区域的属性宜在加载前设置,以避免误访问该区域产生不可预知的问题。操作系统从Flash加载延迟部分的流程如下。它将根据是否使能延迟加载功能来决定是否从flash读取延迟加载的段。

AliOS Things内核延迟加载技术

最后,还有一个问题需要解决:BootLoader如何获得第一部分的加载长度。第一种方式是静态配置,即配置BootLoader源码的加载长度宏。这种方式实现简单,适合在内核镜像变化不大的情况下使用。第二种方式是构建体系自动探测。编译生成内核镜像后,构建体系读取链接时生成的map文件,然后获取第一部分加载长度,并设置BootLoader的编译宏。第三种方式是在内核镜像头部存放第一部分加载长度的信息,BootLoader先读取内核镜像头部,获得第一部分的加载长度后,再继续加载。

四、总结

本文阐述了AliOS Things上的内核延迟加载技术。提供了两种调整内核镜像的方式,可有效降低启动时间。

开发者技术支持

如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号

AliOS Things内核延迟加载技术

更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/

上一篇:Windows Media Player 损坏提示“出现了内部应用程序错误解决方法


下一篇:AVEVA Marine中参数化部件建模和出图