Bootloader的主要任务是引导加载并运行应用程序,对于MCU中的BootLoader,我之前写过一篇详细的文章单片机中BootLoader的严谨实现详解介绍它实现的整体流程。对于Linux来说,在运行Linux内核之前也需要BootLoader进行引导,这个BootLoader不需要我们写,因为有很多开源代码供我们选择,其中最常用的就是U-Boot。这篇文章就来简单介绍以下U-Boot,来看一下它是怎么使用的。
文章目录
- 1 U-Boot简介
- 2 U-Boot命令行指令详解
- 2.1 信息查询指令
- 2.2 环境变量相关指令
- 2.3 启动相关指令
- 2.4 文件系统相关指令
- 2.5 内存操作指令
- 2.6 网络相关指令
- 2.7 设备操作指令
- 2.8 其他指令
 
1 U-Boot简介
U-Boot(Universal Bootloader)是一个开源的引导加载程序,主要用于嵌入式系统和嵌入式设备。它被设计成通用的,能够在多种处理器架构上运行,如ARM、MIPS、x86等。U-Boot的主要功能是加载并启动操作系统内核,最常见的是Linux内核。
以下是U-Boot引导Linux内核时所执行的主要步骤:
- 启动阶段(Boot ROM): 
  - 当嵌入式设备上电或者复位时,处理器会从固定地址处的启动ROM(Boot ROM)中开始执行代码。
- 启动ROM的主要任务是初始化系统的基本硬件,如时钟、内存控制器等,并加载U-Boot引导加载程序到RAM中。
 
- U-Boot初始化: 
  - 一旦U-Boot加载到RAM中,它就开始执行。U-Boot首先进行硬件初始化,包括处理器、内存、外设等的配置。
- U-Boot提供了一个命令行界面,允许用户进行配置和交互式操作。用户可以通过串口、网络或其他方式与U-Boot进行通信。
 
- 加载Linux内核: 
  - U-Boot负责从存储介质(如Flash、SD卡、网络等)中加载Linux内核镜像到设备的内存中。这通常涉及到文件系统的读取和解析。
- 用户可以在U-Boot命令行中手动输入加载内核的命令,也可以通过配置文件自动加载。
 
- 设备树加载: 
  - 对于许多嵌入式系统,U-Boot还会加载设备树(Device Tree)。设备树是一种描述硬件设备和系统拓扑结构的数据结构,它允许Linux内核动态适应各种硬件配置。
 
- 对于许多嵌入式系统,U-Boot还会加载设备树(
- 传递控制权给Linux内核: 
  - 一旦Linux内核被成功加载到内存中,U-Boot会设置一些必要的参数,如内核命令行参数、设备树的地址等。
- U-Boot然后将控制权传递给Linux内核,使其开始执行。此时,Linux内核接管系统的控制权。
 
总的来说,U-Boot在嵌入式系统中的角色是引导加载程序,负责初始化硬件、加载操作系统内核、传递必要的参数,并最终将控制权交给内核。这使得U-Boot成为嵌入式系统中一个关键的组件,它的配置和功能对系统的启动和运行起着重要作用。
2 U-Boot命令行指令详解
在U-Boot运行后,会有一个倒计时,如果倒计时内我们按下键盘的任何按键,就可以进入U-Boot的命令行模式,如果不按下,则U-Boot会根据我们默认的启动参数来启动内核。这里我们就来介绍一下常用的U-Boot的命令行的指令。我们可以输入help查看支持的指令:
 
 对于具体的指令,我们可以输入help 指令名来查看这个指令的用法,比如这里的base指令:
 
 下面就具体来看一下这些指令的使用方法,对于不常用的指令,就不详细介绍或举例说明了。
2.1 信息查询指令
-  bdinfo:打印板级信息结构(Board Info structure),包括板级名称、序列号、CPU类型、时钟频率等。
  
-  printenv:打印环境变量的值。
  
 这里面有很多环境变量的值,比如串口的波特率,还有U-Boot上电后倒计时的值bootdelay。修改环境变量的指令参考下面的3.2 环境变量相关指令。
-  version:打印U-Boot的版本信息、编译器和链接器的版本。
  
2.2 环境变量相关指令
-  editenv:编辑环境变量。
  
 以修改bootdelay参数为例,输入editenv bootdelay后,等于进入一个文本模式,这里显示了当前的初始值,我们只需要修改然后回车即可,这条指令修改环境变量很方便。
-  env:环境变量处理命令。
  
-  saveenv:将环境变量保存到存储中。前面设置的变量默认是保存在RAM中,如果需要下次上电使用新设置的环境变量,需要保存到非易失存储中。
-  setenv:设置/新建/删除环境变量的值。- 我们可以调用set bootdelay 5修改环境变量的值,如果环境变量不存在,则会自动创建。如果参数为空,则可以删除环境变量,如set bootdelay。
- 有的U-Boot不支持识别参数中的空格,可能环境变量就设置为第一个空格前的值了,这个时候需要用单引号把参数括起来,如setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2'。
 
- 我们可以调用
-  setexpr:将环境变量设置为表达式的结果。- 举个例子,假设bootdelay为5,setexpr bootdelay1 $bootdelay + 2将设置bootdelay1为7。
 
- 举个例子,假设
-  showvar:打印本地hushshell变量。
2.3 启动相关指令
- bootz:从内存中启动Linux zImage镜像。- 格式为:bootz [addr [initrd[:size]] [fdt]],其中addr为zImage地址,initrd为在系统引导过程中挂载的临时根文件系统,一般我们不使用,可以填-略过这个参数,fdt为设备树的地址。我们可以使用TFTP、NFS等命令下载镜像到RAM后,使用bootz来启动内核:
 
- 格式为:
tftp 80800000 zImage
tftp 83000000 imx6ull-alientek-emmc.dtb
bootz 80800000 – 83000000
- bootm:从内存中启动uImage镜像。
 指令格式同- bootz:- bootm [addr [initrd[:size]] [fdt]],如果不使用设备树,则直接- bootm addr。
- boot:执行环境变量- bootcmd中的启动命令。
 假设我们想每次都从TFTP下载镜像启动,我们就可以设置- bootcmd,以后就输入boot就会执行- bootcmd里的指令了。实际上前面所说的- bootdelay倒计时结束后,默认也是执行- bootcmd。- 还有一个环境变量bootargs也与启动有关,它可以传参给内核,在后面的文章我们会介绍,这里先了解一下。
 
- 还有一个环境变量
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz
80800000 - 83000000'
saveenv
boot
- bootd:和- boot类似,执行- bootcmd的命令,但它提供了额外的参数用来设置- initrd,这里不介绍。
- bootp:通过网络使用BOOTP/TFTP协议启动镜像。
- bootvx:从内存中的ELF映像启动vxWorks操作系统。
- bootelf:从内存中的ELF映像启动。ELF文件包含调试信息,我们仅在调试是使用这个ELF文件,最后能够运行的程序是bin程序,这个指令能从ELF中提取出原始的bin镜像。
2.4 文件系统相关指令
- fatinfo:打印FAT文件系统的信息。- 格式为:fatinfo <interface> [<dev[:part]>]
  
 上面查看了MMC(我这里MMC的dev为1)的第一个分区的文件系统信息
 
- 格式为:
- fatls:列出目录中的文件(默认为根目录)。
 我们在MMC的第一个分区里面的文件系统里存放了- zimage和设备树。
  
- fstype:查看文件系统类型
  
- fatload:从文件系统中加载二进制文件到RAM。
 下面代码加载zImage到0x80000000处
fatload mmc 1:1 80000000 zImage
- fatsize:确定文件的大小。
- fatwrite:将文件写入DOS文件系统。
- ls:列出目录中的文件(默认为根目录)。
- load:从文件系统加载二进制文件。
- save:将文件保存到文件系统。
- ext2load:从Ext2文件系统加载二进制文件。
- ext2ls:列出目录中的文件(默认为根目录)。
- ext4load:从Ext4文件系统加载二进制文件。
- ext4ls:列出目录中的文件(默认为根目录)。
- ext4size:确定文件的大小。
- ext4write:在根目录下创建文件。
2.5 内存操作指令
-  cmp:对比内存内容。- 类似C语言的memcmp,格式为:cmp [.b, .w, .l] addr1 addr2 count,将输出两个内存里的值是否相等。
 
- 类似C语言的
-  cp:内存复制。- 类似C语言的memcpy,格式为:cp [.b, .w, .l] source target count
 
- 类似C语言的
-  md(Memory Display):显示内存内容。- 格式为:md [.b, .w, .l] address [# of objects],其中b/w/l分别表示字节、半字和字
  
 上图为显示0x80000000内存处的前0x10(16)个字节。
 
- 格式为:
-  mm(memory modify):修改内存内容(自动增加地址)。- 格式为:mm [.b, .w, .l] address,输入后会进入交互模式,如果要退出保存,则输入空格回车即可。
  
 上图就是每次修改一个字节,然后回车后修改下一个字节。
 
- 格式为:
-  mtest:简单的RAM读写测试。
-  mw:写内存(填充)。- 格式为:mw [.b, .w, .l] address value [count]
  
 上图填充0x80000000开始的8字节为0xab。
 
- 格式为:
-  nm:修改内存内容(固定地址)。- 格式为:nm [.b, .w, .l] address
  
 与mm类似,但这里不递增,就是修改一个固定的内存的内容。这里没指定数据长度默认就是l,这里输入完后按回车退出不了,按输入q退出。
 
- 格式为:
2.6 网络相关指令
在介绍网络相关的指令之前,先来介绍一下与网络有关的环境变量。在U-Boot使用以太网相关的指令时,比如初始化时,会从环境变量中取这些值进行配置。
| 环境变量 | 描述 | 
|---|---|
| ipaddr | IP地址,若不指定,可使用dhcp命令自动获取 | 
| ethaddr | MAC地址 | 
| gatewayip | 网关 | 
| netmask | 子网 | 
| serverip | 服务器IP地址,一般为我们开发使用的Ubuntu的IP地址 | 
如下图所示:
 
- dhcp:通过DHCP/TFTP协议,使用网络启动镜像。- 如果只输入一个dhcp,就会通过路由器的DHCP服务分配一个IP地址给设备
- 它还能从网络启动镜像,完整指令格式如下:dhcp [loadAddress] [[hostIPaddr:]bootfilename]
  
 这里通过dhcp,路由器给设备分配了一个IP地址192.168.31.203。
 
- 如果只输入一个
- ping:向网络主机发送ICMP ECHO_REQUEST请求。
  
 我们可以ping Ubuntu主机的IP看一下是否在线。
下面来介绍一下nfs和tftpboot两个指令,NFS和TFTP的用处和环境搭建可以参考这篇文章:环境搭建之TFTP、NFS、SSH和FTP的安装和使用
- nfs:通过网络文件系统下载镜像- 格式为:nfs [loadAddress] [[hostIPaddr:]bootfilename]
 现在假设我们的Ubuntu NFS服务器上有一个1.txt,文件内容为“123456\n”,它的十六进制如下:
  
 执行nfs 80000000 192.168.31.120:/var/nfs/general/1.txt:
  
- 如果确定两个板子ping通,但是板子使用nfs命令提示File lookup fail,大概率为版本原因,解决方法如下:
 
- 格式为:
1.修改nfs-kernel-server文件
sudo vi /etc/default/nfs-kernel-server
更改RPCNFSDCOUNT="-V 2 8"和RPCMOUNTDOPTS="-V 2 --manage-gids"
2.重启NFS服务端
sudo /etc/init.d/nfs-kernel-server restart
- tftpboot:通过TFTP协议传输镜像。- 格式为: tftpboot [loadAddress] [[hostIPaddr:]bootfilename],如果不指定服务端地址hostIPaddr,则会使用环境变量serverip。
 假设Ubuntu TFTP目录中有一个1.txt,文件内容为“654321\n”,它的十六进制如下:
  
 执行tftp 80000000 192.168.31.120:1.txt,这里不用指定tftp的文件目录,因为服务端的配置文件中有tftp的目录。
  
 
- 格式为: 
2.7 设备操作指令
- mmc:MMC子系统命令,可以用来读写EMMC、SD等与MMC协议兼容的设备,命令如下:
  
 这里我的板子使用的EMMC,首先使用- mmc list查看一下可用的MMC设备,然后使用- mmc dev 1(后面还能跟一个分区参数,不写则默认第一个分区,假设要切换到分区2,则输入- mmc dev 1 2)切换到EMMC,最后使用- mmc part来查看一下MMC的分区。
 如果EMMC里有Linux的话,它是有3个分区的,第0个存放U-Boot,第1个存放Linux镜像和设备树,第2个存放根文件系统(下图中没有显示出0分区,但实际是存在的)。
  
 下面我们从分区0中读取数据到RAM中,- mmc read是按块读取的,这里MMC的一个块是512字节,下面我们从分区0的第0块开始读4个块到0x80000000处。
  
 如果我们想烧录U-Boot,我们就可以使用- mmc write来实现。首先使用前面所说的- tftpboot或- nfs将Ubuntu中的U-Boot镜像拷贝到我们的RAM中,假设拷贝到了0x80000000处。然后我们要查看镜像的大小,假设镜像的大小为1800,则1800/512=3.51,即占据4个块的大小。注意命令的参数都是16进制。我们就可以调用下面命令对镜像进行烧录:
mmc dev 1 0
mmc write 80000000 2 4  # I.MX系列的启动头的前1024字节(前2个块)存放启动头信息,所以从第2块烧录
mmc partconf 1 1 0 0    # EMMC需要执行此指令来让分区1(参数1)可以被识别(参数2)
-  mmcinfo:显示MMC信息。
  
-  i2c:I2C子系统命令,U-Boot支持与I2C设备进行通信
  
-  sf:SPI Flash子系统命令。
  
-  usb:USB子系统命令。
  
-  usbboot:从USB设备启动。- 格式为:usbboot loadAddr dev:part
 
- 格式为:
2.8 其他指令
- ?:帮助命令的别名。
- base:打印或设置地址偏移量。
- bmp:操作BMP图像数据。
- clocks:显示时钟信息。
- coninfo:打印控制台设备和信息。
- crc32:计算校验和。
- dcache:启用或禁用数据缓存。
- dm:驱动模型低级访问。
- echo:将参数打印到控制台。
- erase:擦除FLASH存储器。
- exit:退出脚本。
- false:什么也不做,返回失败。
- flinfo:打印FLASH存储器信息。
- fuse:Fuse子系统。
- go:从指定地址开始执行应用程序。类似汇编- LDR PC, =地址。
- gpio:查询和控制GPIO引脚。
- help:打印命令描述/用法。
- icache:启用或禁用指令缓存。
- iminfo:打印应用程序映像的头信息。
- imxtract:提取多重映像的一部分。
- itest:根据整数比较返回真或假。
- loadb:通过串行线加载二进制文件(Kermit模式)。
- loads:通过串行线加载S-Record文件。
- loadx:通过串行线加载二进制文件(Xmodem模式)。
- loady:通过串行线加载二进制文件(Ymodem模式)。
- loop:在地址范围上进行无限循环。
- mdio:MDIO实用命令。
- mii:MII实用命令。
- nm:内存修改(常量地址)。
- pmic:电源管理IC(PMIC)。
- protect:启用或禁用FLASH写保护。
- reset:执行CPU复位。
- run:在环境变量中运行命令。
- sleep:延迟一段时间。
- source:从内存中运行脚本。
- test:类似于/bin/sh的最小测试。
- true:什么也不做,返回成功。



















