基于x86的Redboot启动流程分析

Redboot是Redhat公司随eCos发布的一个BOOT方案,是一个开源项目。

当前Redboot的最新版本是Redboot-2.0.1,Redhat公司将会继续支持该项目
Redboot支持的处理器构架有ARM,MIPS,MN10300,PowerPC, Renesas SHx,v850,x86等,是一个完善的嵌入式系统Boot Loader。

Redboot是在ECOS的基础上剥离出来的,继承了ECOS的简洁、轻巧、可灵活配置、稳定可靠等品质优点。它可以使用X-modem或Y- modem协议经由串口下载,也可以经由以太网口通过BOOTP/DHCP服务获得IP参数,使用TFTP方式下载程序映像文件,常用于调试支持和系统初 始化(Flash下载更新和网络启动)。Redboot可以通过串口和以太网口与GDB进行通信,调试应用程序,甚至能中断被GDB运行的应用程序。 Redboot为管理FLASH映像,映像下载,Redboot配置以及其他如串口、以太网口提供了一个交互式命令行接口,自动启动后,REDBOOT用 来从TFTP服务器或者从Flash下载映像文件加载系统的引导脚本文件保存在Flash上。当前支持单板机的移植版特性有:

-          支持ECOS,Linux操作系统引导

-          在线读写Flash

-          支持串行口kermit,S-record下载代码

-          监控(minitor)命令集:读写I/O,内存,寄存器、 内存、外设测试功能等

Redboot是标准的嵌入式调试和引导解决方案,支持几乎所有的处理器构架以及大量的外围硬件接口,并且还在不断地完善过程中。

 

 

现在将通过阅读代码,看看redboot是如何启动的,这是每个系统执行的第一步,也是不可缺少的一步。这部分会分几篇完成,这是第一部分,主要是一个概要介绍。

由于系统启动跟硬件的紧密关系,所以在不同的硬件平台下,这部分都会有相应不同的处理。

下面这幅图来自《EMBEDDED SOFTWARE DEVELOPMENT WITH ECOS》书中,介绍了一款PowerPC的设备启动流程。

而这里,仍将介绍PC下的启动流程,通过阅读代码可以了解其具体的执行步骤,也可以通过修改代码重新编译、运行前面介绍的实验,查看结果。在阅读代码的过程中,最好结合实验,这样能够证实系统是否象我们理解的那样运行。

在今后,会进一步分析eCos操作系统的其他部分代码结构,比如:调度,驱动等等。
也会对其他不同体系架构(如 mips arm)下的代码进行分析阅读。

在Redboot最先执行的文件通常命名为 vectors.S

因为我们是i386架构下,所以可以在下面目录下找到相应文件:
package/hal/i386/arch/v2_0/src/vectors.S
 
 程序执行的入口是 _start
_start 处开始执行,至   call cyg_start 完成启动部分。
在函数 cyg_start 中,将进入 Redboot的执行流程。

这并不是很长的一段代码,却完成了整个PC的启动和初始化的部分。其中相当部分代码为汇编,所以要阅读这些代码必须首先对i386体系结构有基本的了解。

虽然 Redboot的启动过程已经相对简单,它没有使用分页机制,也没有区分内核态和用户态。
所有的代码都被放在一个段中统一编址。只使用了两个段(selector)一个为code,另一个为data。这个对于redboot的size(大概在100k左右吧)已经足够了。

如果对于一个操作系统来讲,这是一个相当程度简化的操作系统。即便如此,启动过程也不是一目了然的清晰。所以,这里仍然会忽略掉一些不太必要的内容,比如对SMP,FPU,GDB的支持等等。

整个启动过程中大概分为以下几个部分:

1. boot-loader: 这部分的工作就是将代码加载(load)到内存中,对于不同的设备,代码会装在不同的地方,通常PC会放在硬盘里面,某些嵌入式设备会把代码放在 ROM(一般是flash)里面,而这里是放在软盘里面。boot-loader就是把软盘的代码load至内存,然后运行它们。

2. 进入保护模式,对于i386体系架构的cpu上电后缺省时实模式,内存访问空间也只有1M,这只是为了跟最初的8086兼容,所以要进入保护模式。

3. 初始化中断向量和异常。

4. 初始化基本的输入输出设备,如显示器,键盘,串行口等等。

5. 其他还有一些零散的初始化工作。

下面开始将按运行次序对启动过程进行介绍。

 

_start 之后的第一行是一个宏 hal_cpu_init , 该宏定义在package/hal/i386/pcmb/v2_0/include/pcmb.inc 文件中。   由于从软盘引导,所以 hal_cpu_init分为两部分: 一部分是引导分区部分(以0xAA55结束),另一部分的功能是使CPU进入保护模式。

解释:PC上电后会进入BIOS, BIOS会检测磁盘(软盘和硬盘),如果软盘(或硬盘)的第一个分区是引导分区(以 0xAA55结束为标志)则将软盘的第一个分区内容加载到内存 0x7c00处,并运行它。

引导分区被运行之后,将整个redboot逐个分区地加载到0x3000为起始地址的内存空间。

注意:这个过程,引导分区内容会再次被加载到0x3000处,且马上会跳转到0x3000处执行引导分区的代码。而0x7c00处的内容会在后面被覆盖掉。

在执行加载工作前,会先执行两个小任务:
   1. 设置栈,栈顶为0x3000
   2. 通过BIOS提供的中断,得到扩展内存和标准内存大小,压入栈中

引导分区代码结束后,程序将进入保护模式。大概次序如下:
  1. 关中断
  2. 初始化GDT和IDT
  3. 进入保护模式
  4. 设置数据段
  5. 重新设置栈段(因为保护模式和实模式,内存编址方式不同)

GDT主要用两个Selector分别是 0x08 (code) 和 0x10 (data)。

寻址空间都是从0x00000000 - 0xFFFFFFFF

特权级(DPL)为 0

IDT的地址为:0x1000

这部分比较复杂,这里不在详细说明,如果有时间,会在讨论i386架构的文章里,详细说明。而有关这部分的资料也相当丰富,可以在网上搜寻。

到这里hal_cpu_init 已经执行结束了 ,不过进入保护模式还需要 激活A20地址线 。不激活A20地址线只能访问1M空间内的内存,激活A20才能访问所有内存。这个任务在另外一个宏 hal_memc_init 中实现,它位于文件 packages/hal/i386/arch/v2_0/include/arch.inc

这样,就完成了加载和进入保护模式的任务。

 

进入保护模式之后做以下工作:

  •  设置中断栈空间,在之前栈定义在小于0x3000的内存空间中。通过
     movl $__interrupt_stack, %esp
     将栈重新设置在__interrupt_stack处,并分配了4096kB的栈空间。
     栈大小也可以通过改变cdl文件进行调整。

 

  • hal_idt_init(packages/hal/i386/pc/v2_0/include/platform.inc) 
      设置idt向量, 回头会在介绍中断的文章中介绍这部分。
  • BSS段初始化(清零)
    解释: BSS(Block Started by Symbol)用来存放程序中未初始化的全局变量的一块內存区域,属于静态內存分配。

 

  • hal_platform_init(packages/hal/i386/pc/v2_0/src/plf_misc.c)
      这部分是内容比较多,也比较重要。主要为 初始化 中断,异常 以及虚拟向量表。
      初始化虚拟向量表的工作通过调用函数 hal_if_init 来完成
    在eCos中设置了3张向量表,idt, hal_vsr_table, hal_virtual_vector_table
    内存地址如下:
    idtStart = 0x00001000;
    hal_vsr_table = 0x00001800;
    hal_virtual_vector_table = 0x00001c00;

idt 是硬件实际设置的中断区域,当中断发生后进入调用idt的中断向量。而eCos将idt的中断向量有指向 hal_vsr_table
(猜测是为了隔离不同的cpu中断机制不同,专门设置了hal_vsr_table,使其中断处理方式一致)

在 hal_platform_init 函数中,初始化所有中断(和异常),设定为缺省的中断处理例程,以防止有中断意外发生,造成程序进入未知状态。

有关中断,异常,虚拟向量等相关处理机制内容比较多,会在以后的文章具体介绍。

  • cyg_hal_invoke_constructors

这个函数主要执行相关的构造函数,因为ecos一部分代码是用c++写的,所以有些类具有构造函数,需要在首先执行,这里面做这部分的处理。

 

  • cyg_start
    最后则进入主函数 cyg_start, 这个函数中,首先执行外围设备的初始化,之后就开始轮询处理用户输入的命令。

你可能感兴趣的:(基于x86的Redboot启动流程分析)