APP下载

基于μCOS系统的嵌入式动态加载技术实现与改进

2017-12-02裴俊宇刘子龙

软件导刊 2017年11期

裴俊宇 刘子龙

摘要:针对嵌入式操作系统不支持外部程序动态加载技术,限制其灵活性和可扩展性的弊端,基于STM32F103μc/OSIII平台,运用ELF文件重定位原理,对原系统功能进行拓展,对编译合适的ELF文件、ELF加载器实现等作深入分析。测试表明,系统支持对软件模块的动态加载功能,并实现外部任务之间的通讯。最后针对内存开销大的问题作出改进,使该项技术具备更高的实用价值。

关键词关键词:动态加载;μC/OS;单地址空间;ELF文件

DOIDOI:10.11907/rjdk.171728

中图分类号:TP319

文献标识码:A文章编号文章编号:16727800(2017)011014904

0引言

在电子技术迅速发展下,嵌入式系统不仅在硬件性能上快速提升,能胜任更多任务,而且在应用上也越发广泛,软件复杂度越来越高,使得对嵌入式软件开发提出了新的要求[1]。相比PC操作系统上早已十分成熟的动态加载技术,常见的μcos嵌入式操作系统缺乏对动态加载程序的支持,一是ROM技术限制,二是内存使用紧张,无法运行太多程序[2]。Flash技术的发展及内存的增加,使得基于嵌入式操作系统的动态加载具备可行性。一些高端嵌入式设备已经具备GB级的内存,硬件支持虚拟地址空间,能够运行Linux量级的系统。但实时性不满足要求,性能过剩且成本偏高。作为嵌入式市场主力的中低端设备,普遍仍是单地址空间,内存受限,此时提高系统的可扩展性迫在眉睫。

本文基于STM32平台,讨论在典型单地址空间、无内核态的μcos嵌入式操作系统上动态加载技术实现,并针对嵌入式内存使用紧张问题作出改进。

1基本原理与机制

传统嵌入式开发通常将程序编译链接成一个二进制文件,然后烧录到芯片上运行调试。从简单的裸机程序到操作系统与应用绑定编译的复杂任务,都要通过反复重新编译链接下载调试流程,周期长而繁琐,不能快速迭代,对于市场快速变化需求而言显得效率低下。此外,每次運行程序都是将特定的任务载入内存,不能像PC端一样安装、卸除程序,每一次变动都要重新烧录整个程序,无法灵活地调整更新[3]。

通常而言,代码经过编译和链接生成二进制可执行文件,其中链接分为静态链接、加载时链接和运行时链接。静态链接即将程序和它需要的库链接成一个单一文件,独立运行、速度快,但是生成文件较大,如果要改动就需重新链接。加载时链接指程序在连接时不会把外部引用的库代码链接到执行程序中,而是在它被加载器加载时将外部库加载入内存进行链接。优点是程序本身小、灵活。运行时链接是加载时链接的更进一步,只有在真正调用外部库代码时才将外部库加载入内存进行链接[4]。

嵌入式动态加载实现主要采用加载时链接,即从外部存储设备中加载程序到内存中,在操作系统中动态申请一个任务栈,让外部加载的程序在该任务栈上运行,并服从操作系统资源管理,同时程序也能申请一些资源,如内存块、定时器等。

2系统实现

单地址空间即整个系统使用一个连续的单地址空间。无用户态与内核态之分,用户程序的代码可以直接访问到操作系统代码。

动态模块设计参考了Linux中动态库文件的实现原理,采用Unix标准的ELF格式文件。该文件格式通用性广、可拓展性强,对arm体系编译器有很好的支持。通过设置恰当的编译和链接参数,生成可重定位位置无关代码[56]。

实现过程中主要解决如下问题:①解决编译问题,好的外部程序必须是位置无关代码,可以加载到任意内存地址运行,而烧录程序要额外生成一个符号表,以便动态链接;②操作系统通过文件系统读取文件,能够加载识别配置文件和程序;③加载模块加载外部程序需进行链接,使得操作系统能够找到外部程序入口,外部程序能够调用系统函数,操作软硬件资源;④解决外部任务之间的通讯。

2.1编译器与链接器参数

采用KeilIDE,默认编译器为armcc,为了方便链接,使用编译器宏命令导出符号表,以便下一步链接。

此处参考armcc手册,使用宏命令建立一个结构数组,存储符号和地址信息。

struct my_module_symtab

{

void *addr;

const char *name;

};,

#define SYM_EXPORT(symbol)

const char __sym_##symbol##_name[] SECTION(".rodata.name") = #symbol;

const struct my_module_symtab __sym_##symbol SECTION("SymTab")=

{

(void *)&symbol,

_sym_##symbol##_name

}

存储符号名和符号地址。其中,SECTION()命令是armcc专属宏指令,编译时将该变量放在指定Seciton。__sym_##symbol##_name创建一个字符串常量存储符号名,struct my_module_symtab用来存储该符号名和符号地址。

由于该结构变量都存储在SECTION("SymTa b")中,因此编译预处理后将顺序分布在SymTabsection中。通过armcc的宏&MySymTabMYMMYMBase访问section的首元素,&MySymTabMYMMYMLimit访问尾元素,像数组一样操作符号表。SYM_EXPORT()用来输出变量和符号,提供重定位时的参考信息。

外部程序选用armnoneeabigcc作为交叉工具链。编译器参数如表1所示。endprint

该命令将编译出所需的ELF文件格式,其中包含重定位符号表,对应已经生成好的符号表,进行重定位[7]。

2.2外部程序加载到内存

在片外Flash上建立fatfs文件系统。Fatfs系统通过usb接口传输ELF文件。其中配置文件选项如表3所示。

系统启动后加载文件系统,等基本驱动初始化完毕后,加载配置文件。配置文件名固定,程序名不固定。根据配置文件的加载程序数量,依次对任务进行加载,创建新任务。需要配置好任务优先级、任务周期及任务栈大小,还有可选的传入参数,最后进入OSStart()启动任务循环。Usb库和文件系统都是移植现成的,配置文件为方便使用采取JSON格式,JSON解析使用cJSON库。

2.3ELF加载器实现

操作系统加载外部ELF文件到内存后需解析ELF文件,将其中的有用信息提取出来,主要是代码段、数据段及重定位表。重定位表的作用是指出需要重定位项在ELF文件的偏移位置,根据重定位段和符号表进行重定位操作,最后将程序的入口交给操作系统,再由操作系统创建新任务启动运行,此时成功加载了一个外部程序[78]。

2.3.1ELF文件头解析

解析ELF header,加载到内存,编译出来的文件是Shared Object File,也即Type字段为DYN,ELF Header检查正常进入下一步[9]。根据ELF Header后紧跟SectionHeader和SectionHeaderStringTable,其中给出了ELF文件中所包含内容的具体信息。使用readELF工具打印出ELFheader,其各Section如图1所示。

图1ELFSectionList

其中,.rel.dyn中包含了重定位信息,.symtab记录了符号表。通过Section Header中offset定位各表,即可在下一步重定位操作[10]。

2.3.2重定位

重定位之前,需要先将代码和数据加载到内存。.text中包含代码、.rodata只读数据段、.data数据段,.bss是未初始化全局变量数据段,需要预留出足够空间。.symtab和.strtab及.symtab,.rel.dyn用于重定位,工作完成后可丢弃。

根据重定位表中信息,确定重定位类型,从而确定重定位地址计算方式。重定位项结构体[11]如下:

struct ELF32_rel {

ELF32_Addr r_offset;

ELF32_Word r_info;//SYMBOL<<8+TYPE&0xff.

};

//r_offset是需要进行重定位的地址;

//r_info包含了重定位项的基本信息;

//SYMBOL是重定位以后需要指向的符号;

//TYPE是重定位的类型

重定位过程就是重定位表的遍历,依次将每个符号地址进行重定位[12]。

重定位类型很多,但大多数很少出现。通过对编译器参数的设置,使得产生的重定位类型简单且容易定位。大多为R_ARM_GLOB_DAT、R_RAM_ABS32,及R_ARM_REL32型[1314]。

假设addr为加载到内存后的符号指向地址,sym_val为上述结构体中的r_offset,则3种类型的定位方式如表4所示。

2.3.3任务创建

重定位完成后,调用操作系统创建OS_TCB和CPU_STK并将资源分配给新任务,同时外部程序将入口交给操作系统以便创建新任务。操作系统入口在链接时已经指定好main函数,ELF header中包含EntryPointAddress即为程序入口。其中,OS_PRIO、CPU_STK_SIZE、OS_TICK、Argument由配置文件指定[15]。

OSTaskCreate((OS_TCB*)&MyAppTCB,/* Create the start task*/

(CPU_CHAR*)"Exteral Program1",

(OS_TASK_PTR ) EntryPointAddress,

(void*) Argument,

(OS_PRIO) MyAppPriority(8),

(CPU_STK*)&MyAppStk[0],

(CPU_STK_SIZE)MY_APP_STK_SIZE(512) / 10,

(CPU_STK_SIZE) MY_APP_STK_SIZE,

(OS_MSG_QTY) 5u,

(OS_TICK) MyTick(1000),

(void*) 0,

(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),

(OS_ERR*)&err);

2.4任務通讯

任务间通讯分为两种情况:原生程序和外部程序间通讯、外部程序和外部程序间通讯。已知原生程序之间通讯通过全局变量实现,通过信号量或互斥量防止竞争。加载外部程序时,如果符号表中有同名的全局变量,会重定位到原生程序中的同名全局变量;如果没有,则为全局变量分配独立内存,并将其名称和地址加入到符号表中,由此实现外部程序和原生程序之间的通讯。

外部和外部程序之间,可以通过约定好的全局变量进行通讯,但这样不够灵活;也可通过调用系统函数实现通讯,使用操作系统提供消息机制。从任务抽象来看,原生任务和外部加载任务并无不同,一律通过调用系统函数接口、消息队列发送消息,信号量、事件标志组实现同步。endprint

3内存改进

在小型嵌入式设备中,内存是一种紧缺资源,因此多数程序内存在使用上都是精打细算,早在编程阶段就已规划好。但对于动态加载的外部程序,内存浪费十分严重。一个程序运行只需要代码段和数据段即可,但实际过程中把整个ELF文件都加载进了内存,ELF文件头、重定位表等在加载后即失去作用,属于冗余信息。

嵌入式烧录好的代码在ROM上运行,但外部程序代码却在RAM上运行,实际上是对RAM的浪费。现代硬件的发展已经支持直接对片上Flash的编程,片上Flash可以直接运行代码。为了节省内存,提高内存利用率,本文对片上Flash地址空间进行管理,在重定位时直接将代码段重定位到片上Flash的地址,数据保留在RAM中,使得二者像原生代码一样分离,达到了节省内存的目的。甚至对数据段也可以规划管理,对于相对固定的参数类数据烧入Flash,而变量保留在内存里,达到对内存最大效率的利用。已知单地址空间,ROM和RAM只是地址分布不同,因此改进地址分布。ROM中的代码地址重定位指向RAM,地址分布如图2所示。

图2地址分布

4测试运行

为测试验证软件模块是否正常运行,基于STM32F103平台,设计不同的外部任务进行加载测试。嵌入式平台上最通用的调试方式是通过LED灯指示程序运行状况。将信号灯任务程序编译为外部程序进行加载,该程序将调用系统函数和驱动接口,对LED进行点亮、闪烁、延时、流水灯等操作。借助该测试样例成功验证功能,包括外部程序成功加载、调用系统函数及多个外部程序之间的通讯。

5结语

本文参考Linux动态加载库原理,实现ELF文件动态加载,使得μcosIII支持外部程序的动态加载,提高操作系统的可拓展性和灵活性。将代码段和数据段分离,尽可能使外部加载代码类似于原生代码的方式执行,对动态加载内存开销大的问题作出改进,通过配置文件提高灵活性,使该技术在嵌入式领域具备实用价值。不足之处是支持多个任务模块之间的通信,但依赖加载顺序,对Static型不支持,在编码过程中需注意Static变量使用,采用全局变量进行替代。本文阐述的动态软件模块机制有待进一步完善。

参考文献参考文献:

[1]张丹.嵌入式系统引导加载程序分析[J].软件,2012,7(9):129132.

[2]李忠儒.嵌入式系统的发展趋势[J].办公自动化,2011,35(11):3537.

[3]王婧怡,应忍冬,周玲玲.微内核系统直接加载文件机制的设计与研究[J].信息技术,2009,15(10):7376

[4]RANDALLHYDE.深入理解计算机[M].韩海东,译.北京:电子工业出版社,2006:295300.

[5]陈紫卿,孙昕.FreeRTOS动态软件模块[J].计算机与现代化,2016,8(6):2428.

[6]郑映,张祖平.基于ARM+μCOSII的程序动态加载实现方案[J].舰船电子工程,2009,29(5):8890.

[7]RICHARD M STALLMAN,THE GCC DEVELOPER COMMUNITY.Using the GNU compiler collection (GNU tools for ARM embedded processors)[M].GNU Press,2016.

[8]楊伟,罗蕾.嵌入式系统中的模块动态加载技术[J].单片机与嵌入式系统应用,2015,23(11):810

[9]李培亮,李振鹏.嵌入式单地址空间操作系统动态加载的研究[J].电子测试,2010,8(7):2327.

[10]朱裕禄.系统下的文件分析[J].电脑知识与技术学术交流,2006,17(26):6466

[11]何先波,唐宁九,吕方,等.文件格式及应用[J].计算机应用研究,2001,18(11):144145

[12]袁鸿野.基于嵌入式操作系统的动态链接器设计与实现[D].成都:电子科技大学,2013.

[13]宁涛.面向嵌入式应用的动态加载机制研究.[D]重庆:重庆大学,2008.

[14]TOOL INTERFACE STANDARD(TIS).Executable and linking format (ELF) specification.version1.2[S].2010.

[15]JEAN J.Labrosse μC/OSIII reference manual[M].US:Micriμm Press,2015.

责任编辑(责任编辑:孙娟)endprint