嵌⼊式Linux系统移植的四⼤步骤
最近在学习系统移植的相关知识,在学习和调试过程中,发现了很多问题,也解决了很多问题,但总是对于我们的开发结果有⼀种莫名其妙的感觉,纠其原因,主要对于我们的开发环境没有⼀个深刻的认识,有时候⼏个简单的命令就可以完成⾮常复杂的功能,可是我们有没有想过,为什么会有这样的效果?如果没有去追问,只是机械地完成,并且看到实验效果,这样做其实并没有真正的掌握系统移植的本质。
在做每⼀个步骤的时候,⾸先问问⾃⼰,为什么要这样做,然后再问问⾃⼰正在做什么?搞明⽩这⼏个问题,我觉得就差不多了,以后不管更换什么平台,什么芯⽚,什么开发环境,你都不会迷糊,很快就会上⼿。对于嵌⼊式的学习⽅法,我个⼈⽅法就是:从宏观上把握(解决为什么的问题),微观上研究(解决正在做什么的问题),下⾯以⾃⼰学习的arm-cortex_a8开发板为⽬标,介绍下⾃⼰的学习⽅法和经验。
嵌⼊式Linux系统移植主要由四⼤部分组成:
⼀、搭建交叉开发环境
⼆、bootloader的选择和移植
三、kernel的配置、编译、和移植
四、根⽂件系统的制作
第⼀部分:搭建交叉开发环境
先介绍第⼀分部的内容:搭建交叉开发环境,⾸先必须得思考两个问题,什么是交叉环境? 为什么需要搭建交叉环境?
先回答第⼀个问题,在嵌⼊式开发中,交叉开发是很重要的⼀个概念,开发的第⼀个环节就是搭建环境,第⼀步不能完成,后⾯的步骤从⽆谈起,这⾥所说的交叉开发环境主要指的是:在开发主机上(通常是我的pc机)开发出能够在⽬标机(通常是我们的开发板)上运⾏的程序。嵌⼊式⽐较特殊的是不能在⽬标机上开发程序(狭义上来说),因为对于⼀个原始的开发板,在没有任何程序的情况下它根本都跑不起来,为了让它能够跑起来,我们还必须要借助pc机进⾏烧录程序等相关⼯作,开发板才能跑起来,这⾥的pc机就是我们说的开发主机,想想如果没有开发主机,我们的⽬标机基本上就是⽆法开发,这也就是电⼦⾏业的⼀句名⾔:搞电⼦,说⽩了,就是玩电脑!
然后回答第⼆个问题,为什么需要交叉开发环境?主要原因有以下⼏点:
原因1:嵌⼊式系统的硬件资源有很多限制,⽐如cpu主频相对较低,内存容量较⼩等,想想让⼏百M
HZ主频的MCU去编译⼀个Linux kernel 会让我们等的不耐烦,相对来说,pc机的速度更快,硬件资源更加丰富,因此利⽤pc机进⾏开发会提⾼开发效率。
原因2:嵌⼊式系统MCU体系结构和指令集不同,因此需要安装交叉编译⼯具进⾏编译,这样编译的⽬标程序才能够在相应的平台上⽐如:ARM、MIPS、 POWEPC上正常运⾏。
交叉开发环境的硬件组成主要由以下⼏⼤部分:
1.开发主机
2.⽬标机(开发板)
3.⼆者的链接介质,常⽤的主要有3中⽅式:(1)串⼝线 (2)USB线 (3)⽹线
对应的硬件介质,还必须要有相应的软件“介质”⽀持:
1.对于串⼝,通常⽤的有串⼝调试助⼿,putty⼯具等,⼯具很多,功能都差不多,会⽤⼀两款就可以;
2.对于USB线,当然必须要有USB的驱动才可以,⼀般芯⽚公司会提供,⽐如对于三星的芯⽚,USB下载主要由DNW软件来完成;
3.对于⽹线,则必须要有⽹络协议⽀持才可以,常⽤的服务主要两个
第⼀:tftp服务:
主要⽤于实现⽂件的下载,⽐如开发调试的过程中,主要⽤tftp把要测试的bootloader、kernel和⽂件系统直接下载到内存中运⾏,⽽不需要预先烧录到Flash芯⽚中,⼀⽅⾯,在测试的过程中,往往需要频繁的下载,如果每次把这些要测试的⽂件都烧录到Flash中然后再运⾏也可以,但是缺点是:过程⽐较⿇烦,⽽且Flash的擦写次数是由限的;另外⼀⽅⾯:测试的⽬的就是把这些⽬标⽂件加载到内存中直接运⾏就可以了,⽽tftp就刚好能够实现这样的功能,因此,更没有必要把这些⽂件都烧录到Flash中去
第⼆:nfs服务:
linux下gcc编译的四个步骤主要⽤于实现⽹络⽂件的挂载,实际上是实现⽹络⽂件的共享,在开发的过程中,通常在系统移植的最后⼀步会制作⽂件系统,那么这是可以把制作好的⽂件系统放置在我们开发主机PC的相应位置,开发板通过nfs服务进⾏挂载,从⽽测试我们制作的⽂件系统是否正确,在整个过程中并不需要把⽂件系统烧录到Flash中去,⽽且挂载是⾃动进⾏挂载的,bootload启动后,kernel运⾏起来后会根据我们设置的启动参数进⾏⾃动挂载,因此,对于开发测试来讲,这种⽅式⾮常的⽅便,能够提⾼开发效率。
另外,还有⼀个名字叫samba的服务也⽐较重要,主要⽤于⽂件的共享,这⾥说的共享和nfs的⽂件共享不是同⼀个概念,nfs的共享是实现⽹络⽂件的共享,⽽samba实现的是开发主机上Windows主机和Linux虚拟机之间的⽂件共享,是⼀种跨平台的⽂件共享,⽅便的实现⽂件的传输。
以上这⼏种开发的⼯具在嵌⼊式开发中是必备的⼯具,对于嵌⼊式开发的效率提⾼做出了伟⼤的贡献,因此,要对这⼏个⼯具熟练使⽤,这样你的开发效率会提⾼很多。等测试完成以后,就会把相应的⽬标⽂件烧录到Flash中去,也就是等发布产品的时候才做的事情,因此对于开发⼈员来说,所有的⼯作永远是测试。
通过前⾯的⼯作,我们已经准备好了交叉开发环境的硬件部分和⼀部分软件,最后还缺少交叉编译器,读者可能会有疑问,为什么要⽤交叉编译器?前⾯已经讲过,交叉开发环境必然会⽤到交叉编译⼯具,通俗地讲就是在⼀种平台上编译出能运⾏在体系结构不同的另⼀种平台上的程序,开发主机PC平台(X86 CPU)上编译出能运⾏在以ARM为内核的CPU平台上的程序,编译得到的程序在X86 CPU平台上是不能运⾏的,必须放到ARM CPU平台上才能运⾏,虽然两个平台⽤的都是Linux系统。相对于交叉编译,平常做的编译叫本地编译,也就是在当前平台编译,编译得到的程序也是在本地执⾏。⽤来编译这种跨平台程序的编译器就叫交叉编译器,相对来说,⽤来做本地编译的⼯具就叫本地编译器。所以要⽣成在⽬标机上运⾏的程序,必须要⽤交叉编译⼯具链来完成。
这⾥⼜有⼀个问题,不就是⼀个交叉编译⼯具吗?为什么⼜叫交叉⼯具链呢?原因很简单,程序不能光编译⼀下就可以运⾏,还得进⾏汇编和链接等过程,同时还需要进⾏调试,对于⼀个很⼤⼯程,还需要进⾏⼯程管理等等,所以,这⾥说的交叉编译⼯具是⼀个由编译器、连接器和解释器组成的综合开发环境,交叉编译⼯具链主要由binutils(主要包括汇编程序as和链接程序ld)、gcc(为GNU系统提供C编译器)和glibc(⼀些基本的C函数和其他函数的定义) 3个部分组成。有时为了减⼩libc库的⼤⼩,也可以⽤别的 c 库来代替 glibc,例如 uClibc、dietlibc 和 newlib。
那么,如何得到⼀个交叉⼯具链呢?是从⽹上下载⼀个“程序”然后安装就可以使⽤了吗?回答这个问题之前先思考这样⼀个问题,我们的交叉⼯具链顾名思义就是在PC机上编译出能够在我们⽬标开发平台⽐如ARM上运⾏的程序,这⾥就⼜有⼀个问题了,我们的ARM处理器型号⾮常多,难道有专门针对我们某⼀款的交叉⼯具链吗?若果有的话,可以想⼀想,这么多处理器平台,每个平台专门定制⼀个交叉⼯具链放在⽹络上,然后供⼤家去下载,想想可能需要很久才能到适合你的编译器,显然这种做法不太合理,且浪费资源!因此,要得到⼀个交叉⼯具链,就像我们移植⼀个Linux内核⼀样,我们只关⼼我们需要的东西,编译我们需要的东西在我们的平台上运⾏,不需要的东西我们不选择不编译,所以,交叉⼯具链的制作⽅法和系统移植有着很多相似的地⽅,也就是说,交叉开发⼯具是⼀个⽀持很多平台的⼯具集的集合(类似于Linux源码),然后我们只需从这些⼯具集中出跟我们平台相关的⼯具就⾏了,那么如何才能到跟我们的平台相关的⼯具,这就是涉及到⼀个如何制作交叉⼯具链的问题了。
通常构建交叉⼯具链有如下三种⽅法:
⽅法⼀:分步编译和安装交叉编译⼯具链所需要的库和源代码,最终⽣成交叉编译⼯具链。该⽅法相对⽐较困难,适合想深⼊学习构建交叉⼯具链的读者。如果只是想使⽤交叉⼯具链,建议使⽤下列的⽅法⼆构建交叉⼯具链。
⽅法⼆:通过Crosstool-ng脚本⼯具来实现⼀次编译,⽣成交叉编译⼯具链,该⽅法相对于⽅法⼀要简单许多,并且出错的机会也⾮常少,建议⼤多数情况下使⽤该⽅法构建交叉编译⼯具链。
⽅法三:直接通过⽹上下载已经制作好的交叉编译⼯具链。该⽅法的优点不⽤多说,当然是简单省事,但与此同时该⽅法有⼀定的弊端就是局限性太⼤,因为毕竟是别⼈构建好的,也就是固定的,没有灵活性,所以构建所⽤的库以及编译器的版本也许并不适合你要编译的程序,同时也许会在使⽤时出现许多莫名其妙的错误,建议读者慎⽤此⽅法。
crosstool-ng是⼀个脚本⼯具,可以制作出适合不同平台的交叉编译⼯具链,在进⾏制作之前要安装⼀下软件:
$ sudo apt-get install g++ libncurses5-dev bison flex texinfo automake libtool patch gcj cvs cvsd gawk
crosstool脚本⼯具可以在下载到本地,然后解压,接下来就是进⾏安装配置了,这个配置优点类似内核的配置。主要的过程有以下⼏点:
1. 设定源码包路径和交叉编译器的安装路径
2.
修改交叉编译器针对的构架
修改交叉编译器针对的构架
3. 增加编译时的并⾏进程数,以增加运⾏效率,加快编译,因为这个编译会⽐较慢。
4. 关闭JAVA编译器,减少编译时间
5. 编译
6. 添加环境变量
7. 刷新环境变量。
8. 测试交叉⼯具链
到此,嵌⼊式Linux系统移植四⼤部分的第⼀部分⼯作全部完成,接下来可以进⾏后续的开发了。
第⼆部分:bootloader的选择和移植
⼀、Boot Loader 概念
就是在操作系统内核运⾏之前运⾏的⼀段⼩程序。通过这段⼩程序,我们可以初始化硬件设备、建⽴内存空间的映射图,从⽽将系统的软硬件环境带到⼀个合适的状态,以便为最终调⽤操作系统内核准备好正确的环境,他就是所谓的引导加载程序(Boot Loader)。
【图1】Flash存储中存放⽂件的分布图
⼆、为什么系统移植之前要先移植BootLoader?
BootLoader的任务是引导操作系统,所谓引导操作系统,就是启动内核,让内核运⾏就是把内核加载到内存RAM中去运⾏,那先问两个问题:第⼀个问题,是谁把内核搬到内存中去运⾏?第⼆个问题:我们说的内存是SDRAM,⼤家都知道,这种内存和SRAM不同,最⼤的不同就是SRAM只要系统上电就可以运⾏,⽽SDRAM需要软件进⾏初始化才能运⾏,那么在把内核搬运到内存运⾏之前必须要先初始化内存吧,那么内存是由谁来初始化的呢?其实这两件事情都是由bootloader来⼲的,⽬的是为内核的运⾏准备好软硬件环境,没有bootloadr我们的系统当然不能跑起来。
三、bootloader的分类。
⾸先更正⼀个错误的说法,很多⼈说bootloader就是U-boot,这种说法是错误的,确切来说是u-boot是bootloader的⼀种。也就是说bootloader具有很多种类,⼤概的分类如下图所⽰:
【图2】bootloader分类图
由上图可以看出,不同的bootloader具有不同的使⽤范围,其中最令⼈瞩⽬的就是有⼀个叫U-Boot的bootloader,是⼀个通⽤的引导程序,⽽且同时⽀持X86、ARM和PowerPC等多种处理器架构。U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项⽬,是由德国DENX⼩组开发的⽤于多种嵌⼊式CPU的bootloader程序,对于Linux的开发,德国的u-boot做出了巨⼤的贡献,⽽且是开源的。 u-boot具有以下特点:
①开放源码;
②⽀持多种嵌⼊式操作系统内核,如Linux、NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS;
③⽀持多个处理器系列,如PowerPC、ARM、x86、MIPS、XScale;
④较⾼的可靠性和稳定性;
⑤⾼度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等;
⑥丰富的设备驱动源码,如串⼝、以太⽹、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等;
⑦较为丰富的开发调试⽂档与强⼤的⽹络技术⽀持;
其实,把u-boot可以理解为是⼀个⼩型的操作系统。
四、u-boot的⽬录结构
* board ⽬标板相关⽂件,主要包含SDRAM、FLASH驱动;
* common 独⽴于处理器体系结构的通⽤代码,如内存⼤⼩探测与故障检测;
* cpu 与处理器相关的⽂件。如mpc8xx⼦⽬录下含串⼝、⽹⼝、LCD驱动及中断初始化等⽂件;
* driver 通⽤设备驱动,如CFI FLASH驱动(⽬前对INTEL FLASH⽀持较好)
* doc U-Boot的说明⽂档;
* examples可在U-Boot下运⾏的⽰例程序;如hello_world.c,timer.c;
* include U-Boot头⽂件;尤其configs⼦⽬录下与⽬标板相关的配置头⽂件是移植过程中经常要修改的⽂件;
* lib_xxx 处理器体系相关的⽂件,如lib_ppc, lib_arm⽬录分别包含与PowerPC、ARM体系结构相关的⽂件;
* net 与⽹络功能相关的⽂件⽬录,如bootp,nfs,tftp;
* post 上电⾃检⽂件⽬录。尚有待于进⼀步完善;
* rtc RTC驱动程序;
* tools ⽤于创建U-Boot S-RECORD和BIN镜像⽂件的⼯具;
五、u-boot的⼯作模式
U-Boot的⼯作模式有启动加载模式和下载模式。启动加载模式是Bootloader的正常⼯作模式,嵌⼊式产品发布时,Bootloader必须⼯作在这种模式下,Bootloader将嵌⼊式操作系统从FLASH中加载到SDRAM中运⾏,整个过程是⾃动的。下载模式就是Bootloader通过某些通信⼿段将内核映像或根⽂件系统映像等从PC机中下载到⽬标板的SDRAM中运⾏,⽤户可以利⽤Bootloader提供的⼀些令接⼝来完成⾃⼰想要的操作,这种模式主要⽤于测试和开发。
六、u-boot的启动过程
⼤多数BootLoader都分为stage1和stage2两⼤部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以⽤汇编语⾔来实现,⽽stage2则通常⽤C语⾔来实现,这样可以实现复杂的功能,⽽且有更好的可读性和移植性。
1、 stage1(start.s代码结构)
U-boot的stage1代码通常放在start.s⽂件中,它⽤汇编语⾔写成,其主要代码部分如下:
(1)定义⼊⼝。由于⼀个可执⾏的image必须有⼀个⼊⼝点,并且只能有⼀个全局⼊⼝,通常这个⼊⼝放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个⼊⼝,该⼯作可通过修改连接器脚本来完成。
(2)设置异常向量(exception vector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化内存控制器。
(5)将rom中的程序复制到ram中。
(6)初始化堆栈。
(7)转到ram中执⾏,该⼯作可使⽤指令ldrpc来完成。
2、 stage2(C语⾔代码部分)
lib_arm/board.c中的start armboot是C语⾔开始的函数,也是整个启动代码中C语⾔的主函数,同时还是整个u-boot(armboot)的主函数,该函数主要完成如下操作:
(1)调⽤⼀系列的初始化函数。
(2)初始化flash设备。
(3)初始化系统内存分配函数。
(4)如果⽬标系统拥有nand设备,则初始化nand设备。
(5)如果⽬标系统有显⽰设备,则初始化该类设备。
(6)初始化相关⽹络设备,填写ip,c地址等。
(7)进⼊命令循环(即整个boot的⼯作循环),接受⽤户从串⼝输⼊的命令,然后进⾏相应的⼯作。
七、基于cortex-a8的s5pc100bootloader启动过程分析
s5pc100⽀持两种启动⽅式,分别为USB启动⽅式和NandFlash启动⽅式:
1. S5PC100 USB启动过程
[1] A8 reset, 执⾏iROM中的程序
[2] iROM中的程序根据S5PC100的配置管脚(SW1开关4,拨到4对⾯),判断从哪⾥启动(USB)
[3] iROM中的程序会初始化USB,然后等待PC机下载程序
[4] 利⽤DNW程序,从PC机下载SDRAM的初始化程序到iRAM中运⾏,初始化SDRAM
[5] SDRAM初始化完毕,iROM中的程序继续接管A8, 然后等待PC下载程序(BootLoader)
[6] PC利⽤DNW下载BootLoader到SDRAM
[7] 在SDRAM中运⾏BootLoader
2. S5PC100 Nandflash启动过程
[1] A8 reset, 执⾏IROM中的程序
[2] iROM中的程序根据S5PC100的配置管脚(SW1开关4,拨到靠4那边),判断从哪⾥启动(Nandflash)
[3] iROM中的程序驱动Nandflash
[4] iROM中的程序会拷贝Nandflash前16k到iRAM
[5] 前16k的程序(BootLoader前半部分)初始化SDRAM,然后拷贝完整的BootLoader到SDRAM并运⾏
[6] BootLoader拷贝内核到SDRAM,并运⾏它
[7] 内核运⾏起来后,挂载rootfs,并且运⾏系统初始化脚本
⼋、u-boot移植(基于cortex_a8的s5pc100为例)
1.建⽴⾃⼰的平台
(1).下载源码包2010.03版本,⽐较稳定
(2).解压后添加我们⾃⼰的平台信息,以smdkc100为参考版,移植⾃⼰s5pc100的开发板
(3).修改相应⽬录的⽂件名,和相应⽬录的Makefile,指定交叉⼯具链。
(4).编译
(5).针对我们的平台进⾏相应的移植,主要包括修改SDRAM的运⾏地址,从0x20000000
(6).“开关”相应的宏定义
(7).添加Nand和⽹卡的驱动代码
(8).优化go命令
(9).重新编译 make distclean(彻底删除中间⽂件和配置⽂件) make s5pc100_config(配置我们的开发板) make(编译出我们的u-boot.bin镜像⽂件)
(10).设置环境变量,即启动参数,把编译好的u-boot下载到内存中运⾏,过程如下:
1. 配置开发板⽹络
ip地址配置:
$setenv ipaddr 192.168.0.6 配置ip地址到内存的环境变量
$saveenv 保存环境变量的值到nandflash的参数区
⽹络测试:
在开发开发板上ping虚拟机:
$ ping 192.168.0.157(虚拟机的ip地址)
如果⽹络测试失败,从下⾯⼏个⽅⾯检查⽹络:
1. ⽹线连接好
2. 开发板和虚拟机的ip地址是否配置在同⼀个⽹段
3. 虚拟机⽹络⼀定要采⽤桥接(VM--Setting-->option)
4. 连接开发板时,虚拟机需要设置成静态ip地址
2. 在开发板上,配置tftp服务器(虚拟机)的ip地址
$setenv serverip 192.168.0.157(虚拟机的ip地址)
$saveenv
3. 拷贝u-boot.bin到/tftpboot(虚拟机上的⽬录)
4. 通过tftp下载u-boot.bin到开发板内存
$ tftp 20008000(内存地址即可) u-boot.bin(要下载的⽂件名)
如果上⾯的命令⽆法正常下载:
1. serverip配置是否正确
2. tftp服务启动失败,重启tftp服务
#sudo service tftpd-hpa restart
5. 烧写u-boot.bin到nandflash的0地址
$nand erase 0(起始地址) 40000(⼤⼩) 擦出nandflash 0 - 256k的区域
$nand write 20008000((缓存u-boot.bin的内存地址) 0(nandflash上u-boot的位置) 40000(烧写⼤⼩)
6. 切换开发板的启动⽅式到nandflash
1. 关闭开发板
2. 把SW1的开关4拨到4的那边
3. 启动开发板,它就从nandflash启动
第三部分:kernel的配置、编译、和移植
⼀、将下载好的linux-2.6.35.tar.bz2拷贝到主⽬录下解压
⼆、修改顶层⽬录下的Makefile,主要修改平台的体系架构和交叉编译器,代码如下:
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?=
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论