2. 苏州大学 计算机科学与技术学院, 苏州 215006;
3. 认知计算与智能信息处理福建省高校重点实验室, 武夷山 354300
2. School of Computer Science and Technology, Soochow University, Suzhou 215006, China;
3. Key Laboratory of Cognitive Computing and Intelligent Information Processing of Fujian Education Institutions, Wuyishan 354300, China
为了提升编程颗粒度、提高可移植性, 借鉴通用计算机的概念与做法, 把基本输入输出系统(Basic Input and Output System, BIOS)与用户程序分离开来, 实现彻底的工作分工, 形成了通用嵌入式计算机(General Embedded Computer, GEC)[1]. GEC架构将嵌入式软件系统分为BIOS工程程序(简称BIOS)和USER工程程序(简称USER)两部分, BIOS先于USER固化于微控制器(Microcontroller Unit, MCU)内的非易失存储器(如Flash)中, 为实时操作系统(Real-Time Operating System, RTOS)的驻留提供了空间. 实时操作系统能提供精确的实时控制和任务管理功能, 保证系统的实时性需求[2].通过将RTOS驻留在BIOS中, 降低用户的编程难度、简化程序的串口写入以及方便用户调用, 同时也可以很好地帮助用户解决因RTOS在不同开发环境的编译困难而造成的烦恼.
同时, 为了充分发挥RTOS的功能, 方便用户使用, 提高用户程序的可移植性, 通过提供对外函数接口的形式是一种比较好的做法. 对外函数接口(也称为应用程序编程接口, API)是软件库提供的一组可访问的接口, 软件库通过API向外提供服务, 开发人员通过使用API实现代码复用, 提高生产效率[3].因此, 在对外函数接口设计上, 要充分考虑其可用性[4,5]、稳定性[6]和安全性[7,8].目前, 对外函数接口的研究已取了一定的研究成果, 主要集中在API使用规约[9-11]、API推荐研究[12,13]、API文档研究[14,15]、API组合模式的应用[16]等方面, 但有关RTOS的驻留及其对外函数接口研究方面文献较少. 为此, 本文首先给出通用嵌入式计算机架构下RTOS的BIOS驻留方法, 剖析了RTOS对外函数接口设计方法, 提出了接口函数重映射机制, 最后在mbedOS下进行应用实践. 实践表明, 将RTOS驻留在BIOS中, 能有效地节省用户程序的编译时间, 同时通过对外接口函数的重映射, 能有效地提升了应用程序的可靠性性和开发效率, 易于用户调用, 也使应用程序易于复用, 为嵌入式人工智能与物联网终端程序的开发提供了技术基础.
1 RTOS的BIOS驻留方法简介在GEC架构中, 虽然嵌入式软件系统分为BIOS和USER两部分, 但最终程序代码和各种变量数据都是放在同一个MCU的Flash和RAM中, 要将RTOS驻留在BIOS中, 实现RTOS与应用程序的物理隔离, 就必须对MCU的Flash和RAM空间进行合理的划分, 这样才能确保代码不重叠, 变量使用不越界, 从而保证RTOS能得到正常运行, 而又不影响USER的执行. 因此, 要考虑RTOS驻留的硬件载体, 即MCU的Flash和RAM空间大小的因素. 对Flash空间的划分可采用分割独享方式, 一部分给BIOS程序使用, 另一部分给USER程序使用, 两者使用的空间不重叠. 对RAM空间的划分则需要考虑MCU的RAM空间大小, 当RAM空间足够大时, 可采用分割独享方式, BIOS和USER各单独使用不重叠的空间; 当RAM空间较小时, 可采用重叠共享方式, 即BIOS使用的空间和USER使用的空间部分重叠共享, 这样可以提高RAM空间的利用率. 在实现RTOS驻留的过程中, 还需要考虑如何合理划分Flash空间, 使USER程序占用的空间尽量大; 重叠共享方式分配RAM空间时, 如何避免出现数据越界与冲突; RTOS的调度会依赖于系统服务调用, 何时将这些调用权移交给RTOS等问题.
2 RTOS对外函数接口设计方法虽然RTOS已驻留在BIOS中, 但要发挥其作用, 还需将RTOS的功能函数设计成对外函数接口表, 然后通过映射形成对外函数映射表, 这样才能向USER提供服务. 也就是说, USER可以通过对外函数映射表实现对RTOS提供的接口函数的调用.
2.1 设计RTOS对外函数接口的必要性在GEC架构下, 将RTOS提供的函数进行对外接口的设计, 不仅能够发挥RTOS的功能, 而且还能方便用户使用, 提高USER程序的可复用性. 因此, RTOS向用户提供接口函数是非常必要的.
(1)提高应用程序的可复用性. 由于USER是通过对外函数映射表实现对RTOS提供的对外接口函数调用的. 因此, 当在BIOS中驻留不同的RTOS时, 只要向USER提供相同功能的接口就可以, 即使这些接口的名称发生的改变, 也不会影响USER的调用, USER程序不需要修改, 提高了USER程序的可复用性.
(2)提升应用程序的稳定性和可靠性. 由于RTOS已经驻留在BIOS中, 它所提供的对外函数功能已经在BIOS中通过编译、测试和验证, 变成了一段可靠的、稳定的机器码. 因此, 在USER程序中可以放心地调用RTOS提供的对外函数, 不用担心会出现的代码错误, 从而提升了应用程序的稳定性和可靠性.
(3)缩短应用程序的开发时间. 由于RTOS提供了原型级的对外函数调用接口, 用户可以直接使用, 无需花大量精力深入理解RTOS的工作原理和调度机制, 不需要知道具体的实现细节, 只需关注用户程序的编写, 大大地提高了开发效率.
(4)方便应用程序调用. 由于RTOS提供的对外函数已经变成了机器码驻留于BIOS内, 用户难以调用, 通过对这些函数进行重映射, 最终向USER提供函数调用原型接口, 用户就可以像调用普通函数一样方便使用这些函数.
2.2 RTOS对外函数接口机制要实现RTOS对外函数接口, 首先需要在BIOS中对函数进行重定义、声明、注册, 形成对外函数接口表; 其次要在USER中通过映射获取对外函数接口表的入口地址, 形成对外函数映射表, 并重定向函数名称, 最后在USER中实现对函数的调用, 其过程如图1所示.
(1)对外函数的二次封装
对外函数不仅可以包含RTOS的功能函数, 而且还可以包括各类构件函数, 本文主要介绍如何封装RTOS提供的对外接口函数. RTOS一般都具备线程管理、同步与通信、中断管理等基本功能, 相应的提供了线程类、事件类及操作系统启动函数等, 在这些类中提供了大量的成员函数, 都是采用C++实现的. 由于这些成员函数的执行依赖于类对象的创建及其成员变量, 而不是使用绝对地址的方式来实现对类中的某个成员函数的调用. 因此, 必须在BIOS程序中对这些函数进行重定义, 二次封装成C语言可以调用的函数形式, 这样才能实现在USER中调用它们. 对外函数的二次封装(或称重定义)主要包括函数名、函数的返回值类型、函数的参数、函数体以及为了便于理解程序而加入的功能说明和代码注释等. 其格式如下:
格式: void重定义函数名(参数表列)
{
[相关变量声明]
调用RTOS函数
}
例如, mbedOS的延时函数名为Thread::wait, 通过二次封装重定义为thread_wait.
(2)对外函数的声明
对外函数重定义好之后, 一般应在与之同名的.h头文件中进行声明, 函数的声明要给出函数名、函数的返回值类型、函数的参数以及函数的功能说明, 即使用者通过函数的声明就能了解函数的功能和使用方法, 而不需要查看函数的具体实现.
格式: void重定义函数名(参数表列);
(3)对外函数的注册
当对外函数定义和声明之后, 还要对函数进行注册才能形成对外函数接口表. 借鉴中断向量表的定义做法, 可以给所有的或部分的函数编号, 并将函数名(即函数的入口地址)集中在一起按编号有序地放在一个统一的区域中, 形成对外函数接口表. 在对外函数接口表中, 对外接口函数的入口地址用32位的二进制表示, 可以看作和定义成long类型的数据, 一般采用汇编语言编写一个SVC中断来注册. 对外函数接口表采用数组(如BIOS_API)存储, 其入口地址就是数组名或数组的首地址, 函数的编号与数组的下标元素的序号一一对应, 其中,0号表示对外函数的数量,1号函数对应BIOS_API[1], 2号函数对应BIOS_API[2], 依此类推, 换句话说, 可以通过数组元素来访问这些对外函数. 同时, 为了便于扩充或更新对外函数的个数, 还预留了一些缺省的函数名(如DefaultFUN).
格式: BIOS_API: //对外函数接口表数组
.long函数个数 //0号表示函数个数
.long重定义函数名 //从1开始对函数编号
……
例如: BIOS_API:
.long 134 //共有134个对外函数
.long OS_start //1号对外函数(启动操作系统)
……
.long thread_wait //4号对外函数(延时函数)
.long DefaultFUN //预留缺省函数
3 接口函数重映射机制当RTOS提供的对外函数经过重定义、声明、注册, 形成对外函数接口表后, USER程序还需进一步通过重映射机制形成对外函数映射表, 才能最后在USER中实现对函数的调用.
3.1 对外函数接口表的映射当RTOS的对外函数接口表形成之后, 此时RTOS提供的函数已经变成了一段机器码, 必须先将BIOS的对外函数接口表映射成USER的对外函数映射表, 获得存放对外函数接口表的数组首地址, 这样USER程序才能使用它. 为了与BIOS_API数组的元素一一对应, USER程序的对外函数映射表也采用数组(如USER_API), 用它来存放对外函数接口表的地址. 这样, 当USER使用USER_API时就相当于使用BIOS_API, 也就是说, USER通过USER_API就可以访问BIOS提供的对外函数.
在USER中, 采用SVC中断的方式来实现对外函数接口表的映射. 因此, 在BIOS中先要将SVC中断重定向为用户的SVC_IRQ, 接着在USER中调用svc1_init函数触发BIOS中的SVC_IRQ中断, 然后由SVC_IRQ中断触发实际的SVC封装函数SVC_HandlerS, 最后在这个汇编程序SVC_HandlerS中实现将BIOS提供的对外函数接口表的入口地址映射到对外函数映射表中, 汇编函数SVC_HandlerS实现流程如图2所示.
3.2 对外函数的重定向当对外函数映射表形成之后, 此时USER就可以通过USER_API数组访问函数, 如USER_API[1]访问的是1号对外函数. 但采用USER_API[i]这种形式对具体要访问的对外函数的类型、函数名、参数以及功能等不够清晰明了. 因此, 类似中断向量重定向的做法, 也可以重定向对外函数, 重新给USER_API[i]取另外一个用户熟悉的函数名, 这个函数名可以与对外函数名同名, 也可以是不同名的, 这样就可以为用户提供函数原型级接口, 易于用户记住和使用,图3描述了从RTOS的原型函数到最终实现用户实际调用函数的映射关系. 函数重定向宏定义的一般形式如下:
#define 函数重定向名 ((对外函数声明指针表达形式) (全局数组[对外函数编号]))
例如:
#define delay ((void (*)(uint_32 millisec)) (USER_API[4]))
3.3 对外函数的调用当对外函数重定向之后, 就可以利用重定向后的名字来调用函数. 例如, 当USER调用delay函数时, 实际上是指向了USER_API[4], 而USER_API[4]对应BIOS_API[4], BIOS_API[4]存放的是thread_wait函数的入口地址, 而thread_wait函数实际上就是Thread::wait函数. 因此, 可以认为USER通过调用delay函数达到调用Thread::wait函数的目的.
4 mbedOS下的应用实践2014年ARM公司推出了mbedOS, 它是一种专为物联网 (IoT) 中的“物体”设计的开源嵌入式实时操作系统[17].本文选用mbedOS进行应用实践, 测试工程在Kinetis Design Studio 3.0.0 IDE集成开发环境和金葫芦AHL-A系列Cortex-M0+内核的KL36微控制器[18](即AHL-AN100VL型号开发板)上进行. KL36片内Flash大小为64 KB, 一般用来存放中断向量、程序代码、常数等; 片内RAM为静态随机存储器SRAM, 大小为8 KB, 一般用来存储全局变量、静态变量、临时变量(堆栈空间)等.
4.1 mbedOS提供的对外接口函数使用KL36微控制器作为mbedOS驻留的硬件载体, 考虑其Flash和RAM空间大小的因素, 在mbedOS驻留于BIOS时, 对Flash空间采用分割独享方式划分. 对RAM空间可采用分割独享和重叠共享方式划分, 如图4所示, 在分割独享方式中BIOS和USER各占一半的RAM空间; 本文采用重叠共享方式, BIOS占全部RAM空间, USER占一半以上的RAM空间, 这样可以最大程度地提高RAM空间的利用率. 根据前面介绍的RTOS对外函数接口设计方法和重映射机制, 可以将mbedOS提供的启动、线程、延时、事件、消息队列、信号量和互斥量等函数进行重定义、声明、注册、映射、重定向, 最后提供给用户程序调用. mbedOS提供的对外函数原型名称、对外函数二次封装名称以及重定向函数名称之间的映射关系如表1所示, 仅列举了本测试工程中使用到的对外接口函数.
4.2 功能性测试
测试工程的功能是创建两个任务, 实现每2秒红灯闪烁一次, 蓝灯任务每1秒切换亮暗一次, 绿灯任务当收到蓝灯任务的信号(17)时, 切换绿灯亮暗. 当芯片上电之后, 首先启动BIOS, 接着转到USER的启动, 在USER的启动过程中通过映射对外函数接口表形成对外函数映射表, 并重定向对外函数. 然后启动mbedOS, 并创建和启动蓝灯任务和绿灯任务, 最后在一个无限循环中使红灯每2 s闪烁一次, 同时对蓝灯任务和绿灯任务进行调度. 蓝灯任务主要完成每秒闪烁一次, 并设置信号; 绿灯任务主要是等待信号, 当收到信号后闪烁一次. 测试工程的执行流程如图5所示.
在测试工程中主要调用的对外函数有: 操作系统启动函数OsStart、任务创建函数create、任务启动函数start、延时函数delay、信号设置函数signal_set和信号等待函数signal_wait等, 其功能性测试结果如图6所示, 从中可以看出能精准调用这些对外函数, 程序的功能得到准确的实现, 说明对外函数接口设计正确.
4.3 编译时间测试编译时间的测试是在联想笔记本电脑K49上进行, 其CPU型号为Intel Core i7-3520M, 主频2.9 GHz, 内存8 GB, 采用64位的Windows 7操作系统. 测试工程针对KL36 (采用Kinetis Design Studio 3.0.0 IDE编译环境)、S32K144 (采用S32 Design Studio for ARM v1.3编译环境)和MSP432 (采用Code Composer Studio 6.2.0编译环境)微控制器分别对mbedOS是否驻留BIOS的USER程序进行编译时间测试, 测试结果如表2所示. 从表2中可以看出, 将mbedOS驻留在BIOS中, USER程序可以节约2~3倍的编译时间, 从而提高了用户程序的开发效率.
5 结论与展望
为充分发挥实时操作系统的强大功能, 本文给出了RTOS在BIOS中的驻留方法, 在GEC架构下提出了RTOS对外接口函数的设计方法, 剖析了接口函数重映射机制, 为用户提供函数原型级的调用接口. 最后以NXP的KL36芯片为例, 在mbedOS进行应用实践, 测试表明对外函数接口的设计是正确的, 有效地解决了函数调用的困难, 提高用户程序的可靠性, 为RTOS的应用研究提供了基础. 后续还将进一步探索用户程序在不同RTOS上的可移植性问题, 本文涉及到的测试工程可到苏州大学嵌入式学习社区网站(网址:
[1] |
王宜怀, 张建, 刘辉, 等. 窄带物联网NB-IoT应用开发共性技术. 北京: 电子工业出版社, 2019.
|
[2] |
张美玉, 张倩颖, 孟子琪, 等. 实时嵌入式双操作系统架构研究综述. 电子学报, 2018, 46(11): 2787-2796. DOI:10.3969/j.issn.0372-2112.2018.11.029 |
[3] |
李正, 吴敬征, 李明树. API使用的关键问题研究. 软件学报, 2018, 29(6): 1716-1738. DOI:10.13328/j.cnki.jos.005541 |
[4] |
Tsai CC, Jain B, Abdul NA, et al. A study of modern Linux API usage and compatibility: What to support when you’re supporting. Proceedings of Eleventh European Conference on Computer Systems. London, UK. 2016. 1–16.
|
[5] |
Atlidakis V, Andrus J, Geambasu R, et al. POSIX abstractions in modern operating systems: The old, the new, and the missing. Proceedings of the Eleventh European Conference on Computer Systems. London, UK. 2016. 19.
|
[6] |
Bavota G, Linares-Vásquez M, Bernal-Cárdenas CE, et al. The impact of API change- and fault-proneness on the user ratings of Android apps. IEEE Transactions on Software Engineering, 2015, 41(4): 384-407. DOI:10.1109/TSE.2014.2367027 |
[7] |
Li L, Bissyande TF, Le Traon Y, et al. Accessing inaccessible android APIs: An empirical study. Proceedings of 2016 IEEE International Conference on Software Maintenance and Evolution (ICSME). Raleigh, NC, USA. 2016. 411–422.
|
[8] |
Nadi S, Krüger S, Mezini M, et al. Jumping through hoops: Why do Java developers struggle with cryptography APIs? Proceedings of the 38th International Conference on Software Engineering. Austin, TX, USA. 2016. 935–946.
|
[9] |
汪昕, 陈驰, 赵逸凡, 等. 基于深度学习的API误用缺陷检测. 软件学报, 2019, 30(5): 1342-1358. DOI:10.13328/j.cnki.jos.005722 |
[10] |
Liang B, Bian P, Zhang Y, et al. AntMiner: Mining more bugs by reducing noise interference. Proceedings of the 38th International Conference on Software Engineering. Austin, TX, USA. 2016. 333–344.
|
[11] |
Murali V, Chaudhuri S, Jermaine C. Bayesian specification learning for finding API usage errors. Proceedings of the 2017 11th Joint Meeting on Foundations of Software Engineering. Paderborn, Germany. 2017. 151−162.
|
[12] |
Thung F. API recommendation system for software development. Proceedings of the 31st IEEE/ACM International Conference on Automated Software Engineering (ASE). Singapore, Singapore. 2016. 896–899.
|
[13] |
吕晨, 姜伟, 虎嵩林. 一种基于新型图模型的API推荐系统. 计算机学报, 2015, 38(11): 2172-2187. |
[14] |
Treude C, Robillard MP. Augmenting API documentation with insights from stack overflow. Proceedings of the 2016 IEEE/ACM 38th International Conference on Software Engineering. Austin, TX, USA. 2016. 392–403.
|
[15] |
Zhou J, Walker RJ. API deprecation: A retrospective analysis and detection method for code examples on the Web. Proceedings of the 2016 24th ACM SIGSOFT International Symposium on Foundations of Software Engineering. Seattle, WA, USA. 2016. 266–277.
|
[16] |
夏艳敏, 唐兵, 唐明董, 等. 利用关联规则挖掘的Web API组合模式发现. 小型微型计算机系统, 2019, 40(10): 2195-2201. DOI:10.3969/j.issn.1000-1220.2019.10.031 |
[17] |
Bock C, Marquardt M, Martens A, et al. Smart sensors and actors with BACnetTM and mbed OS on cortex-M microcontrollers. Proceedings of the 2019 IEEE 5th World Forum on Internet of Things (WF-IoT). Limerick, Ireland. 2019. 937–942.
|
[18] |
NXP. KL36 sub-family reference manual. https://www.nxp.com/docs/en/reference-manual/KL36P121M48SF4RM.pdf. (2013-07-03)[2019-09-25].
|