2.5 镜像分割

镜像分割机制将一个目标分成两个独立的镜像:一个能够执行镜像升级;另一个镜像包含应用代码。通过将升级功能隔离到一个单独的镜像中,应用程序在支持无线升级的同时而无需将flash空间用于网络协议栈和管理代码。

2.5.1 概念

Mynewt支持3个镜像设置:

设置 描述
Single 一个大的镜像,不只是升级
Unified 两个单独的镜像
Split 内核在镜像槽0,应用在镜像槽1

每一个设置都有其平衡,独立设置将保证能够最大限度使用flash空间,但是却不允许升级。统一设置允许完整的备份,以便上传镜像有异常,在每个镜像中有大量的冗余,限制了应用程序可用的flash数量。通过分割设置则介于这两个选项之间。

在探索分割设置之前,对Mynewt的引导顺序有一个基础的了解将会有比较大的帮助。

  • 启动顺序——Single

独立设置下,没有bootloader。从而,镜像从地址0开始。硬件直接从镜像代码开始。由于没有bootloader,不能交换镜像,故而此种模式下不能支持升级。

  • 启动顺序——Unified

在统一设置下,bootloader放置到地址0。启动时,优先启动bootloader,bootloader将从两个镜像中决定启动哪个镜像。

  • 启动顺序——Split

分割设置与以上两种设置不同,一个目标并不包含一个完整的镜像。目标被分为两个分离的镜像:一个加载程序,一个应用程序。

  1. Loader:Mynewt OS;网络协议栈用于连接,如BLE协议栈;任何需要用于升级镜像的组件。
  2. Application:Mynewt中不需要用于镜像升级的部分;应用特定代码。

loader镜像出于三个用途:

  1. 二阶bootloader,启动时跳转到应用程序镜像;
  2. 镜像升级服务器:用户可以升级一个新的加载器+应用,即使当前的应用程序镜像并没有运行;
  3. 功能容器:应用程序镜像可以直接访问loader镜像中的所有代码,保证整个程序功能完整。

从bootloader的角度看,loader镜像与普通镜像是相同的。Loader镜像的不同点在于,其启动顺序有改变:启动Mynewt OS之后,如果镜像槽1有应用将会跳转到应用程序镜像。

2.5.2 示例

本节将以dwm1001来进行说明,参考flash map(hw/bsp/dwm1001/bsp.yml),合计512KB:

名称 偏移 大小(kB)
Bootloader 0x00000000 16
Reboot log 0x00004000 16
Image slot 0 0x00008000 232
Image slot 1 0x00042000 232
Image SCRATCH 0x0007C000 4
Flash文件系统NFFS 0x0007D000 12

当目标构建后,可以通过newt size target-name命令来查看代码大小。

  1. $newt size dwm1001_boot
  2. Size of Application Image: app
  3. FLASH RAM
  4. 25 216 *fill*
  5. 42 0 apps_boot.a
  6. 2385 4004 boot_bootutil.a
  7. 1220 0 crypto_mbedtls.a
  8. 558 508 hw_bsp_dwm1001.a
  9. 44 0 hw_cmsis-core.a
  10. 272 0 hw_drivers_uart_uart_hal.a
  11. 342 0 hw_hal.a
  12. 3210 128 hw_mcu_nordic_nrf52xxx.a
  13. 498 120 kernel_os.a
  14. 434 32 libc_baselibc.a
  15. 472 128 sys_flash_map.a
  16. 316 12 sys_mfg.a
  17. 6 4 sys_sysinit.a
  18. 72 0 dwm1001_boot-sysinit-app.a
  19. objsize
  20. text data bss dec hex filename
  21. 9896 60 4660 14616 3918 /home/me/work/mynewt-dw1000-apps/bin/targets/dwm1001_boot/app/apps/boot/boot.elf

由于是一个简单应用,boot镜像大小约9KB,而一个镜像槽大小为232KB,还是有比较大的可用空间。

当然,这只是单纯的应用程序,对于镜像分割设置,需要在target中设置loader变量,应用使用apps/splitty,该应用为一个分割示例应用,可以与slinky结合构成一个应用。

  1. newt target create split-dwm1001
  2. newt target set split-dwm1001 \
  3. loader=@apache-mynewt-core/apps/bleprph \
  4. app=@apache-mynewt-core/apps/splitty \
  5. bsp=@mynewt-dw1000-core/hw/bsp/dwm1001 \
  6. build_profile=optimized \
  7. syscfg=BLE_LL_CFG_FEAT_LE_ENCRYPTION=0:BLE_SM_LEGACY=0

构建此目标。(若直接使用官方的包,bsp.part2linkerscript: “hw/bsp/dwm1001/split-nrf52-thingy.ld” 需要更改为 split-nrf52.ld,否则将会出错,此问题已经提交若已经修正,则无需修改)

构建好之后,查看镜像的大小:

  1. $newt size split-dwm1001
  2. Size of Application Image: app
  3. FLASH RAM
  4. 38 14778 *fill*
  5. 580 1456 apps_splitty.a
  6. 20 0 boot_split_app.a
  7. 172 432 hw_bsp_dwm1001.a
  8. 110 32 mgmt_newtmgr_transport_nmgr_shell.a
  9. 5168 786 sys_shell.a
  10. 98 0 split-dwm1001-sysinit-app.a
  11. objsize
  12. text data bss dec hex filename
  13. 6152 4 17080 23236 5ac4 /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/app/apps/splitty/splitty.elf
  14. Size of Loader Image: loader
  15. FLASH RAM
  16. 282 282 *fill*
  17. 72 0 split-dwm1001-sysinit-app.a
  18. 90 0 split-dwm1001-sysinit-loader.a
  19. ... ...
  20. objsize
  21. text data bss dec hex filename
  22. 126780 924 13884 141588 22914 /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/loader/apps/bleprph/bleprph.elf

可以看到生成的目标分为了两个部分,一个为应用镜像app,另一个为Loader镜像loader,整个镜像loader部分为127KB左右,而应用程序镜像仅6KB左右。

  1. $newt create-image split-dwm1001 1.0.0
  2. $newt load split-dwm1001 0
  3. Loading app image into slot 1
  4. Loading loader image into slot 0

创建镜像,并将分割后的镜像部署到DWM1001上,可以看到加载过程:将app镜像加载到slot 2,loader镜像加载到slot 1.

2.5.3 镜像升级

Unified模式镜像升级

  1. 上传新镜像到slot 1,(newtmgr image upload filename
  2. 告诉设备在下次启动时测试镜像(newtmgr image test image-hash
  3. 重启设备(newtmgr reset
  4. 镜像可用,将镜像确认替换(newtmgr image confirm

分割模式镜像升级

使用分割模式后,镜像的升级流程将会有些复杂。因为需要更新两个镜像(app和loader),而不能仅仅更新一个,流程如下:

  1. 禁止split功能,以便slot 1中的应用镜像失效(newtmgr image test current-loader-hash

  2. 重启设备(newtmgr reset

    说明:由于loader中包含了newtmgr相关功能,依然正常启动

  3. 确认以上修改(newtmgr image confirm

  4. 上传新的loader到slot 1(newtmgr image upload new-loader

  5. 告诉设备使用新的loader启动(newtmgr image test new-loader-hash

  6. 重启设备(newtmgr reset

    说明:此时重启后,并没有应用程序,若新loader有问题,也是可以以之前的loader启动,依然可以更新、上传代码,不会”成砖“

  7. 确认以上loader(newtmgr image confirm

    说明:确认之后,新loader在slot 0中,旧的loader在slot 1

  8. 上传新的应用到slot 1(newtmgr image upload new-app

  9. 下次启动时测试新的应用(newtmgr image test new-application-hash

  10. 重启设备(newtmgr reset

  11. 确认应用程序更改(newtmgr image confirm

当流程完成后,可以使用image list进行确认镜像状态。

理论上,若在项目构建中loader部分未更新,是否可只用更新应用程序部分代码?即执行8/9/10/11,待验证。

2.5.4 补充

Loader项目

官方提供了一下两个应用程序作为loader,可以参考这个两个应用编写自己的loader应用。

  • @apache-mynewt-core/apps/slinky
  • @apache-mynewt-core/apps/bleprph

分割应用项目

以下两个应用程序被用作分割应用,若需要构建自己的分割应用程序,可以此为参考。需要注意,在slinky中即可以作为loader镜像,也可以作为应用镜像。

  • @apache-mynewt-core/apps/slinky
  • @apache-mynewt-core/apps/splitty

镜像分割原理

首先,newt将应用与loader分开构建,以确保它们是正确的,从而生成elf文件(两个文件),通过该文件newt可以知道每个部分使用的符号。

然后,newt以两种方式收集应用和loader所使用的symbols。它从.elf文件中收集一组符号,同时也从应用的.a文件中收集所有可能的symbols。

然后,收集Newt构建时两个应用程序公用的一整套包,确保在这些包中使用的符号都是匹配的。(注意:由于一些特性和#ifdefs,两个包有可能具有不相同的symbols,这种情况下,newt将会产生错误,建立分割镜像失败,不会建立)

通过这些包中共享的符号列表,newt创建了两个应用程序。接下来newt重新链接loader,以确保包含了loader应用中用到的所有符号(强制链接器从.elf文件中包含)。

newt建立了一个特殊loader.elf副本,仅仅包含了使用的符号。

最终,newt将这些应用程序连接起来,在镜像连接过程中,使用特殊loader.elf中的符号替换通用.a文件中的符号。

  1. $newt create-image split-dwm1001 1.0.0
  2. Compiling bin/targets/split-dwm1001/generated/src/split-dwm1001-sysflash.c
  3. Compiling bin/targets/split-dwm1001/generated/src/split-dwm1001-sysinit-app.c
  4. Compiling bin/targets/split-dwm1001/generated/src/split-dwm1001-sysinit-loader.c
  5. Archiving split-dwm1001-sysinit-app.a #应用静态链接库文件
  6. Linking /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/app/apps/splitty/splitty_tmp.elf #临时.elf副本
  7. Compiling bin/targets/split-dwm1001/generated/src/split-dwm1001-sysinit-app.c
  8. Compiling bin/targets/split-dwm1001/generated/src/split-dwm1001-sysinit-loader.c
  9. Compiling bin/targets/split-dwm1001/generated/src/split-dwm1001-sysflash.c
  10. Archiving split-dwm1001-sysinit-loader.a #loader静态链接库文件
  11. Linking /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/loader/apps/bleprph/bleprph_tmp.elf #临时.elf副本
  12. Linking /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/loader/apps/bleprph/bleprph.elf
  13. #生成最终的img文件
  14. Generating ROM elf
  15. Linking /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/app/apps/splitty/splitty.elf
  16. App image succesfully generated: /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/loader/apps/bleprph/bleprph.img
  17. App image succesfully generated: /home/ronios/work/mynewt-dw1000-apps/bin/targets/split-dwm1001/app/apps/splitty/splitty.img