浅析嵌入式系统 bootloader
对于计算机系统来说,从开机上电到操作系统启动需要一个引导过程。嵌入式Linux系统同样离不开引导程序,这个引导程序就叫作Bootloader。
BootLoader
在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。
Bootloader 的作用
bootloader 就是在系统上电或复位后运行的一段小程序。这段程序将系统的软硬件环境带到一个合适的状态,为最终调用应用程序准备好正确的环境。
主要功能
- 完成处理器和周边电路正常运行所需要的初始化工作
- 建立内存空间的映射
- 将系统的软硬件环境带到一个合适状态
- 从 ROM、Flash 等非易失存储器或网络上加载操作系统映像文件(或者用户应用程序映像)
- 引导操作系统或执行用户应用程序
BootLoader 的“生命周期”
BootLoader的生命周期就是指:BootLoader什么时候开始运行,什么时候结束运行。
-
BootLoader本质上是一个裸机程序(不是操作系统),一旦BootLoader开始SoC就会单纯运行BootLoader(意思就是BootLoader运行的时候别的程序是不可能同时运行的),一旦BootLoader结束运行则无法再回到BootLoader(所以BootLoader启动了内核后BootLoader自己本身就死了,要想再次看到BootLoader界面只能重启系统。重启并不是复活了刚才的BootLoader,重启只是BootLoader的另一生)
-
BootLoader的入口和出口。BootLoader的入口就是开机自动启动,BootLoader的唯一出口就是启动内核。BootLoader还可以执行很多别的任务(譬如烧录系统),但是其他任务执行完后都可以回到BootLoader的命令行继续执行BootLoader命令,而启动内核命令一旦执行就回不来了。
总结:一切都是为了启动内核。
BootLoader 必须解决哪些问题?
1. 自身可开机直接启动
-
一般的SoC都支持多种启动方式,譬如SD卡启动、NorFlash启动、NandFlash启动等,BootLoader要能够开机启动,必须根据具体的SoC的启动设计来设计BootLoader。
-
BootLoader必须进行和硬件相对应的代码级别的更改和移植,才能够保证可以从相应的启动介质启动。BootLoader中第一阶段的start.S文件中具体处理了这一块。
2. 能够引导操作系统内核启动并给内核传参
-
BootLoader的终极目标就是启动内核。
-
linux内核在设计的时候,设计为可以被传参。也就是说我们可以在BootLoader中事先给linux内核准备一些启动参数放在内存中特定位置然后传给内核,内核启动后会到这个特定位置去取BootLoader传给他的参数,然后在内核中解析这些参数,这些参数将被用来指导linux内核的启动过程。
3. 能提供系统部署功能
-
BootLoader必须能够被人借助而完成整个系统(包括BootLoader、kernel、rootfs等的镜像)在Flash上的烧录下载工作。
-
裸机教程中刷机(ARM裸机第三部分)就是利用BootLoader中的fastboot功能将各种镜像烧录到iNand中,然后从iNand启动。
4. 能进行soc级和板级硬件管理
-
BootLoader中实现了一部分硬件的控制能力(BootLoader中初始化了一部分硬件),因为BootLoader为了完成一些任务必须让这些硬件工作。譬如BootLoader要实现刷机必须能驱动iNand,譬如BootLoader要在刷机时LCD上显示进度条就必须能驱动LCD,譬如BootLoader能够通过串口提供操作界面就必须驱动串口。譬如BootLoader要实现网络功能就必须驱动网卡芯片。
-
SoC级(譬如串口)就是SoC内部外设,板级就是SoC外面开发板上面的硬件(譬如网卡、iNand)
Bootloader 在嵌入式系统中的角色
- Bootloader 是应用程序运行前执行的一段固化在微控制器中的代码程序。
- Bootloader 是底层硬件与上层应用软件之间的一个中间接口软件。
- BootLoader 独立于用户应用程序,可以被编译、连接并下载到 ECU 中,不能与用户应用程序同时运行。
- Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式系统中。
- 在嵌入式系统中难以建立一个通用的 Boot Loader。
在专用的嵌入式板子运行GNU/Linux系统已经变得越来越流行。一个嵌入式Linux系统从软件的角度看通常可以分为四个层次:
- 引导加载程序。包括固化在固件(firmware)中的boot代码(可选),和BootLoader两大部分。
- Linux内核。特定于嵌入式板子的定制内核以及内核的启动参数 。
- 文件系统。包括根文件系统和建立于Flash内存设备之上文件系统。通常用ramdisk来作为rootfs。
- 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式GUI有:MicroWindows和MiniGUI等。
操作模式
大多数Bootloader都包含两种不同的操作模式:
-
启动加载模式
在这种模式下,Bootloader从目标机的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。这种模式是Bootloader的正常工作模式,因此在嵌入式产品发布时,Bootloader必须工作在这种模式下。把控制权交给用户应用程序。
-
下载模式
在这种模式下,目标机上的Bootloader将通过串口或网络等通信手段从开发主机(Host)上下载内核映像和根文件系统映像等到RAM中,然后可再被Bootloader写到目标机上的固态存储媒质中,或者直接进行系统的引导。
- 启动加载模式通常用于第一次烧写内核与根文件系统到固态存储媒质时或者以后的系统更新时使用;
- 下载模式多用于开发人员在前期开发的过程中,工作于这种模式下的Bootloader通常都会向它的终端用户提供一个简单的命令行接口。
启动过程
Bootloader启动大多数都分为两个阶段。
第一阶段主要包含依赖于CPU的体系结构硬件初始化的代码,通常都用汇编语言来实现。
这个阶段的任务有:
- 基本的硬件设备初始化(屏蔽所有的中断、关闭处理器内部指令/数据 Cache 等)。
- 为第二阶段准备 RAM 空间。
- 如果是从某个固态存储媒质中,则复制Bootloader的第二阶段代码到 RAM 。
- 设置堆栈。
在第一阶段中为什么要关闭Cache?通常使用Cache以及写缓冲是为了提高系统性能,但由于Cache的使用可能改变访问主存的数量、类型和时间,因此Bootloader通常是不需要的。
跳转到第二阶段的C程序入口点 。
第二阶段通常用C语言完成,以便实现更复杂的功能,也使程序有更好的可读性和可移植性。
这个阶段的任务有:
- 初始化本阶段要使用到的硬件设备。
- 检测系统内存映射。
- 将内核映像和根文件系统映像从Flash读到RAM。
- 为内核设置启动参数。
- 调用内核。