摘要:嵌入式系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令执行。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000 取它的第一条指令执行;而基于 CPU 构建的嵌入式系统通常都有某种
嵌入式系统上电后,首先执行引导加载程序(Bootloader),然后加载和启动内核。下面以嵌入式 Linux 系统为例,简单讲解嵌入式系统启动流程。
1 固态存储设备的典型空间分配
嵌入式系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令执行。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000 取它的第一条指令执行;而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。
一般来说,基于 CPU 构建的嵌入式系统中的固态存储设备的典型空间分配如下图所示:
固态存储设备(一般为 Flash)存储的内容,按地址从低到高分别是:Bootloader、启动参数、内核映像和根文件系统。因此,在系统上电或复位后,CPU 将首先执行 Bootloader 程序。
2 引导加载程序(Bootloader)阶段
一般来说,Bootloader 阶段又分为 Stage1 阶段和 Stage2 阶段,Stage1 使用汇编语言编写,Stage2 使用 C 语言编写。
2.1 Stage1
Stage1 阶段主要完成以下任务:
硬件设备初始化(屏蔽所有的中断、设置 CPU 的速度和时钟频率、RAM 初始化等)。
为加载 Boot Loader 的 Stage2 准备 RAM 空间。 由于 Stage2 通常是 C 语言执行代码,因此这里的 RAM 空间不仅包括 Stage2 可执行映象所需的空间,还包括 Stage2 C 语言执行所需要的堆栈空间。
拷贝 Boot Loader 的 Stage2 到 RAM 空间中。
设置好堆栈。由于 Stage2 通常是 C 语言执行代码,因此必须初始化堆栈以供 C 语言代码执行(比如初始化堆栈指针)。
跳转到 stage2 的 C 入口点。
经过上述这些执行步骤后,系统的物理内存布局应该如下图所示:
2.2 Stage2
在 Stage1 完成后,就可以跳转到 Bootloader 的 Stage2 去执行了。比如,在 ARM 系统中,可以通过修改 PC 寄存器为合适的地址来实现。
Stage2 阶段主要完成以下任务:
初始化本阶段要使用到的硬件设备。这通常包括:(1)初始化至少一个串口,以便和终端用户进行 I/O 输出信息;(2)初始化计时器等。
检测系统内存映射(memory map)。所谓内存映射就是指在整个 4GB(32位系统)地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如,在 SA-1100 CPU 中,从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间,而在 Samsung S3C44B0X CPU 中,从 0x0c00,0000 到 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说,具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态。由于上述这个事实,Boot loader 的 stage2 必须在它想干点什么 (比如,将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 "unused" 状态的。
将内核(kernel)从 flash 上读到 RAM 空间中。
为内核设置启动参数。在将内核映像和根文件系统映像拷贝到 RAM 空间中后,就可以准备启动 Linux 内核了。但是在调用内核之前,应该作一步准备工作,即设置 Linux 内核的启动参数(比如启动参数 ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD 等)。
调用内核。
3 Linux 内核启动
内核的启动过程也一般分为三个阶段:内核自解压阶段、Linux 内核启动准备阶段和 Linux 内核初始化阶段。
3.1 内核自解压阶段
Bootloader 最后一步将 Linux 内核调入内存中并调用 do_bootm 函数启动内核,跳转至 kernel(内核)的起始位置。如果内核没有被压缩,则直接启动;如果内核被压缩过,则需要进行解压,被压缩过的kernel头部有解压程序。
3.2 Linux内核启动准备阶段
内核启动准备主要做了以下工作:
设置页表建立MMU映射表、使能内存管理单元(MMU)。此时可以使用虚拟地址了。
复制数据段、清除 BSS 段,调用 start_kernel 函数。
3.3 内核相关的初始化
内核相关的初始化完成了以下任务:
来源:小熊科技论