LV05-06-系统烧写-01-shell脚本烧写系统到SD卡

本文主要是系统烧写——在ubuntu中通过shell脚本制作一个sd卡启动盘的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、有哪些文件?

前面我们学习linux系统组件,知道它包括uboot、kernel、设备树、根文件系统,我们前面做完移植,得到了下面这些文件。

image-20241110114412706

前面我们将uboot的imx文件烧写到sd卡中,从tftp下载zImage和dtb,从nfs挂载根文件系统,那么我们怎么脱离网络启动?我们从这一节开始来详细学习一下系统的烧写。

二、怎么给SD卡分区

首先,我们来学习一下SD卡分区,后面会知道为什么要先学习这个了。这里我们在ubuntu中操作。

1. 查看SD卡信息,确定挂载点

1.1 插入sd卡

首先我们要将SD卡插入PC电脑,保证vmware中的ubuntu识别到了sd卡,这个一般都没啥问题。

1.2 相关命令

1
2
3
fdisk -l # 查看系统真实存在的设备,也就是说,是一直处于动态监控设备状态,只要有一个设备‘消失’,它都会显示。
cat /proc/partitions # 系统已识别的设备
df -h

注意: 查看系统真实存在的硬盘设备的时候

  • 查看系统真实存在的硬盘设备,看到的不一定是系统识别的
  • 存在的,不一定是识别的
  • 识别的,不一定是使用的
  • 使用的,不一定是正在用的
  • 系统真实存在的不一定能被识别,能识别的不一定能被用,能用的不一定是正在用

1.3 确定挂载点

然后我们通过下面的命令查看一下sd信息:

1
sudo fdisk -l

然后会有如下打印信息:

image-20241110125140902

可以看到SD卡的扇区大小是512字节,挂载点是sdc。sd卡挂载之后的节点一般都是sd开头的,我们可以在/dev下看到对应的节点:

1
ls /dev/sd*
image-20241110125156470

还有一个命令,可以直接打印出系统识别到的设备:

1
cat /proc/partitions
image-20241110125213942

反正最后能确定挂载点就可以了。

2. 卸载SD卡

在进行分区操作时,我们需要先卸载SD卡:

1
umount /dev/sdx

上面是sdc,所以就是:

1
umount /dev/sdc
image-20241110125231547

要是没挂载就算了。

3. 分区操作

3.1 进入分区菜单

1
sudo fdisk /dev/sdc
image-20241110125253584

3.2 m——查看命令菜单

1
m
image-20241110125346905

3.3 p——查看当前SD卡状态

1
p
image-20241110125508014

可以看到我这里SD卡有没有分区,在进行SD卡分区前,我们要删除所有分区。这个删除操作后面再说。

3.4 创建分区

1
2
3
4
5
n    # 新建一个分区
p # 使该分区为主分区
1 # 输入分区号,这里使用1
2048 # 输入第一个扇区大小2048
+10M # 输入最后一个扇区大小,这里使用+10M,表示直接给该分区预留10MB
image-20241110130013750

然后我们再用p看一下sd卡当前状态:

image-20241110130147577

可以看到这里产生了一个sdc1节点,大小为10M,这就是我们刚才制作的10M的分区。

3.5 设置分区类型

上面已经创建好分区了,接下来就是设置分区的类型:

1
2
3
4
5
6
t # 设置分区类型
c #设置分区类型,至于这里为什么输入c,可以在设置分区类型的时候,按L查看不同分区格式对应的命令。我这里选择FAT32格式,根据自己需求选择即可

# 分区类型设置完成后,就可以看到系统打印出了相关的提示信息

a # 设为引导分区
image-20241110130737351

然后我们再查看一下分区的结果:

image-20241110130836816

3.6 写入分区

前面我们的分区实际上还并未生效,我们要输入w命令才可以:

1
w # 写入分区
image-20241110131025774

输入完成后会直接退出fdisk的菜单界面。

3.7 格式化分区

我们下来看一下我们分区好后的节点情况:

1
ls /dev/sd*
image-20241110131146349
1
sudo mkfs.vfat -F 32 -n boot /dev/sdc1
  • mkfs.vfat:这是一个用于创建 VFAT 文件系统的命令。VFAT 是 FAT 文件系统的一个变种,它支持长文件名,通常用于 USB 闪存驱动器、SD 卡等存储设备。尽管名为 vfat,这个命令也可以用来创建标准的 FAT12、FAT16 和 FAT32 文件系统。
  • -F 32:这个选项指定了要创建的 FAT 文件系统的类型。数字 32 表示创建一个 FAT32 文件系统。FAT32 支持更大的分区和文件大小,通常用于大于 2GB 的分区。
  • -n boot:这个选项用来设置文件系统的卷标(volume label)。在这个例子中,卷标被设置为 boot。卷标是一个简短的文本字符串,用于在文件系统中标识分区,通常在挂载点、文件浏览器等地方显示。
  • /dev/sdc1:这是指定要格式化的分区的设备文件。在 Linux 系统中,硬盘和分区通常以 /dev/sdX 的形式表示,其中 X 是字母,表示设备(如 sda 表示第一个硬盘),数字(如 1)表示分区号(如 sda1 表示第一个硬盘的第一个分区)。在这个例子中,/dev/sdc1 表示第三个硬盘的第一个分区。
image-20241110131301570

会发现这里报了个警告。

先了解一下了解一下簇的概念和扇区的区别:

簇和扇区不是属于一个范畴。簇是系统在硬盘上读写文件时的单位,是一个数据块。而扇区是硬盘划分的最小单位值,就是簇(数据块)占用的地方。

簇的大小大于扇区的大小。硬盘每簇的扇区数与硬盘的总容量大小有关。扇区是存储介质上可由硬件寻址的基本单位,x86系统几乎总是定义512字节的扇区大小;簇是许多文件系统格式使用的可寻址数据块,簇的大小总是扇区大小的整数倍,且不同文件系统对于不同大小的卷(分区)会有不同的默认簇大小,比如FAT32对于8GB以下的分区,默认簇大小为4KB,对于8GB ~ 16GB的分区,默认簇大小为16个扇区(8KB),NTFS对于大于2GB的分区,默认簇大小为8个扇区(4KB)。
打个比方,我们(系统)要在仓库(仓库可视为硬盘)里存放一些书(数据)。我们先把书分门别类放到一些大纸箱(簇)里,然后放进仓库,纸箱的体积是根据我们仓库大小来决定的,而仓库始终划分成单位为0.1立方米的小格子(扇区),仓库大了,纸箱就大些,仓库小了,纸箱就小些。

制作fat32文件系统有个最小的簇的个数要求:这里扇区大小为512bytes,经测试当簇大小等于1个扇区时,分区最少需要40MB,当簇大小设为2个扇区时,分区至少需要80MB,40M / 512 = 81920, 80M / 512 / 2 = 81920,计算出制作fat32的分区大小至少需要有81920个簇。

我后面重新创建了一个40M的分区,然后格式化就没问题了:

image-20241110132615593

这里这个这个警告信息是在提醒我们,在使用DOS或Windows系统时,使用小写的标签(labels)可能会导致一些问题或无法正常工作。这通常发生在处理文件系统、批处理脚本、环境变量或其他需要区分大小写的场景中,但DOS和早期版本的Windows(如Windows 95, 98, ME)的文件系统通常是不区分大小写的,而它们的某些命令行工具和环境变量处理却可能是区分大小写的。这里其实不用关心。

3.8 删除分区

上面不是刚好报了警告,我们学习一下怎么删除分区:

1
d # 删除分区
image-20241110132239941

有多个分区?这个会提示我们选择要删除的分区的:

image-20241110132424812

3.9 挂载分区

格式化完成后我们需要将SD卡挂载到系统,直接点击系统边上的U盘图标(要是没有的画,直接拔掉重新插一下就可以了):

image-20241110132904767

可以看到这里的卷标就是boot,使我们刚才设置的。我们还可以用df命令看一下:

1
df -h
image-20241110132956376

我们也可以手动挂载:

1
2
sudo mkdir -p /mnt/sdc1_tmp             # mkdir命令创建一个空目录,用于挂载SD卡上的文件系统。
sudo mount /dev/sdc1 /mnt/sdc1_tmp # 挂载分区
image-20241110133311760

3.10 卸载分区

1
sudo umount <sdcard mount dir>

三、SD卡启动盘制作

1. 参考资料

NXP为我们提供了一份文档,,这个其实位于L4.1.15_2.0.0_LINUX_DOCS (nxp.com.cn)这个压缩包内

image-20241110133753211

我们下载下来解压就可以看到:

image-20241110134037145

里面有一章在介绍这个:

image-20241110134754488

这里有一份在线的,i.MX Linux User’s Guide (nxp.com),但是不是这个4.15版本,不过都差不多。

2. SD卡分区情况说明

先来看一下i.MX6ULL使用SD卡启动时的分区情况,NXP官方给的镜像布局结构如下所示(《i.MX Linux User’s Guide》的4.3 Preparing an SD/MMC card to boot ):

image-20241110134940658

可以看到,上图将一张SD卡分成了三部分:

第一部分:扇区起始地址0x400bytes(2),大小为20478个扇区,分区格式为原始格式,用于存放uboot。0x400 bytes转为10进制是1024 bytes, 刚好等于2个扇区大小;这两个扇区是为了保存分区表的信息。所以从0x400地址开始存放U-boot,括号中的2可以理解为从第2个扇区的起始地址开始,扇区编号从0开始数起。

第二部分:扇区起始地址0xa0000 bytes(20480个扇区),大小为500M(1024000个扇区),分区格式为FAT,用于存放内核和设备树;

第三部分:扇区起始地址0x25800000(1228800个扇区),大小为SD卡剩余的空间,分区格式为Ext3或Ext4,用于存放rootfs,也就是文件系统。(注意:就这里其实有个坑的,我也不知道为啥,后面会说到这个地方,这里注意下)

image-20241110140850593

由于第一部分用于存放U-Boot,无文件系统格式的要求,所以我们对分区的创建从第二部分(第20480扇区所在的地址)开始就可以。拿到一张SD卡,首先将SD卡在windows下格式化成FAT32格式:

image-20241110141146802

这里只有40M应该是因为我前面做实验的时候的第一个分区是40M的原因。

3. 对SD卡重新分区

3.1 确认节点名称

我这里重新插入ubuntu后节点是sdc:

image-20241110141814036

可以看到是没有其他分区的,但有时候拿到的sd卡可能有分区,就按后面的继续处理。有分区的是这样的:

image-20241110153008072

3.2 卸载所有分区

当有其他分区存在的时候,我们需要卸载所有分区

1
2
ls /dev/sdc*
sudo umount /dev/sdcxx

3.3 清空SD卡

一般来说清空前面的部分数据即可:

1
sudo dd if=/dev/zero of=/dev/sdc bs=1024 count=1024
image-20241110153237641

3.4 两个分区

手册上fdisk的命令说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# sudo fdisk /dev/sdx

p # [lists the current partitions]
d # [to delete existing partitions. Repeat this until no unnecessary partitions are reported by the 'p' command to start fresh.]
n # [create a new partition]
p # [create a primary partition - use for both partitions]
1 # [the first partition]
20480 # [starting at offset sector]
1024000 # [size for the first partition to be used for the boot images]
p # [to check the partitions]

n
p
2
1228800 # [starting at offset sector, which leaves enough space for the kernel, the bootloader and its configuration data]
<enter> # [using the default value will create a partition that extends to the last sector of the media]
p # [to check the partitions]
w # [this writes the partition table to the media and fdisk exits]

这里我不按NXP的文档来了,主要是出现了这个问题,相同的步骤我按照NXP的文档,最后我挂载sdc2的时候出现了这个问题:

image-20241110155904254

我按照网上的说法用下面这个命令处理:

1
sudo fsck -y /dev/sdc2 # 出现"结构需要清理"说明磁盘文献问题,需要使用这个命令进行检查修复

但是最后没啥用,我甚至还怀疑是我sd卡有问题,然后我去试了正点原子教程的分区方式,就没问题,说明还是分区的问题。最后也没解决,不过,分区还是为了放文件,也没有必要那么严格的按照NXP文档来。后面我就按需要来了。

3.4.1 分区1——64M

这个分区用于存放kernel和设备树文件,我们可以看一下这两个文件多大:

image-20241110165409967

发现一个文件8M,一个文件27K,所以其实第一个分区根本没必要500M,这里给64M吧:

image-20241110170524143

3.4.2 分区2——剩余部分

image-20241110171021545

3.4.3 设置分区格式

这里其实在NXP的文档中并没有,不过我看正点原子脚本中有,就也加上了:

image-20241110171407795

这里主要是设置一下分区1的格式就行。然后我们再设置一下分区1为启动分区(官方文档没有这一步,正点原子的脚本有,这里就一起加上了):

image-20241110171614078

3.4.4 最终分区情况

image-20241110171635675

3.4.5 写入分区

最后的最后一定要输入w命令写入分区,确认分区的修改:

image-20241110171734729

3.5 格式化分区

1
2
3
4
5
6
7
# 设置sdc1分区格式为FAT
sudo mkfs.vfat -F 32 -n "boot" /dev/sdc1 # sdc1分区格式为FAT

# 设置sdc2分区格式为Ext3或Ext4都可以
sudo mkfs.ext3 -F -L "rootfs" /dev/sdc2 # sdc2分区格式为Ext3
# 或者
sudo mkfs.ext4 -F -L "rootfs" /dev/sdc2 # dc2分区格式为Ext4
image-20241110154318160

3.6 SD卡分区情况分析

我们来看一下最终的sd卡分区情况:

image-20241110171635675

此时sd卡分布如下图:

image-20241110172120026

前面好像就留了大概1M来写uboot,够吗》我们看一下uboot镜像文件的大小:

image-20241110172207362

基本上够了,uboot只是拿来引导一下启动linux内核,也不会增加什么额外的功能,这个大小差不多够了。

4. 烧写uboot到sd卡

这个和前面没什么区别,就是从偏移1KB的位置开始烧写uboot的imx文件即可:

1
sudo dd if=u-boot-dtb.imx of=/dev/sdc bs=1024 seek=1 conv=fsync
image-20241110162432151

从上面我们对SD卡分区的时候知道,uboot需要从第0分区的扇区地址2开始的,SD卡中一个扇区占512字节,SD卡偏移1K字节处开始保存uboot。上面这条命令中,bs设置dd命令的读写块为1K大小(这个数据可以任意设置,设置大一点读写就快),并且设置seek=1(单位为KB),表示从SD卡/dev/sdb开头偏移1K字节处开始写u-boot-dtb.imx,正好就是写到了u-boot在SD卡中0区所在位置处,前面预留1K字节刚好2个扇区大小保存分区表信息。

为什么从SD卡1K位置处存放uboot而不是从SD卡头部开始?

这是NXP官方手册《i.MX Linux® User’s Guide 》上写前面1K字节是保存分区表的信息,在Rev. L4.1.15_2.0.0-ga, 10/2016这个版本上没有,但是后面更新的文档都有说明了,例如这个:i.MX Linux User’s Guide (nxp.com)

image-20241110144506677

5. 拷贝kernel和dtbs

接下来就是把kernel和设备树拷贝到分区1中。

  • 创建挂载点
1
mkdir -p ~/tmp/sd_sdc1
  • 挂载分区1
1
sudo mount /dev/sdc1 ~/tmp/sd_sdc1 #  (注意根据上面的分区这是sdc1,存放kernal image和dtbs)
  • 使用cp命令将zImage和*.dtb文件拷贝进/media/sd_sdc1目录
1
2
sudo cp zImage ~/tmp/sd_sdc1/
sudo cp imx6ull-alpha-emmc.dtb ~/tmp/sd_sdc1/
image-20241110154745853
  • 取消挂载点
1
2
sudo umount ~/tmp/sd_sdc1
rm -rf ~/tmp/sd_sdc1
image-20241110154853628

6. 拷贝文件系统到SD卡

6.1 拷贝文件

和分区1的操作流程一样:

1
2
3
mkdir -p ~/tmp/sd_sdc2
sudo mount /dev/sdc2 ~/tmp/sd_sdc2
sudo cp rootfs.tar.bz2 ~/tmp/sd_sdc2/
image-20241110180201860

6.2 解压根文件系统

1
2
3
4
sudo mkdir rootfs
sudo tar -jxvf rootfs.tar.bz2 -C rootfs/
cd rootfs/
sudo tar -xf rootfs.tar
image-20241110180810646

可能是打包方式问题吧,需要解压两次,但感觉又不应该,算了就这样吧。

6.3 拷贝到顶层目录

1
sudo cp -a * ~/tmp/sd_sdc2

其实吧,直接在挂载的目录下解压就可以了,没必要多此一举,之前是以为有什么问题,后面直接挂载目录解压就可以。

image-20241110181039165

6.4 取消挂载

1
2
sudo umount /dev/sdc2
rm -rf ~/tmp/sd_sdc2

7. 总结

到此为止,sd卡启动盘就算是做好了,我们已经在sd卡中烧写了uboot、kernel、dtb文件以及根文件系统:

image-20241110181634105

四、从SD卡启动

1. 拨码开关设置

我们要先设置为SD卡启动:

image-20240117190003663

2. uboot相关命令

我们先复习几个命令,后面会用到的。

2.1 进入uboot

image-20241110182125122

2.2 EMMC和SD卡操作

我们来熟悉一下mmc相关的命令。uboot 支持 EMMC 和 SD 卡,因此也要提供 EMMC 和 SD 卡的操作命令。一般认为 EMMC
和 SD 卡是同一个东西 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
=> mmc
mmc - MMC sub system

Usage:
mmc info - display info of the current MMC device
mmc read addr blk# cnt
mmc write addr blk# cnt
mmc erase blk# cnt
mmc rescan
mmc part - lists available partition on current mmc device
mmc dev [dev] [part] - show or set current mmc device [partition]
mmc list - lists available devices
mmc hwpartition [args...] - does hardware partitioning
arguments (sizes in 512-byte blocks):
[user [enh start cnt] [wrrel {on|off}]] - sets user data area attributes
[gp1|gp2|gp3|gp4 cnt [enh] [wrrel {on|off}]] - general purpose partition
[check|set|complete] - mode, complete set partitioning completed
WARNING: Partitioning is a write-once setting once it is set to complete.
Power cycling is required to initialize partitions after set to complete.
mmc bootbus dev boot_bus_width reset_boot_bus_width boot_mode
- Set the BOOT_BUS_WIDTH field of the specified device
mmc bootpart-resize <dev> <boot part size MB> <RPMB part size MB>
- Change sizes of boot and RPMB partitions of specified device
mmc partconf dev [boot_ack boot_partition partition_access]
- Show or change the bits of the PARTITION_CONFIG field of the specified device
mmc rst-function dev value
- Change the RST_n_FUNCTION field of the specified device
WARNING: This is a write-once field and 0 / 1 / 2 are the only valid values.
mmc setdsr <value> - set DSR register value

可以看出, mmc 后面跟不同的参数可以实现不同的功能 :

命令 描述
mmc info 输出 MMC 设备信息
mmc read 读取 MMC 中的数据。
mmc wirte 向 MMC 设备写入数据。
mmc rescan 扫描 MMC 设备。
mmc part 列出 MMC 设备的分区。
mmc dev 切换 MMC 设备。
mmc list 列出当前有效的所有 MMC 设备。
mmc hwpartition 设置 MMC 设备的分区。
mmc bootbus…… 设置指定 MMC 设备的 BOOT_BUS_WIDTH 域的值。
mmc bootpart…… 设置指定 MMC 设备的 boot 和 RPMB 分区的大小。
mmc partconf…… 设置指定 MMC 设备的 PARTITION_CONFG 域的值。
mmc rst 复位 MMC 设备
mmc setdsr 设置 DSR 寄存器的值。

2.2.1 mmc rescan 命令

1
=> mmc rescan

好像没见打印输出,先不管。

2.2.2 mmc list 命令

list 命令用于来查看当前开发板一共有几个 MMC 设备,输入“mmc list” :

1
2
3
=> mmc list
FSL_SDHC: 0 (SD)
FSL_SDHC: 1

可以看出当前开发板有两个 MMC 设备: FSL_SDHC:0 和 FSL_SDHC:1 (eMMC),这是因为我现在用的是 EMMC 版本的核心板,加上 SD 卡一共有两个 MMC 设备, FSL_SDHC:0 是 SD卡, FSL_SDHC:1(eMMC)是 EMMC。

2.2.3 mmc dev 命令

mmc dev 命令用于切换当前 MMC 设备,命令格式如下

1
mmc dev [dev] [part]

dev用来设置要切换的 MMC 设备号, part是分区号。如果不写分区号的话默认为分区 0。使用如下命令切换到 SD 卡:

1
2
3
=> mmc dev 0 # 切换到 SD 卡, 0 为 SD 卡, 1 为 eMMC
switch to partitions #0, OK
mmc0 is current device

2.2.4 mmc info 命令

mmc info 命令用于输出当前选中的 mmc info 设备的信息,输入命令“mmc info”即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
=> mmc info
Device: FSL_SDHC
Manufacturer ID: 99
OEM: 8888
Name: W620X
Bus Speed: 50000000
Mode : SD High Speed (50MHz)
Rd Block Len: 512
SD version 1.10
High Capacity: No
Capacity: 1.9 GiB
Bus Width: 4-bit
Erase Group Size: 512 Bytes

看出当前 SD 卡为 1.10 版本的,容量为 1.9GiB(2GB 的 SD 卡), 4 位宽的总线。

2.2.5 mmc part 命令

SD 卡或者 EMMC 会有多个分区,可以使用命令“mmc part”来查看其分区

  • 查看emmc分区
1
2
=> mmc dev 1
Card did not respond to voltage select!

这里好像切换失败了,先不管吧,后面再说。

  • 查看sd卡分区
1
2
3
4
5
6
7
8
9
10
11
=> mmc dev 0
switch to partitions #0, OK
mmc0 is current device
=> mmc part

Partition Map for MMC device 0 -- Partition Type: DOS

Part Start Sector Num Sectors UUID Type
1 2048 131072 0acdccb3-01 0c Boot
2 133120 3798016 0acdccb3-02 83

此时 SD卡 有两个分区, 第一个分区起始扇区为 2048,长度为 131072个扇区; 第二个分区起始扇区为 133120,长度为 3798016个扇区。如果 SD卡 里面烧写了 Linux 系统的话, SD 是有 3 个分区的,第 0 个分区存放 uboot,第 1 个分区存放Linux 镜像文件和设备树,第 2 个分区存放根文件系统。但是在上面只有两个分区,那是因为第 0 个分区没有格式化,所以识别不出来,实际上第 0 个分区是存在的。一个新的 SD卡默认只有一个分区,那就是分区 0,前面我们就是把uboot烧写到了分区0.

2.3 FAT格式文件系统操作

有时候需要在 uboot 中对 SD 卡或者 EMMC 中存储的文件进行操作,这时候就要用到文件操作命令,跟文件操作相关的命令有: fatinfo、 fatls、 fstype、 fatload 和 fatwrite,但是这些文件操作命令只支持 FAT 格式的文件系统!!

2.3.1 fatinfo 命令

fatinfo 命令用于查询指定 MMC 设备分区的文件系统信息 :

1
fatinfo <interface> [<dev[:part]>]

interface 表示接口,比如 mmc, dev 是查询的设备号, part 是要查询的分区。比如我们要查询 SD卡 分区 1 的文件系统信息 :

1
2
3
4
5
6
7
=> fatinfo mmc 0:1
Interface: MMC
Device 0: Vendor: Man 000099 Snr 00000700 Rev: 0.0 Prod: W620X
Type: Removable Hard Disk
Capacity: 1919.5 MB = 1.8 GB (3931136 x 512)
Filesystem: FAT32 "boot "

2.3.2 fatls 命令

atls 命令用于查询 FAT 格式设备的目录和文件信息 :

1
fatls <interface> [<dev[:part]>] [directory]

interface 是要查询的接口,比如 mmc, dev 是要查询的设备号, part 是要查询的分区, directory是要查询的目录。比如查询 SD卡分区 1 中的所有的目录和文件,输入命令:

1
2
3
4
5
6
=> fatls mmc 0:1
8315704 zImage
27436 imx6ull-alpha-emmc.dtb

2 file(s), 0 dir(s)

可以看到分区1中就是zImage和dtb文件。

2.3.3 fstype 命令

fstype 用于查看 MMC 设备某个分区的文件系统格式 :

1
fstype <interface> <dev>:<part>

我们来看一下SD卡的几个分区:

1
2
3
4
5
6
7
8
9
=> fstype mmc 0:0
** Unrecognized filesystem type **
=> fstype mmc 0:1
fat
=> fstype mmc 0:2
ext4
=> fstype mmc 0:4
** Invalid partition 4 **

分区 0 格式未知,因为分区 0 存放的 uboot,并且分区 0 没有格式化,所以文件系统格式未知。分区 1 的格式为 fat,分区 1 用于存放 linux 镜像和设备树。分区 2 的格式为 ext4,用于存放 Linux 的根文件系统(rootfs)。

2.3.4 fatload 命令

fatload 命令用于将指定的文件读取到 DRAM 中 :

1
fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]

interface 为接口,比如 mmc, dev 是设备号, part 是分区, addr 是保存在 DRAM 中的起始地址, filename 是要读取的文件名字。 bytes 表示读取多少字节的数据,如果 bytes 为 0 或者省略的话表示读取整个文件。 pos 是要读的文件相对于文件首地址的偏移,如果为 0 或者省略的话表示从文件首地址开始读取。我们将 SD卡分区 1 中的 zImage 文件读取到 DRAM 中的0X80800000 地址处,

1
2
=> fatload mmc 0:1 80800000 zImage
8315704 bytes read in 794 ms (10 MiB/s)

2.3.5 fatwrite 命令

注意! uboot 默认没有使能 fatwrite 命令,需要修改板子配置头文件,比如 mx6ullevk.h、mx6ull_alpha_emmc.h 等等,板子不同,其配置头文件也不同。找到自己开发板对应的配置头文件然后添加如下一行宏定义来使能 fatwrite 命令:

1
#define CONFIG_FAT_WRITE /* 使能 fatwrite 命令 */

我用的这个2019版本的直接打开了好像,没打开的话在源码修改一下就可以了。fatwirte 命令用于将 DRAM 中的数据写入到 MMC 设备中,命令格式如下:

1
fatwrite <interface> <dev[:part]> <addr> <filename> <bytes>

interface 为接口,比如 mmc, dev 是设备号, part 是分区, addr 是要写入的数据在 DRAM中的起始地址, filename 是写入的数据文件名字, bytes 表示要写入多少字节的数据。我们可以通过 fatwrite 命令在 uboot 中更新 linux 镜像文件和设备树。

所以我们可以在uboot下在线更新kernel镜像文件和设备树文件,我们可以直接从tftp下载这两个文件到内存中,然后写入到对应的分区中去。

我们以更新 linux 镜像文件 zImage为例,首先将 zImage 镜像文件拷贝到 Ubuntu 中的tftpboot 目录下,命令 tftp 将 zImage 下载到 DRAM 的 0X80800000 地址处,命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
=> tftp 80800000 zImage
ethernet@020b4000 Waiting for PHY auto negotiation to complete.... done
Using ethernet@020b4000 device
TFTP from server 192.168.10.101; our IP address is 192.168.10.102
Filename 'zImage'.
Load address: 0x80800000
Loading: #################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
#################################################################
###############################################
972.7 KiB/s
done
Bytes transferred = 8315704 (7ee338 hex)

zImage 大小为 8315704(0x7ee338)个字节 ,接下来使用命令 fatwrite 将其写入到 SD卡的分区 1 中,文件名字为 zImage :

1
2
=> fatwrite mmc 0:1 80800000 zImage 7ee338
8315704 bytes written

3. 手动启动内核

3.1 进入uboot界面

跟前面操作一样。

3.2 设置bootargs参数

设置bootargs参数,指定根文件系统保存的路径:

1
2
3
4
#指定根文件系统在mmc设备0的2分区上。并且ip地址为静态ip地址
=> setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 ip=192.168.10.102:192.168.10.101:192.168.10.1:255.255.255.0::eth0:off init=/linuxrc'
=> saveenv
Saving Environment to MMC... Writing to MMC(0)... OK

这里主要是设置根文件系统的挂载路径,需要知道的是,不管是sd卡还是emmc在linux中的文件节点都叫mmcblk,这里的mmc0就表示sd卡,p2就表示sd卡的第二个分区,也就是我们前面创建的根文件系统所在分区。

3.3 加载内核镜像

加载zImage内核到内存的0x80800000地址上:

1
2
=> load mmc 0:1 0x80800000 zImage
8315704 bytes read in 800 ms (9.9 MiB/s)

其实和前面的fatload是一样的。

3.4 加载设备树

加载设备树到0x83000000内存地址上:

1
=> load mmc 0:1 0x83000000 imx6ull-alpha-emmc.dtb

3.5 启动内核

1
2
3
4
5
6
7
8
9
=> bootz 0x80800000 - 0x83000000
Kernel image @ 0x80800000 [ 0x000000 - 0x7ee338 ]
## Flattened Device Tree blob at 83000000
Booting using the fdt blob at 0x83000000
Using Device Tree in place at 83000000, end 83009b2b
ft_system_setup for mx6

Starting kernel ...
# ......

然后内核就起来了,然后就出问题了:

image-20241111210347645

4. 问题解决

4.1 JBD2——问题一

4.1.1 问题分析

我们先来看这两行:

1
2
[    6.761505] JBD2: no valid journal superblock found
[ 6.766840] EXT4-fs (mmcblk0p2): error loading journal

这里吧其实我没怎么深入研究,大概的原因就是新的操作系统上使用系统自带的 mkfs.ext4 对文件系统进行了格式化,默认使用了一些新的的特性,这些新的特性在旧的系统上是无法使用的,即在旧的内核上不支持。

我们可以看一下ubuntu的内核版本:

image-20241111210621620

而我使用的linux内核是4.19版本的,也许是中间加入了某些新的特性吧。这里主要是journal相关的这个特性出了问题,怎么处理呢?网上搜了一堆,简单说就是把这个属性去掉就好了。

4.1.2 解决方法

  • (1)确定sd卡节点

这里出问题的是sd卡的ext4分区,也就是sdx2,我的挂载上来就是sdc2:

1
2
3
4
5
6
7
sumu@sumu-virtual-machine:~/7Linux/imx6ull-alpha-release/release$ cat /proc/partitions 
major minor #blocks name

#......
8 32 1965568 sdc
8 33 65536 sdc1
8 34 1899008 sdc2
  • (2)看分区journal日志
1
sudo tune2fs -l /dev/sdc2 | grep feature
image-20241111211758170

发现是有这个has_journal特性,我们接下来要做的就是去掉这个特性。

  • (3)卸载分区
1
sudo mount /dev/sdc2
  • (4)修改分区journal日志
1
sudo tune2fs -O ^has_journal /dev/sdc2
image-20241111211925133
  • (5)检查是否成功
1
sudo tune2fs -l /dev/sdc2 | grep feature
image-20241111212008521

发现已经没有了。

4.1.3 开发板验证

我们来验证一下,和前面的步骤一摸一样,正常应该是问题已经解决了:

image-20241111212207419

发现可以这次正常挂载了

4.2 EXT2-fs——问题二

甚至第二个问题也没了,大概应该是上面的问题一引起的第二个问题吧。那这里就不管了。

5. 自动挂载

每次都要敲那么多的命令,其实uboot是有自动运行的命令的,就是bootcmd环境变量,在uboot启动后,会自动运行这个环境变量里面的命令。我们来配置一下:

1
2
3
4
5
6
7
8
# 设置启动参数
=> setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 ip=192.168.10.102:192.168.10.101:192.168.10.1:255.255.255.0::eth0:off init=/linuxrc'
# 设置命令
=> setenv bootcmd 'mmc dev 0;fatload mmc 0:1 80800000 zImage;fatload mmc 0:1 83000000 imx6ull-alpha-emmc.dtb;bootz 80800000 - 83000000'
# 保存环境变量
=> saveenv
Saving Environment to MMC... Writing to MMC(0)... OK

然后我们复位开发板就会发现可以自动加载镜像和设备树,并且自动启动了:

image-20241111213032658

最后也是可以正常启动了:

image-20241111213103247

五、做成shell脚本

每次敲那么多命令肯定很烦,做成shell脚本呗:release/imx6ull_sd_boot.sh · 苏木/imx6ull-alpha-release - 码云 - 开源中国 (gitee.com)

image-20241112220637919

参考资料:

SD卡启动与uboot 、zImage、dtb、rootfs固化_怎么让 uboot kernel rootfs 在 sd 卡里运行-CSDN博客

imx6ul基于zImage,dtb 在SD卡制作linux镜像 - 知乎 (zhihu.com)

imx6ull开发板设置SD卡启动,SD卡中烧写uboot,kernel,设备树,根文件系统fs_imx6ull sd卡-CSDN博客

【问题解决】报错:EXT4-fs (mmcblk0p2): couldn‘t mount RDWR because of unsupported optional features (400)_ext4-fs error (device mmcblk0p2)-CSDN博客

解决 EXT4 使用无法挂载-阿里云开发者社区 (aliyun.com)

linux开机启动挂载mmc错误解决_qqliyunpeng-GitCode 开源社区 (csdn.net)

解决 EXT4 使用无法挂载 | Erdong’ Blog

[Linux如何关闭分区journal日志 - 飞入云端_blog - 博客园 (cnblogs.com)](https://www.cnblogs.com/zhangyunfei-blog/p/9288482.html#:~:text=1.重启系统,进入单用户模式 2.查看要修改的分区 %23df -h 3.查看分区journal日志 (如%2Fdev%2Fsda1) %23tune2fs -l,|grep feature 查看是否有has_journal字段 4.以只读模式挂载根分区 %23mount -o remount%2Cro %2Fdev%2Fsda1)