本文所有资料来至互联网,笔者加以整理和归纳,仅供以后复习
一、bootloader一些共性
一个嵌入式Linux系统从软件的角度看通常可以分为四个层次:引导加载程序、Linux内核、文件系统、用户应用程序。
嵌入式Linux系统中常用的bootloader有armboot、redboot、blob、u-boot等,u-boot是Sourceforge网站上的一个开放源代码的项目。它可对 PowerPCMPC5xx、MPC8xx、MPC82xx、MPC7xx、MPC74xx、ARM(ARM7、ARM9、strongARM、XScaLe)、MIPS(4kc、5kc)、X86等处理器提供支持,支持的嵌入式操作系统有Linux、Vx-WorkS、NetBSD、QNX、RTEMS、ARTOS、LynxOS等,
对于ARM bootloader的一些共同特性,理论上只局限于bootloader的基本功能,因为扩展功能众多,可以有串口、USB、以太网接口、IDE、CF等,无法进行归纳与总结。
对于一个ARM系统来说,本质上,bootloader作为引导与加载内核镜像的“工具”,在实现上,必须提供以下几个功能,更确切地说,必须做到以下几点:
(1)初始化RAM(必需):bootloader必须能够初始化RAM,因为将来系统要通过它保存一些Volatile数据,但具体地实现要依赖与具体的CPU以及硬件系统。
(2)初始化串口(可选,推荐):bootloader应该要初始化以及使能至少一个串口,通过它与控制台联系进行一些debug的工作;甚至与PC通信。
(3)创建内核参数列表(针对linux操作系统,推荐)。
(4)启动内核镜像(必需):根据内核镜像保存的存储介质不同,可以有两种启动方式:FALSH启动以及RAM启动;但是无论是哪种启动方式,下面的系统状态必须得到满足:
a、 CPU寄存器的设置: R0=0;
R1=机器类型;
R2=启动参数标记列表在RAM中的起始地址;
这三个寄存器的设置是在最后启动内核时通过启动参数来传递完成的。
直接跳转至RAM中的内核镜像的第一条指令执行:
linux_kernel = (void (*)(int, int, struct tag*))ram_start;
linux_kernel(LINUX_ARG0, LINUX_ARG1,tagparams);
其中,三个参数的函数如下:
LINUX_ARG0固定为0;
LINUX_ARG1为表征系统机器类型,要与内核中的一致;
Tagparams为系统传给内核的参数列表的首物理地址。
以汇编角度看,最终这三个参数会对应系统寄存器R0,R1和R2。
启动参数详细说明:
设置启动参数,现今的Linux内核都以链表(taggedlist)的形式来传递参数,启动参数以标记ATAG_CORE开始,ATAG_NONE结束。任何bootloader想要传递给kernel的参数都可以添加在在这两个表头与表尾之间。相关的代码如下:
struct tag* params = (struct tag *)BOOT_PARAMS;
params->hdr.size= tag_size(tag_core);
params->hdr.tag= ATAG_CORE;
params->u.core.flags= 0;
params->u.core.pagesize= 0;
params->u.core.rootdev= 0;
params= tag_next(params);
//如果有参数传递,在此添加
params->hdr.size = 0;
params->hdr.tag= ATAG_NONE;
//其中,相关数据结构如下:
struct tag_header {
ulongsize;
ulongtag;
};
struct tag {
struct tag_header hdr;
union{
structtag_core core;
structtag_mem32 mem;
structtag_videotext videotext;
structtag_ramdisk ramdisk;
structtag_initrd initrd;
structtag_serialnr serialnr;
structtag_revision revision;
structtag_videolfb videolfb;
structtag_cmdline cmdline;
struct tag_acorn acorn;
structtag_memclk memclk;
}u;
};
b、 CPU模式: 关闭中断;
属于SVC模式;
Bootloader中没有必要支持中断的实现,这属于内核机制以及设备驱动管理的管理范畴;SVC模式是系统的一种保护模式,这样就可以进行一些只能在SVC模式下的操作,例如一些特定寄存器访问操作。
c、 Cache和MMU的设置: MMU必须关闭;
数据cache必须关闭;
指令cache可以关闭也可以开启;
Bootloader中所有对地址的操作都是使用物理地址,是实在的实地址,不存在虚拟地址,因此MMU必须关闭。Bootloader主要是装载内核镜像,镜像数据必须真实写回SDRAM中,所以数据cache必须关闭;而对于指令cache,不存在强制性的规定,但是一般情况下,推荐关闭指令cache。
d、 Bootloader启动内核镜像的方法:是通过跳转语句直接跳转至内核镜像的第一句指令语句。
二、uboot启动过程
Uboot启动分为两个阶段:阶段一主要为汇编代码,涉及cpu/arm1176/start.s,board/开发板目录/lowlevel_init.S和arch/arm/lib/board.c中的board_init_f函数。
完成工作如下:
(1)设置CPU为SVC模式,关中断,关看门狗,关MMU;
(2)设置时钟,初始化各硬件控制器;
(3)设置堆栈;
(4)复制(重定位)Uboot到内存中;
(5)跳转到第二阶段代码入口;
阶段二:加载内核镜像并转让控制权给内核,主要用C语言,分两个阶段代码具有更好的可读性与移植性:若对于相同的CPU以及存储设备,要增加外设支持,阶段1的代码可以维护不变,只对阶段2的代码进行修改;若要支持不同的CPU,则基础代码只需在阶段1中修改。
arm/cpu/arm1176/start.S
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
以_start标号标识,一开始处设置异常中断向量表,当是冷启动时,直接跳转至对应处进行启动:
系统正常启动,都属于冷启动,程序直接跳转至reset标号处执行。
图片来至:博主------- 一米阳光
我自己代码笔记,基于tiny6410
三、编译环境
基于ARM平台的工具分别为arm-linux-as(汇编器)、arm-linux-gcc(c编译器)、arm-linux-g++(c++编译器)、arm-linux-ld(链接器)
四、uboot地址规划设计
当bootloader阶段设计好之后,需要考虑的是镜像存储的地址分配:总镜像保存在什么地方,阶段2对应的镜像会被拷贝到什么地方;内核镜像原先存放在什么地方,bootloader会把它又重新加载到什么地方;具体怎么设计要看处理器和内存情况。