嵌入式Linux系统从软件角度通常可以分为以下4个层次:
引导加载程序 | Linux内核 | 文件系统 | 用户应用程序
嵌入式Linux系统中典型分区结构:
正常启动过程中,Bootloader首先运行,然后它将内核复制到内核中,并且在内存某个固定地址设置好要传递给内核的参数,最后运行内核。内核启动之后,它会挂接根文件系统,启动文件系统中的应用程序。
一、Bootloader的作用:
CPU上电后,会从某个地址开始执行,对于ARM结构的CPU是从0x00000000开始,Bootloader就存放在这个地址的开始处。这样一上电就可以执行。Bootloader主要用于初始化最基本的硬件、准备好软件环境和加载Linux操作系统。
在Linux内核启动时,除了内核映像必须在主存的适当位置外,CPU还必须满足以下条件:
CPU寄存器的设置
1、R0 = 0
2、R1 = Machine ID
3、R2 = 内核启动参数在RAM中的起始基地址
U-boot最终是调用theKernel函数来跳转执行linux内核的,uboot调用此函数(即linux内核)时会直接传递给linux内核3个参数,而这3个参数就是通过寄存器来实现传参的。
其中第1个参数固定为0,就放在r0寄存器中,第二个参数为机器类型ID即机器码,放在r1寄存器,第3个参数就是启动参数标记列表在RAM中的首地址,就放在r2寄存器中。
CPU模式
1、必须禁止中断(IRQs 和 FIQs,中断请求和快速中断请求)
2、CPU必须是SVC模式(特权模式)
为何要禁止中断?---因为U-Boot只是完成硬件初始化、环境参数设置、代码搬运等工作,用不到中断,屏蔽中断是为了避免因为意外中断使得boot失败,毕竟很多外设还
没有初始化,对应中断代码也都没有准备好。
为何特权模式?---从uboot方面考虑,其要做的事情是初始化系统相关硬件资源,需要获取尽量多的权限,以方便操作硬件,初始化硬件。设置为svc模式,更有利于其工作。
Cache 和MMU 的设置
1、 MMU 必须关闭。
2、 指令Cache 可以打开也可以关闭。
3、数据Cache 必须关闭。
MMU在上电之初没有任何作用,即U-boot第一阶段的汇编代码与第二阶段的源代码初始化相关外设时访问的都是实际地址,为了启动之初不影响对程序启动常关MMU。
Cache是位于RAM和CPU内部寄存器之间的一个存储设施,用来加速二者之间的数据传输速度,即用来加快CPU从内存中取出指令的速度。但是在上电后CPU的初始化要比内存RAM快一拍,当CPU初始化完成后需要读取来自内存的数据,若内存还没有准备好那势必会造成异常,系统就挂掉了,因此需要关闭数据Cache,而指令Cache关与不关影响不大。
1、源代码组织
对于ARM而言,主要的目录如下:
board 平台依赖 存放电路板相关的目录文件,每一套板子对 应一个目录。如smdk2410(arm920t)
cpu 平台依赖 存放CPU相关的目录文件,每一款CPU对应一个目录,例如:arm920t、 xscale、i386等目录
lib_arm 平台依赖 存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数,如软件浮点。
common 通用 通用的多功能函数实现,如环境,命令,控制台相关的函数实现。
include 通用 头文件和开发板配置文件,所有开发板的配置文件都在configs目录下
lib_generic 通用 通用库函数的实现
net 通用 存放网络协议的程序
drivers 通用 通用的设备驱动程序,主要有以太网接口的驱动,nand驱动。
.......
启动相关的几个重要文件:
1)cpu/arm920t 目录下
Start.S文件是一个汇编代码文件,是Bootloader运行的第一个文件,Bootloader的入口。
U-boot.lds 文件是一个连接器用的连接脚本文件,用来指定映像文件中各段的加载地址和运行地址。
......
2)Makefile简要分析
所有这些目录的编译连接都是由顶层目录的makefile来确定的。
在执行make之前,先要执行make $(board)_config 对工程进行配置,以确定特定于目标板的各个子目录和头文件。
$(board)_config:是makefile 中的一个伪目标,它传入指定的CPU,ARCH,BOARD,SOC参数去执行mkconfig脚本。
这个脚本的主要功能在于连接目标板平台相关的头文件夹,生成config.h文件包含板子的配置头文件。
使得makefile能根据目标板的这些参数去编译正确的平台相关的子目录。
以smdk2410板为例,执行 make smdk2410_config,
主要完成三个功能:
1)在include文件夹下建立相应的文件(夹)软连接,
#如果是ARM体系将执行以下操作:
#ln -s asm-arm asm
#ln -s arch-s3c24x0 asm-arm/arch
#ln -s proc-armv asm-arm/proc
2)生成Makefile包含文件include/config.mk,内容很简单,定义了四个变量:
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
3)生成include/config.h头文件,只有一行:
/* Automatically generated - do not edit */
#include "config/smdk2410.h"
顶层makefile先调用各子目录的makefile,生成目标文件或者目标文件库。
然后再连接所有目标文件(库)生成最终的u-boot.bin。
连接的跟平台相关的主要目标(库)如下:
cpu/$(CPU)/start.o
board/$(BOARDDIR)/lib$(BOARD).a
cpu/$(CPU)/lib$(CPU).a
cpu/$(CPU)/$(SOC)/lib$(SOC).a
lib_$(ARCH)/lib$(ARCH).a
这里面的四个变量定义在include/config.mk(见上述)。
其余的均与平台无关。
所以考虑移植的时候也主要考虑这几个目标文件(库)对应的目录。
U-boot启动流程:
从文件层面上看主要流程是在两个文件中:cpu/arm920t/start.s,lib_arm/board.c,下一节开始分析start.s
思考:
为什么引导代码不全用C语言写?
1、在初始化硬件时,需要设置特殊功能寄存器,比如CPU工作模式,C语言不能对这些寄存器操作
2、C程序运行需要提前设置栈空间。