本文以实际项目中一个MIPS32" title="MIPS32">MIPS32架构的CPU和板级系统为例,阐述了如何将Linux操作系统" title="Linux操作系统">Linux操作系统移植到目标平台上。
1 目标平台概述
本文所讨论的开发平台采用的CPU是同济大学微电子中心自主开发的BC320处理器,采用MIPS 4KC的体系结构,带MMU、无浮点协处理器、标准5段流水线,指令及数据Cache的大小各为4KB,寻址空间为4GB,其中0x00000000-0x7fffffff为用户空间,0x80000000-0xffffffff为核心空间,板级系统采用了PMC的PM8172芯片组,支持最高128MB的SDRAM,BOOT ROM的地址空间是0x1fc00000-0x1fffffff。
2 Linux交叉编译环境的建立及内核配置和编译
在进行实际的Linux操作系统移植之前,需要在宿主机上建立图1所示的MIPS的交叉编译环境,以便能在普通PC机上通过交叉编译工具来调试运行在目标开发板上的程序。
建立MIPS交叉编译环境的主要工具有binutils、GCC、glibc以及作为调试器的gdb等,其中binutils为二进制文件的处理工具,它主要包括一些辅助开发工具,例如:readelf可显示elf文件信息及段信息;nm可列出程序的符号表;strip将不必要的代码去掉以减小可执行文件,objdump可用来显示返汇编代码等,GCC是GNU提供的支持多种输入高级语言与多种输出机器码的编译器,是Linux操作系统的配套编译器,支持Linux所采用的扩展C语言。glibc是连接和运行库,由于此链接和运行库须运行在目标开发板上,所以必须用先前建立的交叉编译器对其进行编译,如图内核大小要求较为苛刻。还可以使用uclibc等其他链接和运行库作为glibc的替代品,此外,若不是从硬盘启动,则还须为Linux制作ramdisk。在ramdisk上,除了要安放/dev(放置Linux操作系统所需要的设备文件)、/etc(放置Linux系统配置文件)、/lib(放置交叉编译后生成的库文件)等目录及其下的文件外,还需要在/bin和/sbin下放置各种系统必需的命令程序,如shell、init、vi等,为此需要busybox或者tinylogin等专为Linux操作系统提供的标准工具程序。凡此种种,都可以在GNU旗下的网站下载并自由修改其源代码。
由于Linux操作系统的内核源代码支持各种不同的体系结构和不同的应用需要,所以在使用交叉译码器编译前还需要进行内核的配置工作,包括选择处理器的体系结构、文件系统的种类、板级支持,对设备驱动的支持以及是否使用ramdisk等。配置工具包括make config、make menuconfig、make xconfig,推荐使用操作界面更为良好的make menuconfig及make xconfig。在内核配置工作完成后即可进行内核编译工作,Linux源代码提供的强大的makefile功能,使得复杂的编译过程操作起来并不困难。
关于Linux交叉编译环境的建立及内核配置和编译的详细流程,在《Building Embedded Linux Systems》(Karim Yaghmour著)内有详细的论述,本文为此不再赘述。
3 Linux移植中实际指令集小于标准MIPS指令集的问题
随着软件可移植问题在整个软件方法学中重要性的日益增长,各种大型软件无不把提高自身的跨平台性作为软件设计的主要目标之一。为此,Linux提供了对应用领域内各大主流体系结构的支持,仅以MIPS体系结构为例,Linux操作系统2.4.26版本的内核就支持几乎所有32位和64位不同版本的MIPS架构,为操作系统的移植工作提供了巨大的便利。然而,处于种种原因(诸如专利保护或特殊应用),有相当一部分采用MIPS体系结构的芯片产品只提供了标准的MIPS指令集的一个子集。一旦内核代码在编译完成后生成了不属于实际指令集的指令,CPU将发生保留指令例外,可以说,当体系结构间的差异不再成为最主要的移植工作时,如何逻辑等效地消除实际指令集和标准指令集间的差异成了Linux移植工作中最重要的一环,由于MIPS的专利保护,相当多MIPS兼容芯片的开发者并未对指令集的4条非对齐存取指令(lwl、lwr、swl、swr)加以实现,如Realtek RTL8181"Wireless LAN Access Point/Gateway Controller"等,下文将以同济大学自主开发的BC320芯片为例,从修改内核源代码、修改编译器及汇编器这两个方面出发,讨论如何解决4条非对齐存取指令未被实现的问题,由于编译器及汇编器的修改涉及编译原理方面的知识,不在本文范围之内,所以将把重点放在讨论修改内核源代码的方法上,对GCC和GAS修改的基本知识仅作一般介绍。
3.1 修改内核源代码中的保留指令例外处理程序
当CPU执行到未被实现的机器码时,将会发生reserved instruction exception,然后根据例外的种类跳转到相应的例外处理程序入口处。借助于编写对应的例外处理程序,可以为被实际指令集实现但又属于标准指令集的指令(Iwl、lwr、swl、swr为例)提供逻辑等效的替换方法。在Linux内核源(以2.4.26版本为例)代码的目录树下进入.\arch\mips\kernal目录,打开traps.c文件,并添加simulate_lxRI函数,代码如下:
其中:_OP_为宏操作,可取出32位机器码中的操作码以判断操作类型;0x22对应于lwl指令、0x26对应于lwr指令,0x2A对应于swl指令,而0x2E则对应于swr指令。程序将根据不同的操作码进入不同的替代程序。va和byte变量则计算出4条非对齐指令的偏移量。
完成代码后将此函数添加到同一文件的do_ri函数中去,此函数即负责处理Linux操作系统的保留指令例外,添加的代码为:
if(simulate_lxRI(regs,opcode)){
compute_return_epc(regs);
return;
}
即当simulate_lxRI正确处理完非对齐存取指令并返回1后,系统将通过compute_return_epc函数把epc寄存器的值放回pc寄存器并返回;否则,继续处理do_ri中其他的例外处理程序。
这一方法工作量小,容易保证修改后的等效性,大多数熟悉C语言的程序员来说都是易于掌握的。在软硬件协同开发的系统设计前期具有很大的实际使用价值。系统设计师可快速建立原型机跑通操作系统,以验证软硬件的正确性。但由于其采用的是例外处理的方式,若频繁发生例外则将影响系统性能,所以对memcpy(此函数代码在Linux源代码的rach\mips\lib目录下的memcpy.s文件中)这样使用频繁的汇编程序应手工修改其代码。再加上编译器一般不会生成4条非对齐指令(仅当C程序中的结构体有非字对齐等少数情况下会出现),所以此修改方法可保证大致接近原性能。
3.2 修改GCC编译器或GAS汇编器
尽管利用exception handler来解决保留指令例外问题方便、快捷、但其效率终究是低于直接修改GCC编译器或GAS汇编器的,此外,修改exception handler的方法在smp的情况下有可能带来冲突,所以,直接修改GCC或GAS的方法是有其实用价值的。
GCC的前端可以支持多种语音,后端可以支持多种体系结构,这一特性是由作为中间语言的RTL(寄存器传输语言)实现的,其大致结构如图2所示。
其中负责指令生成的部分在后端,涉及的源代码文件包括inst-cmit.c、inst-flags.h、inst-config.h、inst-code.h、inst-extrax.h、inst-opinit.c、inst-output.c等。此外,作为机器描述的machine.h、machine.md、machine.h文件也必须加以考虑,相当一部分以inst开头的文件是由GCC提供的一组gen*工具根据这3个机器描述文件自动生成的。
修改GAS相对简单,只须修改GAS源代码中的tcmips.c文件,但效率相对低于修改GCC源代码。
直接修改GCC编译器的效率且一劳永逸,但由于编译器的实现原理和操作系统大相径庭,所以此方法难度较大,可能会拖延移植工作进度,而编译器修改其本身的测试工作由于要和Linux操作系统的移植工作混合在一起进行,对软件Debug来说也是相当复杂的,所以,在系统开发早期推荐使用修改保留指令例外处理程序的方法,当软硬件都能保证相当的正确性时再使用修改GCC编译器的方法。
结语
本文根据一个特定的开发平台,介绍了如何将Linux操作系统移植到MIPS体系结构系统上的大致流程和主要技术,就移植过程中所遇到的问题,以4条非对齐指令为例,具体讨论了如何解决实际实现的指令集未能完全覆盖标准指令集而产生保留指令例外的问题,文中详细介绍了修改保留指令例外处理程序的方法,简述了修改GCC或GAS的方法,掌握这些移植流程和修改技术,对于开发嵌入式系统相当的实用价值,对于由其他体系结构实现的开发平台也具有相当的参考意义。