第1章 引言
1.1 嵌入式Linux系统概述
单片机,以其高集成、强可控的特点,一直以来都在产业界和人们的日常生活中扮演着重要角色,发挥着越来越大的作用。
这些年,随着芯片的集成程度不断提高,单片机的功能越发强大,单片机系统已经逐步从传统的裸机应用向嵌入式Linux系统转换,很像之前手机从功能机向智能机的跨越,有种改朝换代的感觉。
使用嵌入式Linux系统的最大好处,就是智能机与功能机的差别:应用与硬件解耦,系统引入了更多的开放性。这样,无论是开发者还是用户,都会受益匪浅。
比如,得益于Linux系统的高度开放性,嵌入式Linux系统可以部署到各种各样的硬件架构上,包括树莓派、香橙派这样的准PC,或者采用openwrt的路由器,又或者是本文的主角:基于ZYNQ7000系列芯片的开发板。
在Linux操作系统之上,我们就可以开发和安装各式各样的应用软件,灵活满足用户的需求。
当然,由于硬件架构众多,部署嵌入式Linux系统就成为极具挑战性的任务。在部署任务中,上电后的第一项任务——启动,尤为关键。大家都知道,良好的开端是成功的一半,只有设备正常启动了,才能取得开门红,为后续的应用开个好头。
俗话说得好:“开门七件事,柴米油盐酱醋茶”。嵌入式Linux系统的启动并不是简单的两个字,启动是一个过程,经历很多的步骤,是一环扣一环的。
另外,启动工作的具体环节与硬件架构密切相关,后续的内容将基于ZYNQ7000系列芯片开发板,详细介绍开发板的启动过程以及如何去操控启动过程。
附带讲一下,嵌入式Linux系统的启动过程还是有很多共通之处,学会了基于ZYNQ7000系列芯片的启动过程,其他硬件架构的启动过程也就豁然开朗了。
1.2 ZYNQ7000系列芯片介绍
ZYNQ7000系列芯片是赛灵思(Xilinx,目前已经被AMD收购)研发的产品,赛灵思以研发高性能的FPGA产品而知名。
ZYNQ7000系列芯片是赛灵思研发的一款具有里程碑意义的产品,首次将高性能的ARM处理器系统(PS,Processing System)与FPGA可编程逻辑(PL,Programmable Logic)紧密集成在单颗芯片,完美地实现了处理器的顺序执行能力和FPGA的并行处理能力的融合。
ZYNQ7000系列芯片具有相同的ZYNQ芯片架构,系列中包含多种型号的芯片,对应不同的硬件规模和芯片性能,以满足不同应用的需求和成本考量。
1.2.1 芯片特色
ZYNQ-7000系列芯片的本质是一个以处理器为中心的可编程SoC,因此芯片首先是一个强大的应用处理器,可以运行像Linux这样的复杂操作系统。其次芯片拥有一个FPGA,可以作为定制硬件加速器或专用外设。
传统的“FPGA+外挂CPU”方案存在通信延迟大、功耗高、PCB设计复杂等缺点。ZYNQ的设计者将FPGA与处理器两者置于同一芯片上,通过高速内部总线互联,实现了高带宽、低延迟的协同工作,特别适用于需要大量数据预处理、实时控制、硬件加速的应用场景。
1.2.2 体系结构:PS + PL
ZYNQ芯片由PS和PL两大体系结合而成,这是理解ZYNQ芯片的关键,其结构可以清晰地分为两大部分:
1. 处理器系统 - PS
PS是一个完整的、独立的ARM Cortex-A9处理器系统,即使不使用PL部分,它也能作为一颗标准的应用处理器工作。其主要组成部分包括:
- 中央处理器:
- 单核或双核ARM Cortex-A9 MPCore,主频最高可达1GHz;
- 每个核心拥有32KB指令和32KB数据一级缓存,共享512KB二级缓存;
- Cortex-A9是一款经典的32位处理器核心,使用ARMv7-A架构指令集;
- 集成了NEON协处理器和FPU浮点单元,支持硬件加速计算。
- 存储器接口:
- 支持多种标准的DDR控制器(如DDR3, DDR2, LPDDR2);
- 静态存储器控制器,支持NAND Flash、NOR Flash、SRAM等。
- 自带丰富的外设接口:
- 通用外设(SPI, I2C, CAN, UART, GPIO等);
- 高速接口(USB 2.0 (OTG), Gigabit Ethernet, SD/SDIO)。
- 这些外设大大降低了设计复杂度,无需在PL中重新实现。
- 互联基础架构:
- 包括AMBA总线、存储器交换网络、外设交换网络等,用于连接PS内部的各个模块。值得一提的是AMBA总线,这是ARM公司提出的一套开放的、用于连接芯片内部模块的通信标准,定义了这些模块之间如何相互通信和数据传输。
另外,ZYNQ支持多种启动方式,包括利用Flash、SD、JTAG启动。
2. 可编程逻辑 - PL
PL部分本质上就是一个传统的Xilinx 7系列FPGA,其逻辑资源根据具体型号有所不同。主要包括:
- 可配置逻辑块:CLB,由查找表(LUT)和触发器(Flip-Flop)组成,实现基本的组合和时序逻辑功能;
- 块RAM:BRAM,每个36Kb的分布式RAM模块,可用于数据缓存;
- 数字信号处理单元:用于实现高性能的乘法、乘加操作,是数学运算和信号处理算法的关键;
- 时钟管理单元:CMT,包含PLL和MMCM,用于时钟生成、去偏斜和频率综合;
- 可编程I/O:支持多种I/O标准,如LVCMOS, LVDS, SSTL等。
3. PS与PL的互联:关键优势
PS和PL之间通过多种高性能的AXI(Advanced eXtensible Interface)总线连接,这是ZYNQ架构的精髓。主要包括:
- 4个高性能AXI_HP接口:用于PL高速访问PS端的DDR存储器。
- 1个加速器一致性ACP端口:允许PL以缓存一致的方式访问PS,使得PL作为加速器时效率极高。
- 4个通用AXI_GP接口:用于PS和PL之间的一般性控制信号传输,例如处理器配置PL中的寄存器,或者PL向处理器发送中断信号。
这种丰富的互联结构保证了数据在处理器和可编程逻辑之间能够高效、低延迟地流动。
1.2.3 芯片系列
ZYNQ-7000系列芯片提供了多种硬件规模和性能的型号,以满足不同应用的需求和成本考量。
首先看PS处理器,分为如下两类:
- Zynq-7000S:单核Cortex-A9,较小规模PL,主打低成本、低功耗市场。
- Zynq-7000:双核Cortex-A9,PL规模从少到多,性能强,是系列中的主流产品,应用范围最广。
因此我们一般选用双核的ZYNQ-7000系列芯片。
其次看PL,也就是FPGA部分。
双核ZYNQ-7000系列芯片主要有Z-7010、Z-7015、Z-7020、Z-7030、Z-7035、Z-7045和Z-7100等型号,最后的数字代表了PL部分的规模大小。数字越大,逻辑资源(CLB、DSP、BRAM)越多,性能越强,价格也越高。
下表列出了三种典型型号Z-7010、 Z-7020和Z-7045的硬件规模:
| 资源类型 | Z-7010 | Z-7020 | Z-7045 |
|---|---|---|---|
| 逻辑单元 | 28K | 85K | 350 |
| 查找表 (LUTs) | 17,600 | 53,200 | 218,600 |
| 触发器 (FFs) | 35,200 | 106,400 | 437,200 |
| 块RAM (BRAM) | 60块 | 140块 | 545块 |
| 数字信号处理单元 | 80 | 220 | 900 |
| 最大I/O 引脚数 | 100 | 200 | 362 |
初学者推荐选择Z-7010或者Z-7020,性价比高。
1.2.4 开发板
市面上有很多采用ZYNQ-7010或者ZYNQ-7020的芯片的开发板,这里我们推荐两款:
1. EBAZ4205
EBAZ4205 是一款基于Xilinx Zynq-7010芯片的矿机主控板,由于矿机退役,被大量拆机流入市场,经过商家改造后,售价通常在百元级别,成为性价比极高的嵌入式学习开发板。
该板卡配备了256MB的DDR3 内存和128MB的NAND Flash,还配备了100M 的以太网口以及SD卡座。另外,该板卡还有多个插座,用于引出ZYNQ-7010的各种管脚,主要是PL端的GPIO端口。
该板卡支持运行 Linux 系统及自定义 FPGA 逻辑设计。
EBAZ4205的缺点是需要外加扩展板,才能实现供电以及提供USB接口。另外一个缺点是无法切换启动方式,在学习嵌入式Linux系统的启动时很不方便。
在本项目中,EBAZ4205主要作为验证板使用,用于验证学习的成果,不作为学习板使用,学习板使用下面介绍的R210。
2. R210
R210是一款基于Xilinx Zynq-7020芯片的软件无线电开发板,主要由Zynq-7020芯片和AD9361芯片组成,与ADI公司的ADALM Pluto基本兼容,价格在千元级别,是一款性价比较高的软件无线电开发板。
作为软件无线电的开发板,R210提供了2 收2发的射频SMA端口,其工作频率范围可达70MHz~6GHz,满足各种双通道无线电系统的需要。这一点,也是R210显著优于Pluto之处。
R210的其他优点包括提供了更为丰富的外部接口,例如千兆的网口、SD卡座以及两个TypeC的USB接口。这两个USB接口都可以作为供电端口,通常我们选择右侧这个USB接口供电。由于R210的功耗较大,因此电源输出需要5V/1A以上。
R210的硬件配置为512MB的DDR3 内存和32MB的NAND Flash,因此可以支持嵌入式Linux系统的启动。
在R210的最右侧,有一个2位的拨码开关,可以灵活地切换启动模式:SD启动、Flash启动或者JTAG启动,这为学习嵌入式Linux系统的启动提供了非常大的方便。
后续的章节我们就在R210开发板上,开始嵌入式Linux系统启动的学习过程。
1.3 后续内容
本书后续内容将围绕R210开发板,详细介绍SD启动以及Flash启动的具体步骤、工作原理以及制作方法。
其中第二章介绍基于分区的SD启动;第三章介绍RAMdisk的SD启动;第四章介绍Flash启动;第五章,介绍统一启动。
本书面向初中以上学历、对计算机操作比较熟悉的读者。编写的内容详尽,只要按照本书介绍的内容,读者应该都能学会嵌入式Linux系统的启动方法。
第2章 深度研究基于分区的SD启动
2.1 概述
R210作为基于ZYNQ7000系列的SDR板卡,支持SD、QSPI和JTAG等3种启动模式,QSPI就是Flash启动。
其中SD启动是R210的出厂标配,这种启动方式借鉴了树莓派的启动特点,将嵌入式Linux系统存放在TF卡上,可以把TF卡看成一个硬盘。
在树莓派系统中,TF卡做成两个分区,一个分区是启动分区,放置了启动文件。这个分区采用FAT的文件系统,可以通过windows系统访问,方便进行配置;另外一个分区是EXT4文件分区,运行树莓派的Raspberry Pi OS,也就是一种Linux系统。
R210的TF卡也是两个分区,一个FAT分区放置启动文件,当然这些启动文件与树莓派的完全不同;另外一个分区是EXT文件分区,运行嵌入式Linux系统。
本章将详细讲解R210基于分区的SD启动,并介绍相关的操作步骤。以下列出的是操作步骤用到的工具与命令
【本章windows工具】
树莓派镜像烧写工具、xshell、bootinfo
2.2 制作TF启动卡
前面说了,R210的TF启动源自树莓派,所以可以采用与树莓派一样的启动卡制作方式,也就是通过烧写镜像来制作TF启动卡。
为此,我们首先到https://www.raspberrypi.com/software/下载树莓派的镜像烧写工具:Raspberry Pi imager,然后安装Raspberry Pi imager,选择I accept the agreement,指定安装路径,点击next进行安装。
接下来下载R210的系统镜像: 2019_R1-2020_06_22.zip(厂商提供)。随后找一张16GB或更大的空闲TF卡,插入电脑,运行树莓派的镜像烧写工具,设置R210系统的烧录参数。
下面3张图片展示了烧录参数的设置过程,其中“树莓派设备”项不用选择,“操作系统”项先选择“使用自定义镜像”,再选择刚刚下载的R210的系统镜像,“存储设备”选择对应的TF卡。
当出现如下图的界面时,点击下一步。
点击最右边的否,然后启动烧录过程。
烧录过程需要运行5到10分钟。烧录完成后,将TF卡重新插拔,就可以在windows系统中看到boot分区。将boot分区NeptuneSDR P210目录下的全部文件复制到根目录,就完成了TF启动卡的制作。
2.3 启动后查看TF卡的分区
制作完TF启动卡后,在启动R210之前,我们还要安装终端工具xshell,通过电脑远程操作R210的嵌入式Linux系统。
启动R210时先将R210的拨码开关打到SD启动,插入做好的TF启动卡,再用Type-C数据线连接R210的UART口与电脑的USB口,R210开始上电启动。
接着在电脑中找到新增的串口,记录串口的端口号。在xshell中增加一个会话,采用串口连接,波特率设为115200。
会话建好后,点击连接,就会出现终端界面。
在终端界面中输入回车键,如果看到如下的提示符,代表连接r210嵌入式Linux系统成功,已经以root用户成功连接到了R210:
root@analog: ~#
接下来查看分区情况:
root@analog:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT mmcblk0 179:0 0 14.9G 0 disk ├─mmcblk0p2 179:2 0 6.4G 0 part / ├─mmcblk0p3 179:3 0 1M 0 part └─mmcblk0p1 179:1 0 1G 0 part root@analog:~# df -h Filesystem Size Used Avail Use% Mounted on /dev/root 6.2G 3.1G 2.8G 53% /
不难看出TF卡的第二分区挂载为系统的根目录,这也就是分区启动的含义。我们还可以输入mount命令,将第一分区挂载到/boot目录:
root@analog:~# mount /dev/mmcblk0p1 /boot
查看/boot目录的内容,不难发现与windows系统中看到的BOOT分区根目录的内容是一致的。
root@analog:/boot# cd /boot root@analog:/boot# ls -l total 9212 -rwxr-xr-x 1 root root 4681552 Sep 11 2022 BOOT.BIN drwxr-xr-x 2 root root 4096 Mar 11 2023 NeptuneSDR K210 drwxr-xr-x 2 root root 4096 Mar 11 2023 NeptuneSDR P210 drwxr-xr-x 2 root root 4096 Aug 8 2025 System Volume Information -rwxr-xr-x 1 root root 16492 Sep 11 2022 devicetree.dtb -rwxr-xr-x 1 root root 392 Feb 4 2020 uEnv.txt -rwxr-xr-x 1 root root 4704936 Oct 29 2024 uImage
附带说一下,输入Linux命令需要区分大小写,命令与参数之间需要有空格。
2.4 启动相关文件
在TF卡BOOT分区的根目录中,有4个文件与启动相关:BOOT.BIN、uEnv.txt、devicetree.dtb和uImage,以下分别介绍:
2.4.1 BOOT.BIN
BOOT.BIN是ZYNQ启动的第一个文件,其内容是多个分区镜像(二进制文件)的组合,这些分区镜像包括fsbl、FPGA比特流和u-boot,其中fsbl是ZYNQ启动过程的关键代码,FPGA比特流用于初始化ZYNQ的PL部分(也就是FPGA),u-boot用于加载嵌入式Linux系统。
BOOT.BIN利用文件头来定义多个分区镜像的结构,下图展示了文件头的结构,其中比较重要的数据结构有启动头(Boot Header)、镜像头表(IMAGE HEADER TABLE)、分区头表(PARTITON HEADER TABLE)。
可见BOOT.BIN中镜像与分区一一对应,构成镜像/分区对,镜像头表给出了镜像文件的名称,分区表给出了镜像文件的存放位置、大小以及加载位置。
BOOT.BIN可以有多个镜像/分区对,利用bootinfo工具可以解析BOOT.BIN的结构参数。将bootinfo.exe和BOOT.BIN两个文件复制到合适的目录,在命令提示符中输入bootinfo BOOT.BIN,就可以看到BOOT.BIN的结构参数:
其中的分区头表展示了镜像文件的关键信息,摘录如下:
PARTITION HEADER TABLE (fsbl.elf)
--------------------------------------------------------------------------------
encrypted_length (0x00) : 0x00006002 unencrypted_length (0x04) : 0x00006002
total_length (0x08) : 0x00006002 load_addr (0x0c) : 0x00000000
exec_addr (0x10) : 0x00000000 partition_offset (0x14) : 0x000005c0
attributes (0x18) : 0x00000010 section_count (0x1C) : 0x00000001
checksum_offset (0x20) : 0x00000000 iht_offset (0x24) : 0x00000240
ac_offset (0x28) : 0x00000000 checksum (0x3c) : 0xfffed7e8
attribute list -
trustzone [non-secure] el [el-0]
exec_state [aarch-32] dest_device [none]
encryption [no] core [none]
--------------------------------------------------------------------------------
PARTITION HEADER TABLE (system_top.bit)
--------------------------------------------------------------------------------
encrypted_length (0x00) : 0x000f6ec0 unencrypted_length (0x04) : 0x000f6ec0
total_length (0x08) : 0x000f6ec0 load_addr (0x0c) : 0x00000000
exec_addr (0x10) : 0x00000000 partition_offset (0x14) : 0x000065d0
attributes (0x18) : 0x00000020 section_count (0x1C) : 0x00000001
checksum_offset (0x20) : 0x00000000 iht_offset (0x24) : 0x00000250
ac_offset (0x28) : 0x00000000 checksum (0x3c) : 0xffd14b7e
attribute list -
trustzone [non-secure] el [el-0]
exec_state [el-0] dest_device [none]
encryption [no] core [none]
--------------------------------------------------------------------------------
PARTITION HEADER TABLE (u-boot.elf)
--------------------------------------------------------------------------------
encrypted_length (0x00) : 0x0001fbc8 unencrypted_length (0x04) : 0x0001fbc8
total_length (0x08) : 0x0001fbc8 load_addr (0x0c) : 0x04000000
exec_addr (0x10) : 0x04000000 partition_offset (0x14) : 0x000fd490
attributes (0x18) : 0x00000010 section_count (0x1C) : 0x00000001
checksum_offset (0x20) : 0x00000000 iht_offset (0x24) : 0x00000260
ac_offset (0x28) : 0x00000000 checksum (0x3c) : 0xf7ea35a6
attribute list -
trustzone [non-secure] el [el-0]
exec_state [aarch-32] dest_device [none]
encryption [no] core [none]
--------------------------------------------------------------------------------
这其中,total_length(代码长度)、load_addr(加载地址)、exec_addr(执行地址)和partition_offset(代码偏移)几个字段的内容最为重要。
我们可以从分区头表中提取分区文件如下表的关键参数,并作出BOOT.BIN文件的结构表(注意代码偏移和长度在分区头表中都是以字为单位,转换为字节数要乘以4)
| 镜像文件 | 代码偏移 | 代码长度(Bytes) | 加载地址 | 执行地址 |
|---|---|---|---|---|
| fsbl.elf | 5888 (4*0x5c0) | 98312 (4*0x6002) | 0x0000000 | 0x0000000 |
| system_top.bit | 104256 | 4045568 | 0x0000000 | 0x0000000 |
| u-boot.elf | 4149824 | 531728 | 0x4000000 | 0x4000000 |
因此,boot.bin中包含了3大功能模块,2.5节将详细介绍这三大功能模块的具体作用,这里只简单描述一下名称:
- FSBL.elf的FSBL是第一阶段引导加载器的缩写;
- system_top.bit 是FPGA的比特流;
- u-boot.elf 的u-boot 是一个通用的引导加载器;
最后,我们得到如下的BOOT.BIN文件的结构图:
2.4.2 uEnv.txt
uEnv.txt是个文本文件,存放有u-boot使用的环境变量,具体内容如下:
uenvcmd=run adi_sdboot
adi_sdboot=echo Copying Linux from SD to RAM... && fatload mmc 0 0x3000000 ${kernel_image} && fatload mmc 0 0x2A00000 ${devicetree_image} && if fatload mmc 0 0x2000000 ${ramdisk_image}; then bootm 0x3000000 0x2000000 0x2A00000; else bootm 0x3000000 - 0x2A00000; fi
bootargs=console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlycon rootfstype=ext4 rootwait cpuidle.off=1
2.4.3 devicetree.dtb
devicetree.dtb是一个二进制文件,是系统设备树信息的编译结果。设备树指的是嵌入式Linux系统所支持的硬件设备,相当于R210的硬件信息。
devicetree.dtb由u-boot加载。
2.4.4 uImage
uImage是一个二进制文件,包括一个64字节长度的头以及一个压缩的嵌入式Linux内核镜像。
文件头中包含操作系统类型、CPU 架构、镜像类型、压缩类型和镜像名称等字段内容。
内核镜像包含了各种硬件设备的驱动,同样由u-boot加载。
2.5 基于分区的启动过程
2.5.1 启动过程图
ZYNQ芯片的嵌入式Linux系统启动过程如下图所示,分为ROM启动→FSBL→U-Boot→内核运行等四个阶段,以下就按照启动过程的顺序,详细介绍每一阶段的运行机制。
2.5.2 ROM启动阶段
ROM启动阶段是启动的第一步,所谓的ROM指的是固化在ZYNQ芯片内部的不可修改代码,负责最基础的硬件初始化和加载FSBL(第一阶段引导加载器),以下用BootROM来描述。
BootROM完成以下功能:
- 检测启动模式(SD启动):上电后,BootROM根据拨码开关(对应MIO3-5引脚)配置启动模式(如QSPI、SD、JTAG等模式)。由于我们将拨码开关打到了SD启动,BootROM进入SD启动模式。
- 初始化SD卡设备:BootROM使能SD卡控制器的时钟,复位TF卡,并进行读取卡唯一标识等工作。
- 读取MBR分区表:BootROM加载TF卡第0扇区的MBR(主引导记录)并解析出TF卡BOOT分区的参数。
- 访问文件系统:BootROM根据BOOT分区的参数,读取TF卡BPB(引导参数块),获取根目录信息;BootROM遍历TF卡根目录,搜索BOOT.BIN。
- 读取BOOT.BIN:BootROM找到BOOT.BIN后,读取BOOT.BIN最前面的内容到片上内存OCM(On-Chip Memory)中,最前面的内容必须包含完整的FSBL(第一阶段启动加载器)。
- 校验BOOT.BIN:BootROM在OCM中搜索有效的BOOT.BIN文件头。有效文件头的关键字段包括:
- 0x020:固定值0xAA995566(宽度检测)。
- 0x024:镜像ID 0x584C4E58(ASCII为"XLNX")。
- 解析分区:找到有效文件头后,BootROM解析BOOT.BIN头部信息,得到类似于2.4.1节的表格。
- 搬迁FSBL:根据表格的内容,BootROM将R210的BOOT.BIN自第5888字节起,总长度为98312字节的内容搬迁到OCM的地址0x0000000处。
- 加载FSBL:BootROM将跳转到OCM的地址0x0000000处执行FSBL。
既然提到了ZYNQ的OCM,这里就展开讲一下。
ZYNQ同时使用两种内存,一种是片上内存OCM(On-Chip Memory),共256KB,可用的范围为192kB。OCM的优点是高速、低延迟:CPU可直接访问,无需初始化,上电即可使用,主要用于引导过程。
另外一种是片外的DDR内存,容量最大可达1GB,R210配备了512MB,这是操作系统工作的内存。
这两种内存占用同样的地址空间,其中OCM为低端0x00000000 ~ 0x0003FFFF,DDR是0x00100000以上,也就是1MB以上。
2.5.3 FSBL阶段
BOOTROM结束后,就进入了FSBL阶段。FSBL是用户可修改的启动加载程序,主要完成更复杂的硬件初始化和后续镜像加载,主要功能如下所示:
- 初始化PS端:通过ps7_init()函数配置MIO、PLL、时钟和DDR控制器等。
- 检测启动设备:读取BOOT_MODE寄存器确认启动方式(如SD启动),并初始化对应的外设。
- 解析BOOT.BIN:FSBL进一步解析BOOT.BIN中的分区表(Partition Header Table),获取U-Boot以及FPGA比特流的地址、大小等信息。
- 加载FPGA比特流:将FPGA比特流从BOOT.BIN拷贝到DDR内存中,并通过PCAP接口将FPGA的比特流(.bit文件)配置到PL端。
- 加载U-Boot:将U-Boot镜像从BOOT.BIN拷贝到DDR内存中,并跳转到U-Boot的入口地址。
2.5.4 U-Boot阶段
FSBL结束后,就进入了U-Boot阶段,这里详细一下介绍前面多次遇到过的U-boot。
首先,这个U代表通用的意思。引入U-Boot这个中间件后,可以增强启动的灵活性,这样就可以支持各种芯片架构、各种操作系统以及各种外设的启动了。比如openwrt,就采用了U-Boot;ZYNQ芯片,同样也采用了U-Boot。
具体而言,为了加载Linux操作系统内核,U-Boot需要完成如下工作:
- 外设驱动:初始化串口、网络、文件系统等,为内核启动做准备。
- 环境变量:读取环境变量文件uEnv.txt的内容,覆盖默认环境变量。
- 加载内核:从存储介质(TF卡)加载Linux内核(uImage)和设备树(.dtb)。
- 设置启动参数:启动参数即bootargs,关键参数包括控制台和根文件系统的设置。
- 启动内核:跳转到内核入口,完成启动过程。
U-Boot启动过程与环境变量文件uEnv.txt密切相关,文件中定义了环境变量以及命令脚本。以R210的uEnv.txt为例,以下两行是环境变量的定义:
uenvcmd=run adi_sdboot bootargs=console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlycon rootfstype=ext4 rootwait cpuidle.off=1
uenvcmd代表U-Boot运行后需要执行的命令脚本;bootargs代表U-Boot加载系统内核时,向系统内核传递的启动参数,其中列出了根系统所在的启动分区以及分区的文件系统。
而adi_sdboot则定义了命令脚本的具体任务,这些任务要通过U-Boot的命令来执行:
adi_sdboot=echo Copying Linux from SD to RAM... && fatload mmc 0 0x3000000 ${kernel_image} && fatload mmc 0 0x2A00000 ${devicetree_image} && if fatload mmc 0 0x2000000 ${ramdisk_image}; then bootm 0x3000000 0x2000000 0x2A00000; else bootm 0x3000000 - 0x2A00000; fi
命令脚本中需执行多个u-Boot命令,里面的&&连结这些命令,代表只有前一个命令执行成功,才执行下一个命令。
其中一个关键的U-Boot命令是fatload,这个命令用于加载FAT文件系统的文件到DDR内存中,因此:
fatload mmc 0 0x3000000 ${kernel_image}就是将mmc设备(TF卡)第0分区上的kernel_image这个内核镜像文件加载到DDR内存地址0x3000000。
kernel_image的缺省文件名为uImage,这是在U-Boot中已经预先设定了,当然我们也可以在uEnv.txt中进行更换,比如加一行:kernel_image=uImage_r210。
由于TF启动卡的第0分区也就是BOOT分区,该分区格式化为FAT32的文件系统,因此只要kernel_image 文件在根目录,fatload就可以顺利读取并加载。
同样地,adi_sdboot中还加载了devicetree_image(缺省文件名为devicetree.dtb)到DDR内存地址0x2A00000。
加载完内核镜像以及设备树之后,就可以用bootm命令启动内核了。
bootm的命令有三个参数,分别是内核镜像地址、ramdisk镜像地址和设备树地址。因此,脚本最后还做了一个逻辑判断,如果成功加载了ramdisk_image(缺省文件名为uramdisk.image.gz),就用bootm的三个参数启动内核;如果没有加载成功,就用bootm的两个参数启动内核。
由于基于分区的SD启动没有uramdisk.image.gz文件,因此R210启动时只需要加载uImage和devicetree.dtb两个文件,启动内核的命令是:bootm 0x3000000 - 0x2A00000。
总结一下,在bootm执行前,DDR内存中程序的分布如下图所示:
注意R210的DDR内存为512MB,对应的地址空间为0x00100000~0x1FFFFFFF。
2.5.5 内核运行阶段
内核运行也分为多个阶段进行,包括内核解压与启动、挂载根文件系统和运行系统服务。
2.5.5.1 内核解压与启动
- U-Boot将控制权转交给内核uImage
- uImage解压自身
- 初始化基本硬件:CPU、内存、中断控制器等
- 加载解析设备树
- 解析并展开设备树
- 识别PS端设备和PL端IP核
- 根据设备树注册硬件设备
2.5.5.2 挂载根文件系统
- 内核根据启动参数bootargs中的root=参数挂载文件系统。R210是/dev/mmcblk0p2,也就是TF卡的第2个分区。
- 挂载后执行根文件系统中的/sbin/init脚本。R210的init脚本链接到了/lib/systemd/systemd,也就是运行systemd。
- systemd启动系统服务。
2.5.5.3 运行系统服务
- systemd从/etc/systemd/system.conf获取systemd的配置
- systemd从/usr/lib/systemd/system/获取系统默认服务
这样,嵌入式Linux系统的启动过程就结束了。
最后,我们可以输入lsb_release -a命令,查看发行版信息:
root@analog:/ # lsb_release -a
No LSB modules are available.
Distributor ID:Raspbian
Description:Kuiper GNU/Linux 11.2 (bullseye)
Release:11.2
Codename:bullseye
说明R210的嵌入式Linux系统还是版本比较新的,相当于Ubuntu20.04的版本。
2.6 系统配置
嵌入式Linux系统第一次启动后,通常我们还需要进行系统配置,包括设置IP地址、主机名以及登录的欢迎词等工作。
Linux通过修改系统文件的方式来配置系统,我们用vi来修改文件内容。
2.6.1 设置IP
- 查看以太网卡名称
- 修改interfaces文件
- 修改DNS服务器地址
输入ifconfig命令,可以查看以太网卡名称。
图中,以太网卡的名称为eth0。
输入命令 vi /etc/network/interfaces,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。
DNS服务器地址由/etc目录下的resolv.conf文件存储,但这个文件在重启后会恢复默认设置,且该文件无法用下文提到的保护手段防止覆盖。为保证DNS配置永久生效,我们可以采用下面的方法:
输入命令rm /etc/resolv.conf删除原有的resolv.conf文件。
输入命令 vi /etc/resolv.conf,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。
输入命令chattr +i /etc/resolv.conf,锁定/etc/resolv.conf防止意外修改。
现在重启后,DNS服务器配置仍能生效,下图可见重启后能ping通外网。
2.6.2 设置主机名
输入命令:hostnamectl set-hostname r210,这里的r210为新的主机名。
修改/etc/hosts文件,使其配置与新的主机名一致。输入命令 vi /etc/hosts,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。
重启后修改生效。可通过命令hostnamectl查看主机名。
2.6.3 设置欢迎词
登录的欢迎词由/etc/motd文件来配置。输入命令 vi /etc/motd,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。
重启后可见欢迎词。
2.7 小结
本章详细介绍了ZYNQ芯片嵌入式Linux系统基于分区的SD启动过程,详细分析了启动相关文件的功能,并重点解析了BOOT.BIN文件的结构。
本章还详细讲解了使用树莓派的镜像烧写工具制作TF启动卡的过程,并介绍了用终端工具xshell访问嵌入式Linux系统的方法。
本章最后介绍了使用vi更改系统文件,进行系统配置的方法。
【本章Linux命令】
cd、ls -l、mount、vi、df -h、lsblk、lsb_release -a。
| 命令名称 | 说明 |
|---|---|
| cd | 改变目录 |
| ls -l | 查看目录文件详细列表 |
| mount | 挂载分区 |
| vi | 文本文件编辑器 |
| df -h | 查看文件系统磁盘空间使用情况 |
| lsblk | 查看块设备信息 |
| lsb_release -a | 查看系统发行版信息 |