U-boot使用与启动流程分析

U-boot使用与启动流程分析

文章目录

1、编译体验

1.1、Linux系统启动分析

与PC启动过程对比:
在这里插入图片描述
嵌入式系统启动过程:

  • 嵌入式系统一上电启动bootloader(那么bootloader最终目的就是启动内核)
  • bootloader引导启动内核(读出内核)
  • 内核启动完之后挂接根文件系统
  • 接着运行文件系统中的应用程序

uboot的最终目的就是启动内核:
在这里插入图片描述
那么uboot应该有的功能:
在这里插入图片描述
U-boot从Flash读出内核放到SDRAM上去,然后启动内核,那么U-boot应该有的功能有:

  • 1、硬件相关的初始化:关看门狗、初始化时钟(单片机上电以12MHz运行,提高速度,需要设置时钟)、初始化SDRAM
  • 2、从Flash中读出内核( 为了开发方便还要实现读写Flash功能,u-boot可以从网络、硬盘等获得,那么需要支持网卡、USB、串口等功能)
  • 3、启动内核

1.2、打补丁、编译、烧写、试验

1、解压

tar xjf u-boot-1.1.6.tar.bz2

2、打补丁

cd u-boot-1.1.6
patch -p1 < …/u-boot-1.1.6_jz2440.patch //-p1表示忽略第一个之前的目录
在这里插入图片描述
—:表示原来的代码
++:表示修改后的代码
@@ -34,6 +34,8 @@:表示从34行后开始,原来有6行,修改后有8行:
在这里插入图片描述

3、配置

make 100ask24x0_config

4、编译、烧写

make
oflash 0 1 0 0 0 u-boot.bin

5、实验

//##### 100ask Bootloader for OpenJTAG #####
[n] Download u-boot to Nand Flash
[c] Re-scan Nor Flash
[u] Copy bootloader from nand to nor
[v] Copy bootloader from nor to nand
[k] Download Linux kernel uImage
[j] Download root_jffs2 image
[y] Download root_yaffs image
[d] Download to SDRAM & Run
[z] Download zImage into RAM
[g] Boot linux from RAM
[f] Format the Nand Flash
[s] Set the boot parameters
[b] Boot the system
[r] Reboot u-boot
[q] Quit from menu
Enter your selection:

查看命令说明:? md
在这里插入图片描述
menu:回到菜单界面
print:查看环境变量
save:保存设置的环境变量
reset:重启U-boot
set bootdelay 10 :设置环境变量
set bootdelay :删除环境变量

2、分析U-boot的Makefile

在这里插入图片描述

2.1、uboot的配置过程:make 100ask24x0_config

0、配置命令:make 100ask24x0_config

在 Makefile 文件中找到 100ask24x0_config 如下:
在这里插入图片描述
其中:MKCONFIG 表示在源码树下面有一个 mkconfig 脚本文件
在这里插入图片描述
$(@:_corfig)表示使用空格替换目标,即将 100ask24x0_config 替换为 100ask24x0:
在这里插入图片描述
分析 mkconfig 脚本文件:

1、分析命令传入的参数有没有–、-a等,这条命令没有先去掉

在这里插入图片描述

2、如果 BOARD_NAME 定义了执行前面的,没定义执行后面的:

在这里插入图片描述
我们 BOARD_NAME 为空,那么 BOARD_NAME 为 $1 ,即BOARD_NAME = 100ask24x0:
在这里插入图片描述
在linux脚本中有如下定义:$1表示命令的第一个参数:
在这里插入图片描述

3、 $# 表示参数的个数,小于4、大于6都会退出,我们这条命令不会退出

在这里插入图片描述

4、接下来大于这句话:

在这里插入图片描述

5、我们的 SRCTREE 是等于 OBJTREE 的

在Makefile文件中:如果定义了BUILD_DIR,OBJTREE等于BUILD_DIR;没定义的话就等于CURDIR
在这里插入图片描述
所以下面的代码会执行else分支
在这里插入图片描述
其中ln -s asm-$2 asmln -s asm-arm asm,即建立;一个链接文件asm,这个链接文件指向asm-arm
在这里插入图片描述
在 inculde 下面 asm 有很多架构:
在这里插入图片描述
那么我们写代码时:要包含i386架构的话,需要写asm-i386;要包含arm架构的话,需要写asm-arm
在这里插入图片描述
那么使用上面生成链接的话就能直接使用asm/type.h即可:
在这里插入图片描述
即asm配置的时候临时生成,那么asm就会指向某个架构

6、删除asm-arm/arm

在这里插入图片描述

7、我们第6个参数不为空,执行else分支

在这里插入图片描述
就相当于在目录asm-arm下建立一个 arch 文件,指向 arch-s3c24x0
在这里插入图片描述在这里插入图片描述

7、我们 $2 等于arm,那么就建立 proc 这样一个链接文件

在这里插入图片描述
在这里插入图片描述

8、新建一些文件

// >表示新建文件,>>表示追加内容
在这里插入图片描述
我们第5个参数为空,第6个参数不为空
在这里插入图片描述
运行之后使得config.mk里的内容为:
在这里插入图片描述
在这里插入图片描述

9、创建单板相关的头文件

在这里插入图片描述
其中我们 APPEND 为NO:
在这里插入图片描述
那么指向else分支,新建一个config.h,使得config.h里的内容为:其中 $1 为 100ask24x0
在这里插入图片描述
在这里插入图片描述
那么 100ask24x0.h 就是我们的配置文件,是否支持某些东西就在这里修改

2.2、uboot的编译、链接过程

0、执行编译命令:make

1、在Makefile中包含配置过程生成的文件

1、包含config.h文件
在这里插入图片描述
2、包含其他文件
。。。
这其中就是包含和替换一些文件的操作
。。。
3、我们执行 make的时候,如果不指定目标,就会生成第一个目标,就是all
all依赖ALL:
在这里插入图片描述
其中u-boot.bin依赖于u-boot:
在这里插入图片描述
其中u-boot依赖于下面的:
在这里插入图片描述
那么我们直接执行make命令:
在这里插入图片描述
上面的命令为:arm-linux-ld -Bstatic -T <.lds> -Ttext 0x33f80000 <一些库> -Map u-boot.map -o u-boot.bin,最终会生成u-boot.bin文件。

其中那些库的前后顺序依赖链接文件.lds:
在这里插入图片描述
在链接文件中指定了顺序:
在这里插入图片描述
在这里插入图片描述
其中. = 0x00000000; //表示当前地址为0,会加上 0x33f80000,即后面的东西放在0x33f80000地址处,链接顺序为:

  • 先放arm920t/start.o的.test(代码段)
  • 再放boot_init.o的.text段
  • 。。。。
  • 。。。。

其中 0x33f80000 为 TEXT_BASE,TEXT_BASE 在 LDFLAGS 中定义:
在这里插入图片描述在这里插入图片描述
修改0x33f80000就可以修改u-boot的链接地址,我们的单板有64M的SDRAM。64M从0x300000开始,u-boot在最上面的512K
在这里插入图片描述

2.3、分析Makefile可得如下结论

  • 1、u-boot运行的第一个文件为cpu/arm920t/start.S
  • 2、链接地址在:board/100ask24x0/u-boot.lds + 0x33f80000 处

3、uboot启动流程第1阶段

第一阶段大体流程如下;
在这里插入图片描述

1、硬件初始化

第一个文件为cpu/arm920t/start.S
跳转到reset:
在这里插入图片描述

  • 把cpu设置为SVC32管理模式
    在这里插入图片描述
  • 关闭看门狗
    在这里插入图片描述
  • 关中断
    在这里插入图片描述

2、为加载bootloader的第二阶段代码准备RAM空间

在这里插入图片描述
这里start地址:

  • 1、上电自动运行,即从NAND flash拷贝到SDRAM,地址就是0;
  • 2、通过仿真器运行,地址就是链接地址0x33f80000
    判断start和TEXT_BASE不等的话,说明SDRAM没有初始化。

cpu_init_crit如下:

  • 关闭CACHE、MMU:
    在这里插入图片描述
  • 还有lowlevel_init:
  • lowlevel_init里初始化SDRAM存储控制器,之后内存就可以使用了。
    在这里插入图片描述

3、设置栈

  • 设置栈
    在这里插入图片描述
    u-boot的内存分布:
    在这里插入图片描述

4、复制bootloader的第二阶段代码到RAM空间中(重定位)

  • 重定位
  • 首先判断是NOR启动还是NAND启动,如果是NOR启动就直接拷贝数据。拷贝代码之前,要传递给拷贝函数三个参数,源,目的,长度。读取NAND的话要参考芯片手册的NAND读取数据的时序,选中NAND,发出读命令,发出地址,发出读命令,判断状态,读取数据,取消选中等。
    在这里插入图片描述
    copy_code_to_sdram函数为:
    在这里插入图片描述

5、跳转到第二阶段的代码的C入口点

  • 跳转之前清除BSS段
    在这里插入图片描述
  • 调用第二阶段的C函数
    在这里插入图片描述
    绝对跳转,跳到SDRAM上执行

4、uboot启动流程第2阶段

第2阶段从lib_arm/board.c的start_armboot函数开始:
在这里插入图片描述

4.1、初始化本阶段要用到的硬件设备

初始化系统时钟

board_init函数设置MPLL、改变系统时钟,在board/smdk2410/smdk2410.c中实现;
在这里插入图片描述
board_init还保存了机器ID,在其被调用时传入内核;
在这里插入图片描述

串口初始化

为了方便开发,至少要初始化一个串口以便程序员与 Bootloader进行交互。
主要是serial_init函数,它设置UART控制器,是CPU相关函数,在cpu/arm920t/s3c24x0/serial.c中实现。
在这里插入图片描述
在这里插入图片描述

4.2、检测系统内存映射

所谓检测内存映射,就是确定板上使用了多少内存、它们的地址空间是什么。由于嵌入式开发中 Bootloader多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各类情况的复杂算法。

对于特定开发板,其内存分布是明确的,所以可以直接设置。
board/smdk2410/smdk2410.c中的dram_init函数直到开发板内存起始地址为0x30000000,大小为 0x4000000:
在这里插入图片描述
在这里插入图片描述

4.3、将内核映象和根文件系统映象从 Flash上读到SDRAM空间中

Flash上的内核映象有可能是经过压缩的,在读到SDRAM之后,还需要进行解压。当然,对于有自解压功能的内核,不需要 Bootloader来解压。将根文件系统映象复制到SDRAM中,这不是必需的。这取决于是什么类型的根文件系统以及内核访问它的方法。

将内核存放在适当的位置后,直接跳到它的入口点即可调用内核。调用内核之前,下列条件要满足:

  • CPU寄存器的设置
    R0=0(规定)。
    R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见 linux/arch/arm tools/ mach-types
    R2=启动参数标记列表在RAM中起始基地址(下面会详细介绍如何传递参数)。
  • CPU工作模式
    必须禁止中断(IRQ和FIQ,uboot启动是一个完整的过程,没有必要也不能被打断)
    CPU必须为SVC模式(为什么呢?主要是像异常模式、用户模式都不合适。具体深入的原因自己可以查下资料)。
  • Cache和MMU的设置
    MMU必须关闭。
    指令 Cache可以打开也可以关闭。
    数据 Cache必须关闭。

4.4、为内核设置启动参数

Bootloader与内核的交互是单向的, Bootloader将各类参数传给内核。由于它们不能同时行,传递办法只有一个:Bootloader将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。

除了约定好参数存放的地址外,还要规定参数的结构。Linu2.4x以后的内核以标记列表( tagged_list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记 ATAG_CORE开始,以标记 ATAG_NONE结束。

标记的数据结构为tag,它由一个 tag_header结构和一个联合(union)组成。 tag_ header结构表小标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用 tag_mem32,表示命令行时使用 tag_cmdline。

bootloader与内核约定的参数地址,设置内存的起始地址和大小,指定根文件系统在那个分区,系统启动后执行的第一个程序linuxrc,控制台ttySAC0等。

4.5、调用内核

调用内核就是uboot启动的最后一步了。到这里就uboot就完成了他的使命。

5、u-boot命令分析

5.1、U-boot命令的格式

u-boot中每个命令都是通过U_BOOT_CMD宏来定义格式如下:
在这里插入图片描述
各项参数意义如下:
在这里插入图片描述
其中宏U_BOOT_CMD在include/command.h中定义,如下所示:
在这里插入图片描述
Struct_Section 也是在include/command.h中定义,如下所示:
在这里插入图片描述
例如:bootm 命令,定义如下:
在这里插入图片描述
宏U_BOOT_CMD展开后如下:
在这里插入图片描述
对于每个使用U_BOOT_CMD宏定义的命令,其实都是在“.u_boot_cmd”段中定义一个 cmd_tbl_t 结构体。链接脚本U-Boot.lds中有如下代码:
在这里插入图片描述
程序中就是根据命令的名字在内存段__u_boot_cmd_start ~~ __u_boot_cmd_end找到它的cmd_tbl_t结构体,然后调用它的函数(具体在common/common.c的find_cmd函数中实现)。
在这里插入图片描述

5.2、在uboot下面实现hello命令

在这里插入图片描述
在这里插入图片描述
在common目录的main.c中,增加hello命令:
添加cmd_hello.c文件,放在common目录下。
添加(common目录下)cmd_bootm.c的头文件:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
把cmd_hello.c放在common的目录下。
修改common下的Makefile:添加一个cmd_hello.o
重新make
重新烧写u-boot:
oflash 0 1 0 0 0 u-boot.bin
在这里插入图片描述

6、启动内核的过程分析

6.1、读出内核

启动内核的两个命令:

  • bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
    从nand读出内核:从哪里读?去kernel分区;放到哪里?0x30007fc0地址上
    nand read.jffs2 0x30007fc0 0x00060000(起始地址)0x00200000(长度)
    注释:jffs2是一种文件格式,使用时就不需要页对齐
  • bootm 0x30007FC0:从地址上启动内核

其中分区:
在这里插入图片描述
PC机上每个硬盘都有一个分区表,linux里没有专门的分区表,需要在源码中写死各分区的地址和大小,大概分为boot、env、kerne、root
在include/100ask24x0.h中:
在这里插入图片描述
在u-boot界面使用mtd命令查看分区信息:
在这里插入图片描述

6.2、启动内核

1、uboot与Linux内核之间的参数传递

我们知道,uboot启动后已经完成了基本的硬件初始化(如:内存、串口等),接下来它的主要任务就是加载Linux内核到开发板的内存,然后跳转到Linux内核所在的地址运行。

具体是如何跳转呢?做法很简单,直接修改PC寄存器的值为Linux内核所在的地址,这样CPU就会从Linux内核所在的地址去取指令,从而执行内核代码。

在前面我们已经知道,在跳转到内核以前,uboot需要做好以下三件事情:

  • CPU寄存器的设置
    R0=0。
    R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见 linux/arch/arm tools/ mach-types
    R2=启动参数标记列表在RAM中起始基地址。
  • CPU工作模式
    必须禁止中断(IRQs和FIQs)
    CPU必须为SVC模式
  • Cache和MMU的设置
    MMU必须关闭
    指令 Cache可以打开也可以关闭
    数据 Cache必须关闭

其中上面第一步CPU寄存器的设置中,就是通过R0,R1,R2三个参数给内核传递参数的。(ATPCS规则

2、为什么要给内核传递参数呢?

在此之前,uboot已经完成了硬件的初始化,可以说已经”适应了“这块开发板。然而,内核并不是对于所有的开发板都能完美适配的(如果适配了,可想而知这个内核有多庞大,又或者有新技术发明了,可以完美的适配各种开发板),此时对于开发板的环境一无所知。所以,要想启动Linux内核,uboot必须要给内核传递一些必要的信息来告诉内核当前所处的环境

3、如何给内核传递参数?

因此,uboot就把机器ID通过R1传递给内核,Linux内核运行的时候首先就从R1中读取机器ID来判断是否支持当前机器。这个机器ID实际上就是开发板CPU的ID,每个厂家生产出一款CPU的时候都会给它指定一个唯一的ID,大家可以到uboot源码的arch\arm\include\asm\mach-type.h文件中去查看。
在这里插入图片描述
R2存放的是块内存的基地址,这块内存中存放的是uboot给Linux内核的其他参数。这些参数有内存的起始地址、内存大小、Linux内核启动后挂载文件系统的方式等信息。很明显,参数有多个,不同的参数有不同的内容,为了让Linux内核能精确的解析出这些参数,双方在传递参数的时候要求参数在存放的时猴需要按照双方规定的格式存放。

除了约定好参数存放的地址外,还要规定参数的结构。Linux2.4.x以后的内核都期望以标记列表(tagged_list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。

标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成。tag_header结构表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_ mem32,表示命令行时使用 tag_cmdline。具体代码见arch\arm\include\asm\setup.h。
在这里插入图片描述
从上面可以看出,struct_tag结构体由structtag_header+联合体union构成,结构体struct tag_header用来描述每个tag的头部信息,如tag的类型,tag大小。联合体union用来描述每个传递给Linux内核的参数信息。

下面以传递内存标记、传递命令行参数为例来说明参数的传递。
1、设置开始标记ATAG_CORE

	tag->hdr.tag  = ATAG_CORE;
	tag->hdr.size = tag_size(tag_core);
	tag->u.core.flags = params->u1.s.flags & FLAG_READONLY;
	tag->u.core.pagesize = params->u1.s.page_size;
	tag->u.core.rootdev = params->u1.s.rootdev;

	tag = tag_next(tag);

涉及到的结构体定义如下

struct tag_header {
	__u32 size;
	__u32 tag;
};

/* The list must start with an ATAG_CORE node */
#define ATAG_CORE	0x54410001

struct tag_core {
	__u32 flags;		/* bit 0 = read-only */
	__u32 pagesize;
	__u32 rootdev;
};

其中tag_next,tag_size定义如下,指向当前标记的结尾

#define tag_next(t)	((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)	((sizeof(struct tag_header) + sizeof(struct type)) >> 2)

2、设置内存标记

t->hdr.tag = ATAG_MEM;
t->hdr.size = tag_size(tag_mem32);
t->u.mem.start = CFG_GLOBAL_RAM_BASE;
t->u.mem.size = CFG_GLOBAL_RAM_SIZE;
t = tag_next(t);

相关结构体定义如下

#define ATAG_MEM	0x54410002
struct tag_mem32 {
	__u32	size;
	__u32	start;	/* physical start address */
};

3、设置命令行参数标记
命令行参数是一个字符串,一般用它来告诉内核挂载根文件系统的方式。由uboot的bootargs环境变量提供,它的内容有如下两种格式

root=nfs nfsroot=202.193.61.237:/work/nfs_root/first_fs ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
root=/dev/mtdblock2 ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
名称含义
root告诉Linux内核挂载根文件系统的方式,nfs表示以NFS服务的方式挂载根文件系统,/dev/mtdblock2表示根文件系统在MTD设置的第二个分区上。
nfsroot告诉Linux内核,以NFS方式挂载根文件系统时,根文件系统所在主机的P地址和路径
ip告诉Linux内核,启动后它的p地址
init告诉Linux内核,启动的第一个应用程序是根目录下的linuxrc程序
console告诉Linux区内核,控制台为ttySAC0,波特率为115200
tag = tag_next(tag);
tag->hdr.tag = ATAG_CMDLINE;
tag->hdr.size = (strlen(params->commandline) + 3 +
		 sizeof(struct tag_header)) >> 2;
strcpy(tag->u.cmdline.cmdline, params->commandline);

tag = tag_next(tag);

相关结构体定义如下

/* command line: \0 terminated string */
#define ATAG_CMDLINE	0x54410003

struct tag_cmdline {
	char	cmdline[1];	/* this is the minimum size */
};

4、设置结束标记

tag->hdr.tag = ATAG_NONE;
tag->hdr.size = 0;

我们明白了运行Linux区内核的时候,uboot需要给内核的传递的参数,接下来我们就来看看如何从uboot中跳到Linux内核。

4、uboot跳转到Linux内核

在uboot中可以使用go和bootm来跳转到内核,这两个命令的区别如下:

  • go命令仅仅修改pc的值到指定地址
    格式:go addr
  • bootm命令是uboot专门用来启动uImage格式的Linux内核,它在修改pc的值到指定地址之前,会设置传递给Linux内核的参数,用法如下:
    格式:bootm addr

5、uboot中bootm命令实现

bootm命令在uboot源码common/cmd_bootm.c中实现,它的功能如下:

  • 读取uImage头部,把内核拷贝到合适的地方。
  • 把参数给内核准备好。
  • 引导内核。

当我们使用我们在uboot使用bootm命令后,bootm命令会从uImage头中读取信息后,发现是Linux内核,就会调用do_bootm_linux()函数,函数的具体实现bootm.c中

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
	/* No need for those on ARM */
	if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
		return -1;

	if (flag & BOOTM_STATE_OS_PREP) {
		boot_prep_linux(images);
		return 0;
	}

	if (flag & BOOTM_STATE_OS_GO) {
		boot_jump_linux(images);
		return 0;
	}

	boot_prep_linux(images);
	boot_jump_linux(images);
	return 0;
}

do_bootm_linux 函数最终会 跳转执行 boot_prep_linux 和 boot_jump_linux 函数,首先分析 boot_prep_linux 函数(位于 bootm.c 文件中):

static void boot_prep_linux(bootm_headers_t *images)
{
    char *commandline = getenv("bootargs");      //从环境变量中获取 bootargs 的值

  。。。。。。。
        setup_board_tags(&params);      
        setup_end_tag(gd->bd);    //将 tag 参数保存在指定位置
    } else {
        printf("FDT and ATAGS support not compiled in - hanging\n");
        hang();
    }
    do_nonsec_virt_switch();
}

从代码可以看出来,boot_prep_linux,主要功能是将 tag 参数保存到指定位置,比如 bootargs 环境变量 tag,串口 tag,接下来分析 boot_jump_linux 函数(位于 bootm.c 文件中):

static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number;      //获取机器id (在 board/samsung/jz2440/jz2440.c 中设置,为 MACH_TYPE_SMDK2410(193))
    char *s;
    void (*kernel_entry)(int zero, int arch, uint params);
    unsigned long r2;
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

    kernel_entry = (void (*)(int, int, uint))images->ep;    //获取 kernel的入口地址,此处应为 30000000

    s = getenv("machid");        //从环境变量里获取机器id (本例中还未在环境变量里设置过机器 id)
    if (s) {            //判断环境变量里是否设置机器id
        strict_strtoul(s, 16, &machid);    //如果设置则用环境变量里的机器id
        printf("Using machid 0x%lx from environment\n", machid);
    }

    debug("## Transferring control to Linux (at address %08lx)" \
        "...\n", (ulong) kernel_entry);
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    announce_and_cleanup(fake);

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
        r2 = (unsigned long)images->ft_addr;
    else
        r2 = gd->bd->bi_boot_params;    //获取 tag参数地址,gd->bd->bi_boot_params在 setup_start_tag 函数里设置
if (!fake) kernel_entry(0, machid, r2); }  //进入内核

通过分析可以看出,最终进入内核的函数为:

kernel_entry(0, machid, r2)

到这里bootm就成功给内核传递了参数,并跳转到了内核。关于go命令的实现可以自己参考内核,在cmd_boot.c文件中,所不同的是,go命令实现的时候没有设置参数,只是简单的跳转执行。

至此,uboot就启动了内核。接下来就是挂载根文件系统

7、内核镜像格式vmlinuz、zImage和uImage

最后插讲下内核的不同映像格式的区别:
(1)uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序类似于windows下的exe格式,在操作系统下是可以直接执行的。但是这种格式不能用来烧录下载。我们用来烧录下载的是u-boot.bin,这个东西是由u-boot使用arm-linux-objcopy工具进行加工(主要目的是去掉一些无用的)得到的。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到iNand中执行的。

(2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的原版内核elf文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),经过制作加工成烧录镜像的文件就叫Image(制作把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。

(3)原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。实际上linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在image压缩后的文件的前端附加了一部分解压缩代码。构成了一个压缩格式的镜像就叫zImage。(因为当年Image大小刚好比一张软盘(软盘有2种,1.2M的和1.44MB两种)大,为了节省1张软盘的钱于是乎设计了这种压缩Image成zImage的技术)。

(4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。

(5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。

(6)如果直接在kernel底下去make uImage会提供mkimage command not found。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。

通过上面的介绍我们了解了内核镜像的各种格式,如果通过uboot启动内核,Linux必须为uImage格式。

参考:
超详细分析Bootloader(Uboot)到内核的启动流程(万字长文!)
嵌入式Linux应用开发完全手册

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
U-boot是一个开放源代码的引导加载程序,它是嵌入式Linux系统中非常重要的一部分。它主要负责完成硬件初始化、文件系统加载、内核启动、DTB传递等工作。下面是U-boot启动过程的详细分析: 1. 通电启动 当开关通电时,处理器会执行引导ROM中的启动代码。 2. 初始化寄存器 在进入U-boot之前,系统所有的寄存器都是未初始化的。U-boot要负责初始化CPU的所有寄存器,以确保所有设备都能正常工作。 3. 读取U-boot U-boot位于NOR或NAND闪存器中。为了读取U-boot,首先必须确定闪存器的类型和接口。当读取闪存器中的第一个块(一般是U-boot头)时,U-boot校验它的合法性,包括校验和、magic number、版本号等。如果校验失败,U-boot将停止执行。 4. 解压缩U-boot 如果U-boot被压缩了,那么需要用解压缩算法对其进行解压缩。在这个过程中,需要注意解压缩的起始地址和大小。 5. 设置环境变量 U-boot提供了一些环境变量,可以用来配置系统参数,例如:IP地址、MAC地址等。在这一步骤中,U-boot会将环境变量加载到DRAM中进行管理。同时,也可以通过TFTP、NFS等方式从外部存储设备中加载环境变量。 6. 初始化硬件 在U-boot启动过程中,需要对各种设备进行初始化。这些设备包括:串口、网络接口、SD卡、USB设备、SPI设备等。初始化完成后,U-boot才能正常使用这些设备。 7. 加载内核 U-boot负责加载内核到指定的内存地址。这个过程可以通过很多方式来完成:串口、SD卡、网络等。在加载内核之前,U-boot还会加载设备树文件(DTB)。 8. 启动内核 U-boot会将内核启动参数(包括设备树的地址)传递给内核,并通过软件跳转实现内核启动。此时,U-boot的使命就完成了,内核将接管系统的控制权。 总之,U-boot启动过程非常复杂,但是也非常重要。因为U-boot提供了系统启动所需的所有基础设施,从而保证了Linux系统的正常运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值