嵌入式入门——BootLoader设计 2

三. Boot Loader 的基本架构

   Boot Loader 的启动过程分为单阶段(Single Stage)和多阶段(Multi-Stage)两种,其中,多阶段的 Boot Loader能提供更为复杂的功能,以及具有更好的可移植性。
   Boot Loader的生命周期如下:

  1. 初始化硬件,如设置UART(至少设置一个),检测存储器等
  2. 设置启动参数,告诉内核硬件的信息,如用哪个启动界面,波特率.
  3. 跳转到操作系统的首地址.
  4. 消亡

   从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,其启动过程可以分为 stage 1stage 2 两部分 :

  1. stage 1:主要语言:汇编。实现:简单的硬件初始化。
  2. stage 2:主要语言:C语言 。实现:复制数据、设置启动参数、串口通信等功能。
    嵌入式入门——BootLoader设计 2_第1张图片
Boot Loader 的主要任务:

如果我们假定内核映像与根文件系统映像都被加载到 RAM 中运行的话:

  1. stage1 通常包括以下步骤:
    硬件设备初始化
    为加载 Boot Loader 的 stage2 准备 RAM 空间
    拷贝 Boot Loader 的 stage2 到 RAM 空间中
    设置好堆栈
    跳转到 stage2 的 C 入口点
  2. stage2 通常包括以下步骤:
    初始化本阶段要使用到的硬件设备
    检测系统内存映射(memory map)
    将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中
    为内核设置启动参数
    调用内核

stage1:

1. 基本的硬件初始化

目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境 :

  1. 屏蔽所有的中断
    为中断提供服务通常是 OS 设备驱动程序的责任,Boot Loader 的执行全过程中可以不必响应任何中断
    中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器(如 ARM 的 CPSR 寄存器)来完成
  2. 设置 CPU 的速度和时钟频率。
  3. RAM 初始化
    包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。
  4. 初始化 LED
    通过 GPIO 来驱动 LED,其目的是表明系统的状态是 OK 还是 Error
    如板子上没有LED,那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息
  5. 关闭 CPU 内部指令/数据 cache
2. 为加载 stage2 准备 RAM 空间
  1. 通常把 stage2 加载到 RAM 空间中来执行 。
  2. stage2 通常是 C 语言执行代码,考虑堆栈空间。
  3. 空间大小最好是 memory page 大小(通常是 4KB)的倍数。
  4. 一般1M RAM 空间已经足够,地址范围可以任意安排。
    如 blob 就将 stage2 可执行映像从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行
    将 stage2 安排到 RAM 空间的最顶 1MB也是一种值得推荐的方法
    stage2_end=stage2_start+stage2_size
  5. 对所安排的地址范围进行测试。
    必须确保所安排的地址范围可读写的 RAM 空间
    测试方法可以采用类似于 blob 的方法
    以 memory page 为被测试单位,测试每个 page 开始的两个字是否是可读写的
3. 拷贝 stage2 到 RAM 中
  1. 拷贝时要确定两点
    (1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址
    (2) RAM 空间的起始地址。
4. 设置堆栈指针 sp
  1. 通常把 sp 的值设置为(stage2_end-4)
    1MB 的 RAM 空间的最顶端(堆栈向下生长)
  2. 在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2
5. 跳转到 stage2 的 C 入口点
  1. 可以跳转到 Boot Loader 的 stage2 去执行
  2. 如在 ARM系统中,这可以通过修改 PC 寄存器为合适的地址来实现

stage2:

1.初始化本阶段要使用的硬件设备
  1. 初始化至少一个串口,以便终端用户进行 I/O 输出信息
    在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入main() 函数执行
    设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等
  2. 初始化计时器等
  3. 使用Volatile
    避免编译器优化
    编译器为了提高程序运行速度,可能对变量进行优化,会将其放在缓存中
    缺陷是变量的内容可能被误改,从而与内存中实际值不一致
    采用volatile 关键字可以避免上述问题
2.检测系统的内存映射(memory map)
  1. 在 4GB 物理地址空间中哪些地址范围被分配用来寻址系统的RAM 单元
    如SA-1100 中,从 0xC0000000 开始的 512M空间被用作系统的 RAM 空间
    在Samsung S3C44B0X 中,从0x0c00,0000 到 0x1000,0000之间的 64M 地址空间被用作系统的 RAM 地址空间
  2. 嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态
  3. Boot Loader 的 stage2 必须检测整个系统的内存映射情况
    必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 “unused” 状态的
检测流程

嵌入式入门——BootLoader设计 2_第2张图片

3.加载内核映像和根文件系统映像
  1. 规划内存占用的布局
    内核映像所占用的内存范围
    根文件系统所占用的内存范围
  2. 从 Flash 上拷贝内核映像到RAM上
4.设置内核的启动参数
  1. Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数
  2. 启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束
  3. 每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成
  4. 在嵌入式 Linux 系统中,通常需要由 Boot Loader 设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等
5.调用内核
  1. 直接跳转到内核的第一条指令处
  2. 在跳转时,下列条件要满足
    1. CPU 寄存器的设置
    R0=0;@R1=机器类型 ID;@R2=启动参数标记列表在 RAM 中起始基地址
    2. CPU 模式
    必须禁止中断(IRQs和FIQs);
    CPU 必须 SVC 模式;
    3. Cache 和 MMU 的设置
    MMU 必须关闭;
    指令 Cache 可以打开也可以关闭;
    数据 Cache 必须关闭
6.串口终端的调试
  1. 调试手段: 打印信息到串口终端
  2. 串口终端显示乱码或根本没有显示
    boot loader 对串口的初始化设置不正确
    运行在 host 端的终端仿真程序对串口的设置不正确,这包括:波特率、奇偶校验、数据位和停止位等方面的设置
  3. Bootloader的运行过程中可以正确向串口输出信息,但bootloader 启动内核后无法看到内核启动输出信息
    确认是否在编译内核时配置了对串口的支持
    确认bootloader对串口的设置与内核对串口的设置一样
    确认bootloader所用的内核基地址与内核映像在编译时所用的运行基地址一致

你可能感兴趣的:(嵌入式入门——BootLoader设计 2)