面向NB-IOT智能设备动态链接库的远程技术研究及应用

2021-06-21 01:53贾小林
计算机应用与软件 2021年6期
关键词:嵌入式动态程序

张 正 贾小林

(西南科技大学计算机科学与技术学院 四川 绵阳 621000)

0 引 言

物联网的进步与当今通信技术的不断发展有不可分割的关系,而现在出现的窄带物联网(NB-IoT)以及5G技术都是为了使整个系统中的通信成本与用户的需求成比例[1]。NB-IoT是为了用更小资源实现更高的设备连接数量,而5G的优势则是获取更低的延时以及更高的传输速率,怎么样在更少的资源上实现更多的功能是一个可以不断进行优化的问题。在NB-IoT中因为数据传输的带宽比较低,长时间的数据传输必然会增加能量的消耗,故应当尽量减少数据的传输量。但是上线设备通常是广泛分布且基数较大[2],如果NB-IoT设备出现系统漏洞而需要进行软件升级将导致巨大的不必要的通信流量与能量的消耗。因此,减少传输数据传输量和系统能耗就成了一个值得研究的问题。

目前NB-IoT中的终端设备多为嵌入式设备,其处理器通常为单地址空间的处理器,系统的功能为定制开发的程序。由此引发了新的问题,主要包括系统拓展性差、功能实现受到资源限制、软件需要升级更新等。针对这些问题,本文提出在单地址空间的嵌入式操作系统上实现动态链接库技术。利用动态链接库技术进行程序开发和升级维护,可以为NB-IoT的设备带来更多的优势:(1) 程序模块化开发,维护方便,程序复用性提高;(2) 程序远程升级只需要更新指定软件模块,能够提高更新速度与成功率;(3) 系统的可拓展性大大提高,模块能够进行动态卸载与动态加载以实现程序功能的切换。

在一些高端的嵌入式系统上,其硬件带有存储保护单元(MMU),能够运行Linux等已经带有成熟动态链接库技术的操作系统,远程升级只需要更新指定的程序包即可,但是其实时性仍然不高,且成本高、功耗大、性能经常过剩[3]。而在一些低端的嵌入式系统上虽然有动态加载的解决方案,但是其只支持单应用加载,模块化的加载方式仍然不被支持[4]。本文提出的单地址空间的嵌入式操作系统上动态链接库的实现能够在极少的资源上实现程序的模块化加载,程序更新只需要更新指定模块,应用到NB-IoT设备远程升级中能够减少传输的流量以节省设备消耗的能量,同时提高系统的可扩展性。

1 动态库基本原理

传统的嵌入式程序开发,程序的每一次修改都需要经过编译、调试和烧写的步骤,而且以上步骤可能还需要重复多次才能完全达到要求,这整个流程繁琐且周期长,无法快速地迭代。并且程序无法像Linux等操作系统以动态库的方式进行链接,程序的任意一部分改动都需要程序进行重新编译、调试、烧写,这就使得应用的程序的复用性和可拓展性大大降低[5]。在带有MMU的嵌入式芯片上,能够完成虚拟内存到物理内存的转换,还能够提供内存读写保护功能,这就使得经过编译的二进制文件可以通过虚拟内存映射的方式实现程序的动态加载,而在软件上不需要进行其他特殊的操作[6]。

通常程序在编译完成后,都需要完成链接的步骤,然后生成二进制文件。程序的链接方式分为三种:静态链接,装入时动态链接,运行时动态链接。传统的嵌入式程序开发只支持静态链接方式,即在程序运行前就将各个目标模块等链接成一个完整的程序,但是其生成的程序体积大,修改程序麻烦[7]。其优点是占用资源少,程序无重定向代码,运行效率高。

本文中的嵌入式程序的动态加载技术采用了装入时动态链接,即程序首先放置在外部的文件系统中,需要运行时再加载到内存中来运行,每一个动态加载的程序栈空间独立,其运行时的静态空间也独立。而本文中的动态链接库既可以使用装入时动态链接也可以采用运行时动态链接,即程序在装入时,判断自己需要用到的动态库文件进行装入,程序在运行时也可以指定外部文件系统的动态库文件进行装入,并运行其中的代码块。

2 动态链接库的实现

在单地址空间的芯片上,所有的代码段都共用一个存储空间,所有的函数与变量的地址在运行时都必须确定下来,必须被加载到指定位置,否则运行将会产生致命错误[8]。本文中的动态加载方式与Linux上传统的实现原理不尽相同,传统的Linux加载方式采用Unix的标准ELF格式,该加载文件复杂且含有大量的多余信息,对于嵌入式操作系统的使用资源占用较大。本文为其专门设计了一个软件包,去掉不需要的信息,只保留运行时必要的信息,大大地缩减了程序运行时占用的资源。嵌入式动态库的加载还需要解决如下几个问题:

(1) 编译器必须要能够生成可动态加载的可执行文件。

(2) 程序包之间能够进行相互调用和递归调用。

(3) 不同应用程序之间能够有可靠的通信方式。

(4) 动态加载平台要能够实现中断管理以及加载平台与动态加载的程序间的相互调用。

(5) 系统依赖文件系统进行动态加载,系统能够将程序包解压到文件系统中。

程序包既可以作为程序运行也可以作为动态库使用,程序包中包含四部分:代码段,可读写段,符号文件,资源文件,其中:代码段、可读写段、符号文件是必须的,资源文件不是必须的。代码段是直接加载到ROM或者RAM中执行的部分,可读写段在运行时需要将内容拷贝到动态申请的RAM中去,符号文件在动态加载时进行重定向时使用。该程序包去掉了调试信息等,使得程序的体积能够最小化,程序可以直接加载到ROM中,程序包结构如图1所示。

图1 程序包结构

2.1 编译器工具配置

本文中动态加载平台采用armcc编译器作为编译平台,动态加载平台的测试硬件平台为Cortex-M3,程序要实现动态加载需要保证:(1) 函数间的调用必须采用相对地址调用。(2) 程序中全局变量地址以及静态变量的起始地址需要使用专用的寄存器进行保存。(3) 程序需要显式给出该程序中符号的名字以及在内存中的相对偏移地址[9]。下面通过对编译器的配置能够实现编译出与位置无关的代码,通过对链接器的配置能够得出程序的符号表,编译命令如表1所示。

表1 编译器命令

利用表1中的命令对armcc编译器配置可将程序文件编译为可重定向的代码,其内部所有跳转都将采用相对跳转方式,其数据段的访问将采用R9寄存器的地址作为基地址,其目标访问地址为:dest=R9+offset。参数中采用global_reg=5命令限制编译器使用R8寄存器,R8寄存器将作为嵌入式实时操作系统(RTOS)保存目标程序数据的专用寄存器[10]。

链接器命令如表2所示。链接器将导出可执行的ELF文件与可执行文件的符号表,符号表中包含了可执行文件所有的符号信息(函数名,函数地址偏移,变量名,变量地址偏移等),符号表经过处理后将作为动态库之间的重定向的必备信息。

表2 链接器命令

续表2

Fromelf命令如表3所示。Fromelf工具将链接器生成的ELF文件转换为可执行的.bin文件以及数据段文件,这两个文件最终将要加载到内存中。

表3 Fromelf命令

2.2 动态库重定向的具体流程

动态库重定向具体流程如图2所示,动态库中函数的重定向主要分为5步。

图2 动态重定向的具体流程

(1) 解压程序包并读取代码段与数据段到内存:将程序包中的符号文件、代码段与数据段解压到外部存储器,然后将代码段载入到Flash或者RAM中,将数据段载入到RAM中。

(2) 读取并解析符号表的数据条目:从外部存储器中读取一条符号表数据并解析获取符号的偏移、类型、符号名,以及目标动态库名。

(3) 将与指定KEY值匹配的函数指针变量标记为需要重定向:KEY为需要被重定向函数指针的标识,该值应当尽量保证不被用户用作其他变量的初始化值。

(4) 生成或查找标记的函数指针变量的跳转函数:跳转函数链接重定向函数指针与目标函数。

(5) 重定向需要重定向的函数到跳转函数:将跳转函数的入口地址赋值给重定向的函数指针。

2.3 符号表

符号表为动态库实现的一个核心部分,它包含了各个程序自有的符号表信息,默认的符号表由编译器导出,默认只包含偏移、类型、符号名三项信息,不能够满足重定向的使用要求,经过修改的内容如表4所示,包含四部分:偏移,类型,符号名,目标库。当类型为T表示为一个Thumb类型调用的函数,其偏移地址为在代码段区域内的偏移;当类型为D时标识为一个变量,其偏移地址为在数据段区域内的偏移。当类型为F时表示该程序需要用到的目标动态库。需要进行重定向的函数目标库一栏将会有效。为提升运行时的效率,其中的符号表信息将在编译阶段完成修改。

表4 符号表

2.4 动态库重定向的实现

为了实现动态库之间的相互调用,动态加载平台还支持函数的重定向。与传统重定向不同,本文中的动态加载平台的重定向不直接作用于函数的地址,而是作用于函数指针,其基本思路如图3所示。

图3 重定向示意图

程序调用函数指针,函数指针在加载阶段将会指向全局跳转表,而全局跳转表将指向目标程序的目标函数。函数指针实际也是一个变量,将在符号表中以类型D的形式表示,函数指针调用的一个典型示例如下,默认为需要重定向的函数指针赋一个KEY值0xfedcba98,该KEY在编译器进行符号表信息预处理时用来识别该函数指针是否需要进行重定向。

typedef int (*_add)(int a,int b);

_add add=0xfedcba98;

void main(void){

add(1,2);

}

跳转函数由汇编函数实现,且在加载时动态生成,其主要作用是保护当前程序的R9基地址值,并切换到目标程序的R9基地址值,其汇编指令如表5所示,每一个重定向函数的跳转函数只占用20个字节,5条指令。

表5 跳转函数汇编的实现

2.5 中断管理的实现

实现中断管理是嵌入式动态加载平台模块化实现的一个重要功能,将系统的中断功能交给动态加载平台的一个专门的模块来实现,以不断拓展系统的可拓展性。动态加载平台的中断管理流程如图4所示,其中中断中继表包含一个函数指针向量,动态加载平台获取该中继表并将其与真实的中断映射,而程序与动态库则注册自有程序的中断函数到中断中继表,于是中断执行的步骤就变成:系统中断→中断中继表→注册的中断函数。

图4 中断管理流程示意图

2.6 多任务切换的实现

在单地址空间上实现的多任务为伪多任务,其并不支持虚拟内存映射,且通常单地址空间的处理器多为单核处理器,其多任务的切换也只是时间片的划分。动态加载平台也能够支持简单的多任务,也具有多任务程序的特征:(1) 栈空间独立;(2) 数据段独立;(3) 任务区切换时能够保存当前运行状态。其中数据段独立,保证了不同程序加载到程序运行时不会受到其他程序的影响[11]。在动态加载平台上的多任务切换存在两个问题:

(1) 通过R9寄存器进行内存访问基地址的多任务切换与传统RTOS存在差异,其主要原因是每一个单独运行的应用程序都是靠R9寄存器来进行基地址重定向,任务切换时会导致程序的变量域还没有切换,而代码域已经切换到了嵌入式操作系统的代码域,这将会导致访问变量错误。

(2) 在任务调度中的代码主要由汇编语言完成,而在汇编中不能够直接进行变量或函数的调用,主要原因是利用汇编直接进行的函数跳转其目的地址必须确定,而重定向后的程序地址并不确定,真实地址是在程序运行后才能够知晓。

上述问题的解决可通过R8寄存器保护实现,即将RTOS模块的变量域起始地址与需要保护的变量与函数存入一个数组,将数组的首地址在执行初始化时放入R8寄存器。在访问嵌入式操作系统代码域时,通过汇编代码将R8寄存器的值赋值给R9,通过代码控制即可切换到嵌入式操作系统模块的变量域,而需要访问保护变量时能够通过R8寄存器找到需要的变量值。具体部分实现的汇编程序如下:

ldr r1,[r8];

//保存栈顶值

ldr r1,[r1]

str R0,[r1]

push r9,lr;

//保护R9的值

ldr.w r0,[r8,#4]

ldr.w r1,[r8,#8]

mov r9,r1;

//赋值成为本模块的R9值

blx r0;

//调用任务切换函数

pop r9,lr

3 系统测试与性能分析

系统测试主要分为性能分析与功能测试,功能测试主要完成对系统的功能完整性进行测试,性能分析主要利用软件算法对软件在未进行动态加载与加载后执行的性能做一个比较。

3.1 动态链接库的性能分析

利用快速排序算法以及斐波那契数列(递归法)生成来分别测试程序在内存访问与函数调用与原生软件之间的性能差异。分别测试了在选择排序算法与斐波那契数列生成在加载与未加载执行时在不同存储器内的执行效率。通过表6可知,在选择排序算法测试中,加载到内部RAM中执行效率相比于未加载前在Flash中的执行效率只降低了3.46%,而加载到外部RAM中执行效率相比于未加载前在Flash中执行效率降低了1 131.7%。在斐波那契数列生成测试中,加载到内部RAM中执行效率相比于未加载前在Flash中的执行效率提升了5.58%,而加载到外部RAM中执行效率相比于未加载前在Flash中执行效率降低了625.87%。从测试数据可以看出,动态库加载到内部RAM中测试时相比未加载前运行效率差异较小,但是在加载到外部RAM中测试时运行效率却损失较大,这是因为外部RAM的访问受到了外部总线带宽的限制。

表6 性能测试表

3.2 远程升级功能测试

测试的硬件平台为Cortex-M3的STM32F103ZET6处理器,测试程序的功能框架如图5所示,该测试程序中的功能模块包含了文件系统、TPC/IP协议栈、RTOS、中断管理模块、动态加载平台、远程应用升级模块、板级支持包、驱动程序,传统的升级方式需要将所有的代码文件进行全部烧写,测试程序中利用动态库进行模块化的加载,在进行程序升级时,只需要对需要的程序进行升级即可。

图5 测试程序功能框架

测试场景:温度采集驱动升级,将程序中温度报警模块的阈值提高到28 ℃。

将本文系统的更新方式与传统的更新方式在更新体积以及更新时间上做对比,测试结果数据如表7所示,传统的更新方式是指每次都对所有的应用程序进行更新。

表7 更新测试对比表

可以看出本文系统在更新同样的功能代码时,需要更新的代码大小相比传统方案缩小了218 KB,更新时间缩短了59.2 s,更小的更新代码体积意味着更小的更新时间与更高的更新成功率。这种更新方式对于复杂的嵌入式系统极为有用,在复杂的嵌入式系统上系统功能复杂,功能划分清晰,采用模块化的程序升级方式不仅仅能够减少更新的代码体积,还能够约束开发者更好地规划系统功能模块的实现。

4 结 语

本文针对传统的NB-IoT设备,利用自制Bootloader进行程序升级中存在的弊端进行了改进。利用动态链接库实现模块化的程序加载方式,将传统的一个程序拆分为动态链接库的方式加载运行,可以大幅度减少程序更新时的数据传输量和系统能耗,同时还能提高系统的可拓展性与程序的复用性。本文中的单地址空间处理器上的动态链接库技术不仅能够应用到NB-IoT程序升级,还能够应用到其他通信方式的远程程序升级中。该技术能够提高单地址空间处理器的可拓展性,程序的模块化加载还能够提高处理器资源的利用效率。相比于传统的程序开发模式,该技术能够拓展单地址空间处理器的应用范围,在嵌入式系统和智能终端领域具备良好的应用和推广价值。

猜你喜欢
嵌入式动态程序
国内动态
基于IMX6ULL的嵌入式根文件系统构建
国内动态
国内动态
给Windows添加程序快速切换栏
动态
试论我国未决羁押程序的立法完善
“程序猿”的生活什么样
英国与欧盟正式启动“离婚”程序程序
高校图书馆开展嵌入式信息素质教育的思考