嵌入式操作系统-BootLoader李春杰主要内容BootLoader的概念BootLoader的安装BootLoader的主要任务BootLoader的框架结构部分开源的bootloader引言一个嵌入式Linux系统从软件的角度看通常可以分为四个层次:1.引导加载程序。包括固化在固件(firmware)中的boot代码(可选和BootLoader两大部分2.Linux内核。特定于嵌入式板子的定制内核及内核的启动参数3.文件系统。包括根文件系统和建立于Flash内存设备之、上的文件系统,通常用RAM-Disk来作为根文件系统。4.用户应用程序。特定于用户的应用程序引导加载程序引导加载程序是系统加电后运行的第一段软件代码例如PC机的引导加载程序,包括1.BIOS(其本质就是一段固件程序)2.位于硬盘MBR中的OSBootLoader比如LILO、GRUB等。BIOS的主要任务是1.进行硬件检测和资源分配2.将MBR中的OSBootLoader读到系统的RAM中3.将控制权交给OSBootLoaderBootLoader的主要运行任务是1.将内核映象从硬盘上读到RAM中2.跳转到内核的入口点去运行,也即启动操作系统在嵌入式系统中通常并没有像BIOS那样的固件程序注:有的嵌入式CPU也会内嵌一段短小的启动程序整个系统的加载启动任务完全由BootLoader完成如在一个基于ARM7TDMIcore的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。BootLoader的概念BootLoader是在操作系统内核运行之前运行的第一段小程序。初始化硬件设备建立内存空间的映射图将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。加载操作系统内核映象到RAM中,并将系统的控制权传递给它例如:Linux通用的BootLoader在嵌入式世界里建立一个通用的BootLoader几乎是不可能的嵌入式系统是面向应用的BootLoader对硬件的依赖性非常强,特别是在嵌入式系统世界中尽管如此,仍可对BootLoader归纳出一些通用的概念,以指导用户特定的BootLoader设计与实现。BootLoader依赖于1.CPU的体系结构不同的CPU体系结构都有不同的BootLoader有些BootLoader也支持多种CPU体系结构例如U-Boot同时支持ARM和MIPS体系结构2.具体的嵌入式板级设备的配置对于两块不同的嵌入式板,即使它们基于同一种CPU,要想让运行在一块板子上的BootLoader也能运行在另一块板子上,通常也都需要修改BootLoader源程序BootLoader的安装媒介系统加电或复位后,所有的CPU通常都从某个由CPU制造商预先安排的地址上取指令。比如,基于ARM7TDMIcore的CPU在复位时通常都从地址0x00000000取它的第一条指令。基于CPU构建的嵌入式系统通常都有某种类型的固态存储设备被映射到该预先安排的地址上。比如:ROM、EEPROM或FLASH等BootLoader被放在这个地方,因此:在系统加电后,CPU将首先执行BootLoader程序。一个同时装有BootLoader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图KernelRootfilesystemBootloaderBootparametersBootLoader的安装烧写bootloader程序一般通过jtag烧写需要jtag连接器和PC端的烧写程序编程器控制BootLoader的设备或机制主机和目标机之间一般通过串口建立连接BootLoader在执行时常通过串口来进行I/O,比如输出打印信息到串口从串口读取用户控制字符等。最常用的串口通信软件Linux:minicomWindows:附件中的超级终端BootLoader的启动过程BootLoader的启动过程可以是单阶段(SingleStage)一些只需完成很简单功能的bootloader可能是单阶段的多阶段(Multi-Stage)通常多阶段的BootLoader能提供更为复杂的功能,以及更好的可移植性从固态存储设备上启动的BootLoader大多都是2阶段的启动过程,也即启动过程可以分为stage1和stage2两部分BootLoader的操作模式大多数BootLoader包含两种不同的操作模式启动加载(Bootloading)模式和下载(Downloading)模式这种区别仅对于开发人员才有意义从最终用户的角度看,BootLoader的作用就是加载操作系统,并不存在上述两种模式的区别启动加载模式也称为自主(Autonomous)模式BootLoader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户(开发人员)的介入。这种模式是BootLoader的正常工作模式在嵌入式产品发布时,BootLoader必须工作在该模式下下载模式目标机的BootLoader通过串口或网络等通信手段从主机(Host)下载文件比如内核映像和根文件系统映像HosttargetramtargetFLASH该模式的使用时机通常在第一次安装内核与根文件系统时被使用也用于此后的系统更新工作于该模式下的BootLoader通常都会向它的终端用户提供一个简单的命令行接口下载(Downloading)模式示例BootLoader的操作模式一些功能强大的BootLoader通常1.同时支持这两种工作模式如Blob和U-Boot2.允许用户在这两种工作模式之间进行切换比如,Blob在启动时处于正常的启动加载模式,但是它会延时10秒等待终端用户按下任意键而将blob切换到下载模式。如果在10秒内没有用户按键,则blob继续启动Linux内核。BootLoader与主机之间进行文件传输所用的通信设备及协议最常见通信设备是串口传输协议通常是xmodem、ymodem、zmodem之一。但串口传输的速度有限更好的选择是以太网使用TFTP协议主机方必须有一个软件提供TFTP服务BootLoader的主要任务系统假设:内核映像与根文件系统映像都被加载到RAM中运行。尽管在嵌入式系统中它们也可直接运行在ROM或Flash这样的固态存储设备中。但这种做法无疑是以运行速度的牺牲为代价的。从操作系统的角度看,BootLoader的总目标就是正确地加载并调用内核来执行板级调试角度软件开发角度-下载模式启动加载模式:调用内核来执行软件升级角度-提供了程序更新的便捷方式BootLoader的典型结构框架由于BootLoader的实现依赖于CPU体系结构,大多数BootLoader都分为stage1和stage2两大部分Stage1依赖于CPU体系结构,如设备初始化代码通常用汇编语言实现,短小精悍Stage2通常用C语言可以实现复杂功能代码具有较好的可读性和可移植性BootLoader的stage1Stage1直接运行在固态存储设备上,通常包括以下步骤硬件设备初始化为加载BootLoader的stage2准备RAM空间拷贝BootLoader的stage2到RAM空间中设置好堆栈跳转到stage2的C入口点Stage1:1、硬件初始化这是BootLoader一开始就执行的操作目的:为stage2及kernel的执行准备好基本硬件环境通常包括1.屏蔽所有的中断为中断提供服务通常是OS或设备驱动程序的责任,在BootLoader阶段不必响应任何中断中断屏蔽可以通过写CPU的中断屏蔽寄存器或状态寄存器来完成比如ARM的CPSR寄存器2.设置CPU的速度和时钟频率3.RAM初始化包括正确地设置系统中内存控制器的功能寄存器以及各CPU外的内存(MemoryBank)的控制寄存器等。4.初始化LED典型地,通过GPIO来驱动LED,其目的是表明系统的状态是OK还是Error。若板子上无LED,也可通过初始化UART向串口打印BootLoader的Logo字符信息来完成这一点。5.关闭CPU内部指令/数据cache?Stage1:2、为stage2准备RAM空间为获得更快的执行速度,通常stage2被加载到RAM中执行因此必须为加载stage2准备好一段可用的RAM空间空间大小,应考虑stage2可执行映象的大小+堆栈空间因为stage2通常是C语言代码此外,最好对齐到memorypage大小(通常是4KB)的整数倍一般而言1MB已足够Stage1:为stage2准备RAM空间具体的地址范围可以任意安排比如blob将它的stage2可执行映像安排系统的RAM中0xc0200000开始的1M空间内值得推荐的是可以将stage2安排到整个RAM空间的最顶1MB也即(RamEnd-1MB)开始处假设空间大小:stage2_size(字节)起始和终止地址分别为:stage2_start和stage2_end(均与4字节对齐)则有:stage2_end=stage2_start+stage2_sizeStage1:为stage2准备RAM空间必须确保所安排的地址范围的确为可读写的RAM空间,即必须进行有效性测试Blob的内存有效性测试方法:记为test_mempage:以内存页为被测单位,测试每个页面头两个字是否可读写Stage1:为stage2准备RAM空间test_mempage保存被测页面头两个字的内容。向这两个字中写入任意的数字。比如:向第一个字写入0x55,第2个字写入0xaa。立即将这两个字的内容读回。应当与写入的内容一致,否则此页面地址范围不是一段有效的RAM空间再次向这两个字中写入任意的数字。比如:向第一个字写入0xaa,第2个字中写入0x55。立即将这两个字的内容读回。判断依据同3恢复这两个字的原始内容。测试结束后,为了得到一段干净的RAM空间范围,可以将所安排的RAM空间范围进行清零操作Stage1:3、拷贝stage2到RAM中拷贝时要确定:1.Stage2的可执行映象在固态存储设备的存放起始地址和终止地址2.RAM空间的起始地址Stage1:4、设置堆栈指针sp对C语言编写的程序应当准备运行堆栈通常设置在上述1MBRAM空间的最顶端sp=(stage2_end-4)注:堆栈是向下生长的此外,在设置堆栈指针前,也可关闭led灯,以提示用户即将跳转到stage2。系统的物理内存布局经过上述步骤后,系统的物理内存布局应该如下图所示Stage1:跳转到stage2的C入口点在上述一切都就绪后,就可以跳转到BootLoader的stage2去执行了。比如,在ARM系统中,这可以通过修改PC寄存器为合适的地址来实现关于C入口点的疑惑stage2的代码通常用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通C语言应用程序不同的是,在编译和链接bootloader这样的程序时,不能使用glibc库中的任何支持函数。其原因是显而易见的。???那么从哪里跳转进main()函数呢最直接的想法就是直接把main()函数的起始地址作为整个stage2执行映像的入口点?1.无法通过main()函数传递函数参数;2.无法处理main()函数返回的情况。一种更为巧妙的方法是利用trampoline(弹簧床)的概念。用汇编语言写一段trampoline小程序,并将它来作为stage2可执行映象的执行入口点。在trampoline中用CPU跳转指令跳入main()函数中去执行;当main()函数返回时,CPU执行路径显然再次回到trampoline程序。简而言之:用这段trampoli