基于TMS320C6678 DSP+天熠嵌入式操作系统的软件模块动态加载的研究与实现
2023-03-01高艳鹍刘朝晖
高艳鹍,刘 华,刘朝晖
(1.北京计算机技术及应用研究所,北京 100039;2.南华大学,湖南 衡阳 421001)
0 引言
传统的微处理器不适用于进行数字信号处理所需要的高等数学运算,其运算需要较长的计算时间,不能满足现代信号实时处理的要求。而DSP所具有的系统构成灵活、可编程、使用面广的特点,使其在通信、航空航天、医疗仪器、工业控制及信息家电中成为不可或缺的数字信息处理的计算引擎。TMS320C6678(简称C6678)是美国德州仪器(Texas Instrument)于2011年10月推出的一款高性能浮点嵌入式数字信号处理器(DSP)[1]。作为一款8核的DSP,C6678可以满足在军事、工业等领域的实时数据处理的性能要求。C6678提供了多种芯片外设接口,支持多样化总线协议,包括RapidIO、PCIe、I2C、EMIF、UART、SPI总线及千兆网,GPIO、TSIP等[2]。
天熠嵌入式操作系统弹载版是航天二院706所针对弹上计算机开发的嵌入式操作系统产品,目前针对TI的C6000系列DSP能够支持C6713、C6672、C6678,提供多种基础的内核服务。目前,使用TI公司的TMS系列通用DSP处理器做系统开发需要使用专门的开发工具CCS,通过JTAG接口的仿真器将程序下载到目标机调试运行。在某些比较复杂的应用背景下,需要将用户应用程序通过静态链接生成一个独立的可执行镜像下载执行,用户每次修改一行程序都需要将整个程序重新编译下载。当可执行程序较大时,下载过程就会消耗用户较长的时间,效率不高。
在商业的VxWorks操作系统上已经针对X86、PowerPC等处理器实现了应用程序软件模块的动态加载机制,先将内核运行起来,然后通过内核加载应用程序。当应用程序修改后,也只需要先卸载已经在系统中的该软件模块,再重新加载模块,大大提高了开发的效率。动态加载机制的核心技术是延迟链接,涉及到处理器的体系结构、编译器技术、可执行程序的ABI(Application Binary Interface)三方面内容,目前业界尚未有商用的操作系统能够针对TI的DSP处理器平台提供动态加载机制。为在弹载领域满足用户的使用,在基于TI C6678 DSP处理器的天熠嵌入式操作系统中增加动态加载机制做了技术探索,并研发了动态加载器作为天熠嵌入式操作系统产品组件。
1 嵌入式系统中软件模块动态加载的使用需求分析
1.1 软件产品的集成需求
随着设备集成度越来越高,功能越来越复杂,在一个设备上运行的软件往往需要不同的厂商提供软件模块一起配合运行,各模块提供的服务往往又需要被其他模块调用,例如模块A作为主体应用模块需要调用模块B提供的滤波计算服务,调用模块C提供的设备服务,模块B在执行滤波计算时又需要操作系统提供的内存服务接口的存储器分配功能。
传统的开发方式需要在软件开发时将不同的软件模块静态链接成一个单独的可执行程序并下载到目标机上[3]。该方式的一个缺陷是需要不同的厂商提供对应模块的源码,基于知识产权的保护,厂商一般会将源码封装为静态库的形式提供给主程序的研制方,但需要几方厂商必须同时开发完成并提交成果,否则将无法编译链接[4]。在开发时不同厂商软件产品的难度,需要的资源往往无法协调一致,进度无法保证,作为需要使用其他模块的主应用则无法构建出一个完整的可执行应用,及早地开展自己这部分的测试工作。软件模块独立运行视图和传统静态链接的程序运行视图如图1。
图1 两种运行方式视图对比Fig.1 Comparison of two operation modes
1.2 工程中程序升级方面的需求
通常在调试阶段,程序开发人员对DSP进行编程。首先,是在PC机上使用DSP厂商提供的调试开发软件平台编译程序,而后通过DSP板的JTAG调试接口将程序下载到DSP中运行。在实际应用中,通常需要将DSP程序固化在DSP板上FLASH或者EEPROM中,系统上电后,程序自动从FLASH中加载至DSP内部存储区并且执行。但上述这两种方法都需要使用额外的JTAG线来连接主机和DSP板,对于已经装配完毕的密封设备,如果需要更改程序,必须将设备进行拆装,重新安装JTAG线进行调试[5]。从工程应用的角度来看,频繁地对已经装配完毕的设备进行拆装,会严重影响整个系统的稳定性和可靠性。为了避免这种情况的发生,应选择在不拆装设备的同时远程对DSP板上程序进行动态加载[6]。
现阶段,基于现有的同外部设备连接的总线接口进行程序下载的主要技术实现通过在目标机上固化一小段引导程序bootloader,启动后引导程序负责目标机的运行环境的初始化,并通过外部设备接口(一般为串口)同宿主机进行交互,下载应用程序并固化到Flash上,并通过跳转语句将处理器执行的控制权交给应用程序[7]。
该种方式确实可以满足应用程序升级的需求,但有两点不足:第一点是引导程序和应用程序对内存的使用布局需要在开发时通过链接脚本协同规划,从而两个程序相互依赖;第二点是被加载的对象只能是独立的可执行应用程序,bootloader无法加载多个应用[8]。
1.3 对高可靠的控制计算机系统提供动态升级、故障恢复的技术手段
在一些高可靠的控制计算机系统设计阶段会提出可重构设计需求,完成功能定制、动态升级、故障恢复。目前,笔者接触到的对于长期在轨运行的星载计算机领域对该需求尤为迫切。例如,某星载设备的相机控制系统是针对地面物体进行拍摄的高端相机的核心控制部件,相机对调焦的控制算法需要根据地物高度、卫星在轨位置、速度、姿态等相关因素计算调焦距离[9]。由于是首次研制,缺少历史的相关试验数据,对最终的相机成像效果通过地面试验及仿真不能充分有效验证,无法确定在卫星发射后载荷相机对地面物体的成像能否达到预定的目标。基于此,研制方设计了相机控制系统的在轨升级机制,相机的控制系统使用的C5000的DSP处理器,上电启动后先运行一段引导程序,由引导程序判断是否从遥测控制接收到对控制程序的在轨更新命令。当接收到命令时,更新核心的控制程序[10]。但设计中存在一个不足:需要控制计算机的重新上电,运行引导程序。
1.4 在TI C6000 DSP构成的嵌入式系统中引入基于动态链接机制的动态加载解决方案
通过以上几点分析,在由TI C6000 DSP构成的复杂、高可靠嵌入式系统中引入基于动态链接机制的动态加载解决方案有多方面的工程需求。在以天熠嵌入式操作系统弹载版的基础上,开展了研究和设计。
2 动态链接原理分析
2.1 编译器支持
完成动态加载及链接牵涉不同方面的计算机技术,其中一个必要条件是编译器必须能够支持生成延迟重定位的目标程序。这需要编译器在编译时能够暂时屏蔽掉未定址的符号而不会报编译错误,同时为生成地址无关的代码要插入特定的桩代码形式或过程链接表。该目标程序同可执行程序的区别是可执行程序是完全具备执行能力的程序,不需要任何其他模块辅助支持。而动态库程序本身不具备执行能力,其中存在引用的符号处于未定址的状态,如果强制执行,则处理器会进入到异常状态。
根据TI的编译器手册说明能够生成支持动态链接库的ELF文件格式的编译器版本应至少为7.2以上。目前国内针对C6000处理器使用的开发环境主要为CCS3.3和CCS5.5。其中,CCS3.3中使用的Cl6x编译器版本为6.08,CCS5.5的Cl6x编译器版本为7.4,基于CCS3.3开发环境无法实现动态链接库生成。
2.2 EABI文件格式支持
动态链接库的加载过程紧密依赖于TI定义的EABI(ELF Application Binary Interface)文件格式。该格式是TI公司根据标准ELF文件格式自定义的一种文件格式,依据其标准定义主要分为3类,具体见表1。
表1 ELF文件的主要类型Table 1 Main types of ELF files
目前,可使用的文件格式为共享目标文件。从连接和运行的角度,可以分别把目标文件的组成部分划分为以下两种视图,具体如图2。
图2 连接视图和运行视图对比Fig.2 Comparison of connection view and operation view
表2为ELF文件格式中各段的作用,在实际加载过程都是以“段”作为处理对象。
表2 ELF文件格式的各段作用和说明Table 2 Functions and descriptions of each paragraph of ELF file format
2.3 动态链接过程
目标模块被加载到内存空间后,不能立即运行。动态加载机制还须对目标模块进行处理,解决模块的外部引用(符号解析)和重定位,这一步是动态加载过程中的最关键环节。由于模块是被单独编译成共享目标文件,因而在生成目标文件时,调用的其他模块或函数库中的函数和全局变量(统称为符号)的地址仍处于不确定状态。以对全局函数的引用举例说明编译器插入的stub形式及重定址过程。源码如下,import关键字标识其为导入的符号,其作用是指示编译器该符号需要加载时绑定地址。
该源码编译成add.dll后查看其反汇编的代码:
在目标模块中,对.plt段中sym符号的地址以0x0000的形式写入到MVK指令的地址码,并通过B10寄存器直接寻址跳转到sym函数,而.text段中add函数对sym符号的引用通过对.plt段的相对寻址访问(两个端加载后的相对偏移量保持不变)。在重定址时只需根据sym符号的实际地址和记录的引用位置修改MVK指令的地址码。
3 天熠操作系统动态加载器的设计实现
3.1 动态加载器的总体设计
3.1.1 动态加载器的分层设计
动态加载管理采用分层式设计,由用户接口层、管理层、执行层3个层次组成,如图3。
图3 动态加载器的分层结构Fig.3 Hierarchical structure of dynamic loader
1)用户接口层:负责同用户进行交互的界面,提供用户关于动态加载相关命令的输入、解析、执行,具体见表3。
表3 用户接口的主要命令集合Table 3 Main command sets of user interface
2)管理层:管理层主要利用内核提供的多种服务加载模块,对加载模块的ELF文件格式有效性进行判断,保存和维护模块的相关信息,并形成加载模块的关系链表。
3)执行层:主要负责对加载模块外部引用的全局符号进行重定位,完成动态链接过程。
3.1.2 与天熠操作系统内核其他服务的关系
考虑到天熠嵌入式操作系统自身的微内核架构,动态加载器在设计上规划为一个独立的操作系统组件,可以跟随系统进行功能裁剪,并充分利用天熠操作系统已有的系统服务完成动态加载器的设计,如图4。
图4 内核组件服务的调用过程Fig.4 Calling process of kernel component service
Shell组件先使用网络组件提供的ftp服务、内存管理组件、文件系统组件将软件模块下载到文件系统,然后再通过动态加载组件完成模块的动态加载及动态链接。
3.2 动态加载器
3.2.1 内存布局规划
嵌入式系统资源使用一般规划的比较严格,尤其在内存使用方面。DSP程序的开发需要通过cmd文件的链接脚本预先规划好内存的整体布局。主机系统对动态库的加载主要是由进程控制块来维护整个32位虚拟地址空间的内存使用,通过查询空闲空间找到未使用区域后,再加载动态库。因为动态库加载后随着应用生命周期一直运行,不会像堆数据空间的内容、大小经常性变化。基于以上特点,采取在链接脚本中规划出固定大小的内存空间BLOB区域专用于动态库加载。加载器会计算加载的模块总共占用内存空间的大小,当预先规划的加载空间不足时会通过Shell向用户提示,需要用户重新规划内存空间布局。
链接脚本如下:
3.2.2 加载的模块管理
加载器的功能主要是完成动态库文件的加载,包括ELF文件头解析,文件格式有效性判断。对实际加载的模块信息进行维护,包括文件头、程序头、加载段等信息,为后续的模块卸载、依赖性分析提供支撑。描述模块描述信息的结构体定义如图5,并通过DLIMP_Loaded_Module*loaded_module指针建立已加载模块的维护链表快速遍历模块的相关信息。
图5 加载模块的管理Fig.5 Load module management
3.2.3 加载的段管理
依据ELF文件格式定义,其包含了诸多段,但并不是所有段对模块的实际运行有作用。根据内存访问模型和编译器选项,在动态加载过程中需要加载同实际运行相关.plt、.got、.data、.text段,详见表4。
表4 加载段的作用Table 4 Functions of loading section
最终加载到实际运行内存空间的段视图,如图6。
图6 模块加载后的.BLOB块内存视图Fig.6 BLOB Block memory view after module loading
3.3 动态链接器
动态链接器将dll模块加载完成后,根据导入符号的名称遍历查找已加载的Base映像的导出符号,将符号地址填入到.plt、.got段中,使已加载的模块具备执行能力,如图7。
图7 重定址过程Fig.7 Re addressing process
4 结语
本文通过分析在嵌入式系统中软件模块动态加载的使用需求和动态链接原理,并在天熠嵌入式操作系统的产品中实现了基于C6678处理器的软件模块动态加载组件。该组件可以完成多个用户定义模块的自动(文件系统)、手动(网络或串口终端)加载及卸载,可用于支持嵌入式系统的功能重构、产品的动态升级、故障恢复,最终使整个系统扩展性和灵活性大大提高,较好地满足了用户的实际需要。