嵌入式Linux系统启动过程

2019-07-12 15:18发布

嵌入式Linux系统启动过程



系统启动过程 


一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader), Linux 内核,文件系统,应用


程序。  


当系统首次引导时,或系统被重置时,处理器会执行一个位于Flash/ROM中的已知位置处的代码,Bootloader就是这第


一段代码。它主要用来初始化处理器及外设,然后调用 Linux 内核。Linux 内核在完成系统的初始化之后需要挂载某个


文件系统作为根文件系统(Root Filesystem),然后加载必要的内核模块,启动应用程序。这就是嵌入式Linux系统启


动过程 Linux 引导的整个过程。 


根文件系统是 Linux 系统的核心组成部分,它可以作为Linux 系统中文件和数据的存储区域,通常它还包括系统配置文


件和运行应用软件所需要的库。应用程序可以说是嵌入式系统的“灵魂”,它所实现的功能通常就是设计该嵌入式系统所要


达到的目标。如果没有应用程序的支持,任何硬件上设计精良的嵌入式系统都没有实用意义。 


从以上分析可以看出 Bootloader在运行过程中虽然具有初始化系统和执行用户输入的命令等作用,但它最根本的功能就


是为了启动 Linux 内核,让我们进一步分析 Bootloader 和 Linux 内核在嵌入式系统中的关系和作用。 


Bootloader  


1、Bootloader基本概述


Bootloader是嵌入式系统的引导加载程序,它是系统上电后运行的第一段程序,其作用类似于 PC 机上的 BIOS。


Bootloader是依赖于硬件而实现的,特别是在嵌入式领域,为嵌入式系统建立一个通用的Bootloader是很困难的,但为


了能达到启动Linux 内核的目的,所有的 Bootloader都必须具备以下功能: 


1) 初始化 RAM  


因为 Linux 内核一般都会在 RAM 中运行,所以在调用 Linux 内核之前 Bootloader 必须设置和初始化 RAM,为调用 


Linux内核做好准备。初始化 RAM 的任务包括设置 CPU 的控制寄存器参数,以便能正常使用 RAM 以及检测RAM 大


小等。 


2) 初始化串口端口 


在 Linux 的启动过程中有着非常重要的作用,它是 Linux内核和用户交互的方式之一。Linux 在启动过程中可以将信息


通过串口输出,这样便可清楚的了解 Linux 的启动过程。虽然它并不是 Bootloader 必须要完成的工作,但是通过串口


输出信息是调试 Bootloader 和Linux 内核的强有力的工具,所以一般的 Bootloader 都会在执行过程中初始化一个串


口作为调试端口。 


3) 检测处理器类型  


Bootloader在调用 Linux内核前必须检测系统的处理器类型,并将其保存到某个常量中提供给 Linux 内核。Linux 内核


在启动过程中会根据该处理器类型调用相应的初始化程序。 


4) 设置 Linux启动参数  


Bootloader在执行过程中必须设置和初始化 Linux 的内核启动参数。  


5) 调用 Linux内核映像  Bootloader完成的最后一项工作便是调用 Linux内核。如果 Linux 内核存放在 Flash 中,并且可直接在上面运行(这


里的 Flash 指 Nor Flash),那么可直接跳转到内核中去执行。但由于在 Flash 中执行代码会有种种限制,而且速度也


远不及 RAM 快,所以一般的嵌入式系统都是将 Linux内核拷贝到 RAM 中,然后跳转到 RAM 中去执行。 


2、Bootloader启动过程


嵌入式Linux系统通过Bootloader引导,一上电,就要执行Bootloader来初始化系统。在完成对系统的初始化任务之后,


它会将非易失性存储器(通常是 Flash或 DOC 等)中的Linux 内核拷贝到 RAM 中去,然后跳转到内核的第一条指


令处继续执行,从而启动 Linux 内核。Bootloader 和 Linux 内核有着密不可分的联系。 


Bootloader多数有两个阶段的启动过程: 


Stage1: ?


 基本的硬件初始化  ? 为加载stage2准备RAM空间  ? 拷贝内核映像和文件系统映像到RAM中  ? 设置堆栈指针sp  ?  跳到stage2的入口点 


Stage2: ?


 初始化本阶段要使用到的硬件设备  ?  检测系统的内存映射  ? 加载内核映像和文件系统映像  ? 设置内核的启动参数 


嵌入式系统中广泛采用的非易失性存储器通常是 Flash,而 Bootloader就位于该存储器的最前端,所以系统上电或复位


后执行的第一段程序便是 Bootloader。Bootloader在flash中的存储示意图如下: 


 


Bootloader启动流程图  


3、Bootloader 的启动方式


3.1网络启动方式 


这种方式的开发板不需要较大的存储介质,跟无盘工作站有点类似,但是使用这种启动方式之前,需要把Bootloader安


装到板上的EPROM或者Flash中。Bootloader通过以太网接口远程下载Linux内核映像或者文件系统。Bootloader下


载文件一般都使用TFTP网络协议,还可以通过DHCP的方式动态配置IP地址。 


3.2硬盘启动方式  


传统的Linux系统运行在台式机或者服务器上,这些计算机一般都使用BIOS引导,并使用磁盘作为存储介质。Linux传


统上是LILO (Linux Loader) 引导,后来又出现了GUN的软件 (Grand Unified Bootloader) 。 这两种Bootloader广泛


应用在X86的Linux系统上。 


3.3 Flash启动方式 


大多数嵌入式系统上都使用Flash存储介质。Flash有很多类型,包括NOR Flash、NAND Flash和其它半导体盘。它们


之间的不同在于: NOR Flash 支持芯片内执行(XIP, eXecute In Place),这样代码可以在Flash上直接执行而不必


拷贝到RAM中去执行。而NAND Flash并不支持XIP,所以要想执行 NAND Flash 上的代码,必须先将其拷贝到 RAM


中去,然后跳到 RAM 中去执行。NOR Flash 使用最为普遍。Bootloader一般放在Flash的底端或者顶端,这需要根据


处理器的复位向量来进行设置。可以配置成MTD设备来访问Flash分区。 


4、Bootloader种类


嵌入式Linux系统已经有各种各样的Bootloader,种类划分的方法也不是唯一的,一般可以按照它所支持处理器体系结


构不同进行划分,如下表: Bootloader


 Mointor描述 X86ARMPowerPC


LILO 否 Linux磁盘引导程序 是 否 否 Grub 否 GNU引导的LILO替代程序 是 否 否 


Loadlin 否 从DOS引导Linux 是 否 否 


ROLO 否 从ROM引导Linux而不需要BIOS是 否 否 


Etherboot 否 通过以太网启动Linux引导程序 是 否 否 


Linux BIOS否 完全替代BUIS的Linux引导程序 是 否 否 


Blob 否 LART等硬件平台的引导程序 否 是 否 


U-Boot 是 通用引导程序 是 是 是 


RedBoot 是 基于eCos的引导程序 是 是 是 常见嵌入式


Linux的Bootloader有:Blob、Redboot、U-Boot 


Linux内核的启动过程


Linux 内核有两种映像:一种是非压缩内核,叫 Image,另一种是它的压缩版本,叫zImage。根据内核映像的不同,Linux 


内核的启动在开始阶段也有所不同。ZImage 是 Image经过压缩形成的,所以它的大小比 Image 小。但为了能使用 


zImage,必须在它的开头加上解压缩的代码,将 ZImage 解压缩之后才能执行,因此它的执行速度比 Image 要慢。但


考虑到嵌入式系统的存储空容量一般比较小,采用 zImage 可以占用较少的存储空间,因此牺牲一点性能上的代价也是


值得的。所以一般的嵌入式系统均采用压缩内核的方式。 


在 Bootloader将 Linux 内核映像拷贝到 RAM 以后,解压内核映像和初始化,完成剩余的与硬件平台相关的初始化工


作,再进行一系列与内核相关的初始化后,调用第一个用户进程-init 进程并等待用户进程的执行,这样整个 Linux 内


核便启动完毕。在很多情况下,我们可以调用一个简单的 shell 脚本来启动必需的嵌入式应用程序。  ··········································································································································································································· u-boot介绍:

u-boot是一种普遍用于嵌入式系统中的Bootloader,Bootloader是在操作系统运行之前执行的一小段程序,通过它,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的软硬件环境,为最终调用操作系统内核做好准备。Boot Loader的主要运行任务就是将内核映象从硬盘上读到RAM中,然后跳转到内核的入口点去运行,即开始启动操作系统。系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的Boot Loader程序。

u-boot目录结构:

1、board中存放于开发板相关的配置文件,每一个开发板都以子文件夹的形式出现。
2、Commom文件夹实现u-boot行下支持的命令,每一个命令对应一个文件。
3、cpu中存放特定cpu架构相关的目录,每一款cpu架构都对应了一个子目录。
4、Doc是文档目录,有u-boot非常完善的文档。
5、Drivers中是u-boot支持的各种设备的驱动程序。
6、Fs是支持的文件系统,其中最常用的是JFFS2文件系统。
7、Include文件夹是u-boot使用的头文件,还有各种硬件平台支持的汇编文件,系统配置文件和文件系统支持的文件。
8、Net是与网络协议相关的代码,bootp协议、TFTP协议、NFS文件系统得实现。
9、Tooles是生成U-boot的工具。
其中比较重要的目录就是/board、/cpu、/drivers和 /include目录,如果想实现u-boot在一个平台上的移植,就要对这些目录进行深入的分析。 
u-boot的启动过程: 系统启动的入口点。既然我们现在要分析u-boot的启动过程,就必须先找到u-boot最先实现的是哪些代码,最先完成的是哪些任务。另一方面一个可执行的image必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在/board /lpc2210/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu /arm7tdmi/start.o文件。因为我们用的是ARM7TDMI的cpu架构,在复位后从地址0x00000000取它的第一条指令,所以我们将Flash映射到这个地址上,这样在系统加电后,cpu将首先执行u-boot程序。 u-boot的启动过程是多阶段实现的,分了两个阶段: 第一阶段是用汇编写的,主要任务是:
1、CPU 自身初始化:包括MMU,Cache,时钟系统,SDRAM 控制器等的初始化
2、重定位:把自己从非易失性存储器搬移到 RAM 中
3、分配堆栈空间,设置堆栈指针
4、清零 BSS 数据段
5、跳转到第二阶段入口函数 start_armboot()
第二阶段是用C写的,主要任务是: 1、为 U-boot 内部私有数据分配存储空间,并清零
2、依次调用函数指针数组 init_sequence 中定义的函数进行一系列的初始化
3、如果系统支持 NOR Flash,调用flash_init ()和display_flash_config ()初始化并显示检测到的器件信息
4、如果系统支持 LCD 或VFD,调用lcd_setmem()或vfd_setmem()计算帧缓(Framebuffer)大小,然后在BSS 数据段之后为Framebuffer 分配空间,初始化gd->fb_base 为Framebuffer 的起始地址
5、调用 mem_malloc_init()进行存储分配系统(类似于C 语言中的堆)的初始化和空间分配
6、如果系统支持 NAND Flash,调用nand_init ()进行初始化
7、如果系统支持 DataFlash,调用AT91F_DataflashInit()和dataflash_print_info()进行初始化并显示检测到的器件信息
8、调用 env_relocate ()进行环境变量的重定位,即从Flash 中搬移到RAM 中
9、如果系统支持 VFD,调用drv_vfd_init()进行VFD 设备初始化
10、从 环 境 变 量 中 读 取 IP 地址和MAC 地址, 初始化gd->bd-> bi_ip_addr 和gd->bd->bi_enetaddr
11、调用 jumptable_init ()进行跳转表初始化,跳转表在global_data 中,具体用途尚不清楚
12、调用 console_init_r()进行控制台初始化
13、如果需要,调用 misc_init_r ()进行杂项初始化
14、调用 enable_interrupts ()打开中断
15、如果需要,调用board_late_init()进行单板后期初始化,对于AT91SAM9260EK,主要是以太网初始化
16、进入主循环:根据用户的选择启动 linux,或者进入命令循环执行用户输入的命令
这部分是一些相对变化不大的部分,我们针对不同的板子改变它调用的一些初始化函数,并且通过设置一些宏定义来改变初始化的流程,所以这些代码在移植的过程中并不需要修改,也是错误相对较少出现的文件。在文件的开始先是定义了一个函数指针数组,通过这个数组,程序通过一个循环来按顺序进行常规的初始化,并在其后通过一些宏定义来初始化一些特定的设备。在最后程序进入一个循环,main_loop。这个循环接收用户输入的命令,以设置参数或者进行启动引导。 ······································································································································································································································