linux系统从上电到系统运行起来(二)
运行嵌入式linux的系统,一般都由引导程序、内核以及根文件系统等几部分组成,一般都存放在flash闪存这样的存储介质上,各映像在存储介质上的逻辑分区如下:
1、这些映像文件可能并不存在于同一存储介质上
2、这些映像文件可能并不是以完全独立的文件存在,有的系统将这些功能的文件重新打包,生成一个整体的映像文件。即使如此,但是从逻辑上,还是能够划分为这些功能模块
引导程序用于加载并允许内核,可通过参数控制内核的运行;linux内核在启动过程中会寻找并加载根文件系统,加载成功则进入linux shell,运行用户程序
linux BootLoader
系统上电后,需要一段程序来进行初始化:关闭看门狗、设置系统时钟、初始化存储器、将更多的代码复制到内存等,这段程序就成为BootLoader,简单的说它就是一小段程序,在系统上电时开始执行,初始化硬件设备、准备好软件环境,最后调用操作系统内核。
BootLoader的实现非常依赖于具体硬件,在嵌入式系统中硬件配置差别很大,即使是相同的cpu,它的外设也可能不同,所以无法找到一个通用的BootLoader来支持所有的设备。
CPU上电后,会从某地地址开始执行。比如MIPS架构的cpu会从0xbfc00000取第一条指令,而ARM架构的cpu则从地址0x00000000开始。嵌入式系统中,需要把存储器件ROM或Flash等映射到这个地址,BootLoader就存放在这个地址开始处,这样一上电就可以开始执行。
BootLoader可以分为两种操作模式
1、启动加载模式
上电后,BootLoader从板子上的某个固态存储设备上将操作系统加载到RAM中运行
2、下载模式
在这种模式下,开发人员可以使用各种命令,通过串口或者网络方式从主机下载文件(比如内核映像、文件系统映像等),将它们直接放在内存运行或烧入Flash类固态存储设备中
BootLoader可以分为两个阶段
BootLoader的启动过程可以分为单阶段、多阶段两种。通常多阶段的BootLoader能提供更复杂的功能和更好的可移植性。从固态存储设备上启动的BootLoader大多都是两阶段的启动过程。第一阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化,并调用第二阶段的代码;第二阶段通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
这两个阶段完成的功能可以如下分类
1、BootLoader的第一阶段功能
1.1 硬件设备初始化
1.2 为加载BootLoader的第二阶段代码准备RAM空间
1.3 复制BootLoader的第二阶段代码到RAM空间中
1.4 设置好栈
1.5 跳转到第二阶段代码的C入口点
2、BootLoader的第二阶段功能
2.1 初始化本阶段需要使用到的硬件设备
为了方便开发,至少要初始化一个串口以便调试
2.2 检测系统内存
内存检测就是确认板子上使用了多少内存、它们的地址空间是什么、
2.3 将内存映像和根文件系统映像从Flash上读到RAM空间中
Flash上的内核映像有可能是经过压缩的,在读到RAM之后,还需要进行解压
对于有自解压功能的内核,不需要BootLoader来解压
将根文件系统映像复制到RAM中,这不是必须的,这取决于是什么类型的根文件系统以及内核访问它的方式
2.4 为内核设置启动参数
2.5 调用内核
将内核存放在适当的位置后,直接跳到它的入口即可调用内核
U-Boot
简介
U-BOOT全称Universal Boot Loader,即通用BootLoader,是遵循GPL条款的开放源代码项目。它的通用有两层含义:可以引导多种操作系统、支持多种架构的CPU。支持的操作系统如下:Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS等。支持的架构如下:PowerPC、MIPS、ARM、x86等。
U-Boot具有如下特性:
1、开放源码
2、支持多种嵌入式操作系统内核
3、支持多个处理器系列
4、较高的可靠性和稳定性
5、高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等
6、丰富的设备驱动源码,如串口、以太网、SDRAM、Flash、LCD、NVRAM、EEPROM、RTC等
7、较为丰富的开发调试文档和强大的网络技术支持
8、支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统
9、支持NFS挂载、从Flash中引导压缩或非压缩系统内核
10、可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤其对于LINUX支持最为完善
11、支持目标板环境变量多种存储方式,如Flash、NVRAM、EEPROM
12、CRC32校验,可校验Flash中内核、RAMDISK镜像文件是否完好
13、上电自检功能:SDRAM、Flash大小自动检测,SDRAM故障检测,CPU型号
14、特殊功能:XIP内核引导
U-Boot的启动简介
U-Boot 的启动可以分为第一个阶段和第二个阶段。第一阶段的代码主要用汇编语言写成,主要工作是初始化部分硬件(初始化内存、调试串口等)、搬移代码(从非易失储存器卡中把 U-boot 复制到 SDRAM)、准备 C 代码的执行环境;第二阶段代码主要用 C 语言写成,主要任务是初始化硬件、环境变量、部份驱动等,最后进入命令提示符。
1、第一阶段
对于所有的处理器来说,U-boot的最初启动代码都是cpu目录下。第一阶段的入口在相应的cpu架构内的start.S文件,启动流程如下:
start.S 首先要初始化部分硬件,主要是要初始化 RAM,为下一步“复制第二阶段的代
码到 RAM”做好准备。
U-Boot 第二阶段的代码必须要在 RAM 中执行,所以在第一阶段必须要把第二阶段的代
码复制到 RAM 中。对不同的开发板而言, U-Boot 有各种不同的安装方式:有时安装在 Nor
Flash、有的安装在 NAND Flash、有的安装在 SD/TF 卡等。
2、第二阶段
当第一阶段启动完成后,就会启动 start_armboot()函数,进入第二阶段的启动。start_armboot()函数实现在 lib_arm/board.c 文件。 start_armboot()函数的执行流程如图
U-Boot 的环境变量可以保存在 Nor Flash、 NAND Flash、 TF/SD、 EEPROM 等设备中。
U-Boot 在启动时会根据预先的设定在指定的设备的指定位置读取环境变量。如果环境变量不存在, U-Boot 则使用默认的环境。U-Boot 的 start_armboot()函数在最后会执行 main_loop()函数。在 main_loop()函数中,会根据用户的响应而决定进入 U-Boot 命令行终端或根据 bootcmd 环境变量启动内核。
不同平台的kernel启动时,最开始部分的汇编脚本会有些不一样,但是从汇编跳转到C语言的代码过程中的第一条命令都是start_kernel函数,比如arm平台,它汇编代码的最后一个跳转是“b start_kernel” (linux-3.14/arch/arm/kernel/head-common.S),然后执行start_kernel函数(linux-3.14/init/main.c),它负责进行kernel正式运行前各个功能的初始化:打印一些信息、内核工作需要的模块的初始化被依次调用(例如内存你管理、调度系统、异常处理等),最后调用rest_init函数启动了三个进程(idel、kernel_init、kthreadd)来开启操作系统的正式运行,内核启动如下图
idel是操作系统的空闲任务,当cpu空闲时候会运行的任务,即0号进程
kernel_init最开始只是一个函数,这个函数作为进程被启动,但是最后将读取根文件系统下的init程序,这个操作将完成内核态到用户态的转变,而这个init进程是所有用户态进程的父进程,它来产生其他的子进程,所以init进程将永远存在,它就是1号进程(其实它就是busybox)
kthreadd是内核守护进程,即2号进程, 负责所有内核线程的调度和管理
u-boot从flash分区中读取linux内核到内存中,然后跳转到内存地址执行内核。Linux内核会进行一系列验证,注册相关驱动,根据分区表创建分区,然后挂载根文件系统,启动第一个用户空间进程。原生的Linux内核默认启动的第一个用户进程是/sbin/init,openwrt将其修改为默认启动的第一个进程为/etc/preinit。