树莓派(Raspberry Pi)修改系统镜像(img)
树莓派修改系统镜像
讨论如何使用通过loop设备映射文件,挂载树莓派img中的分区,然后像普通的文件系统那样修改镜像分区中的文件。
有2个关键点
- 如何确认分区的起始位置,以及分区的范围。
- 不同版本的losetup支持的参数不一样,如果支持-P参数特性,则可以直接加载分区,无需再手动计算偏移量,可以像挂载普通硬盘一样直接生成分区块设备
准备工作
- 需要一个支持linux设备,已经root了的termux,或者,一个普通的linux发行版均可。
- 需要用到的关键命令
losetup
,fdisk
,tun2fs
,mount
,请检查是否都有。 - 大部分操作均需要root权限。
- 去源内下载一份镜像文件。
第一步:下载镜像
首先,下载一个可以用于写入树莓派的img镜像,国内可以在清华的镜像站中下载。
清华源的地址:https://mirrors.tuna.tsinghua.edu.cn/raspberry-pi-os-images/raspbian/images/raspbian-2020-02-14/
我下载的是2020-02-13-raspbian-buster.zip
,解压后得到2020-02-13-raspbian-buster.img
,img文件可以直接烧录到内存卡,它实际上是个包含了mbr分区表的磁盘映像。
第二步:使用losetup命令把img文件映射成块设备
首先我们先确认losetup支持的选项,有没有-P选项:
#这是我手机版本的losetup
$losetup --help
usage: losetup [-cdrs] [-o OFFSET] [-S SIZE] {-d DEVICE...|-j FILE|-af|{DEVICE FILE}}
Associate a loopback device with a file, or show current file (if any)
associated with a loop device.
Instead of a device:
-a Iterate through all loopback devices
-f Find first unused loop device (may create one)
-j Iterate through all loopback devices associated with FILE
existing:
-c Check capacity (file size changed)
-d Detach loopback device
new:
-s Show device name (alias --show)
-o Start assocation at OFFSET into FILE
-r Read only
-S Limit SIZE of loopback association (alias --sizelimit)
看帮助信息,它不支持-P参数,直接加载一个多分区的img文件,它不会生成/dev/loop0p1这样的分区块设备。
如果losetup支持-P参数,那么,可以这样让他/dev生成分区的块设备loop0p1
#losetup -f -P ./2020-02-13-raspbian-buster.img
再查看/dev下loop有没有生成对应分区的块设备,如果额外生成/dev/loop0p2
这样的文件,则直接挂载这个设备。如果没有,继续以下步骤,手动计算分区的起始位置和大小。
注意,linux发行版的losetup如果没有-S参数,请看看是否是--sizelimit
参数,没有-o的话,则看看--offset
,先通过losetup --help
查看
第三步:确认分区数量和分区的位置
上面的losetup
有两个很重要的参数
-
-S
Limit SIZE of loopback association - 指定映射的文件部分内容(可以用来设定分区的大小)
-
-o
Start assocation at OFFSET into FILE - 跳过多少个字节(用这个参数可以指定分区的起始位置)
不同版本的可能losetup相关的参数有细微的不同,要以实际为准。
那么需要确定分区的起始和结束位置。先尝试把整个img映射,注意,这需要root权限
加载当前文件夹的2020-02-13-raspbian-buster.img
#losetup -f ./2020-02-13-raspbian-buster.img
映射后通过losetup命令(不带参数)得知,文件映射到了/dev/loop0这个设备
因为img包含了mbr分区表信息,我可以使用fdisk
来读取分区表,获取分区信息。
#fdisk -l /dev/loop0
Disk /dev/loop0:3.5 GiB,3787456512 字节,7397376 个扇区
单元:扇区 / 1 * 512 = 512 字节
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:dos
磁盘标识符:0xea7d04d6
设备 启动 起点 末尾 扇区 大小 Id 类型
/dev/loop0p1 8192 532479 524288 256M c W95 FAT32 (LBA)
/dev/loop0p2 532480 7397375 6864896 3.3G 83 Linux
通过分区表,我获得了一些信息。
- 这个是个3.5G的映像。
- 这个img有2个分区,一个是fat32分区(/dev/loop0p1),一个是linux的ext分区。(/dev/loop0p2)
- 分区的具体物理位置(起点、末尾),分区大小(扇区)
- 这个磁盘镜像每个扇区是512字节
他虽然写了/dev/loop0p2
这样的设备,但是实际上,可能/dev下根本没有loop0p1 loop0p2这两个块设备文件。
此时需要换一种方式,根据分区在文件内的位置,直接映射分区到loop设备,然后再挂载。
第3.5步:计算分区位置及大小
因为img文件包含一个mbr分区表,可以通过查看分区表确认分区数量和位置,通过以上fdisk读取的分区表信息可以得知,该img有2个分区。
fat32类型那个分区应该存放内核,config.txt之类的分区,也就是我们烧录后看到内存卡只有256m的原因。
linux类型分区是树莓派的根分区,也就是进系统后,挂载在/的根分区
此时我需要计算根分区的起始位置,如果我需要改动config.txt,则计算fat32分区的位置,以下以计算根分区为例。
逻辑扇区大小为512字节,分区大小为扇区数量的512倍字节,需要乘以扇区大小(下面换算分区大小同理),根分区的起始位置
则换算为
512 * 532480 = 272629760
它偏离文件起始的272629760字节,即用-o参数指定偏移的值。
分区的大小为
512 * 6864896 = 3514826752
使用-S参数限制映像文件的内容范围,记录下来以上信息后,卸载loop设备(此处为/dev/loop0),然后再重新进行映射。
卸载设备
#losetup -d /dev/loop0
再次映射
#losetup -f -o 272629760 -S 3514826752 ./2020-02-13-raspbian-buster.img
此时,我们再次losetup
看看映射的loop设备
#losetup
NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC
/dev/loop0 3514826752 272629760 0 0 /home/xxx/raspbian/2020-02-13-raspbian-buster.img 0 512 0 512
可以看到sizelimit
字段和offset
字段都有了具体的值,和我们设置的一致。/dev/loop0映射到了映像文件的根分区范围内
这是一个ext分区,我们可以通过tun2fs来读取文件系统的超级块
#tun2fs -l /dev/loop0
可以看到以下内容
tune2fs 1.44.5 (15-Dec-2018)
Filesystem volume name: rootfs
Last mounted on: /mnt/raspbian
Filesystem UUID: 80571af6-21c9-48a0-9df5-cffb60cf79af
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: has_journal ext_attr resize_inode dir_index filetype extent flex_bg sparse_super large_file dir_nlink extra_isize
Filesystem flags: unsigned_directory_hash
Default mount options: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 214704
Block count: 858112
Reserved block count: 42905
Free blocks: 133541
Free inodes: 108390
First block: 0
Block size: 4096
Fragment size: 4096
Reserved GDT blocks: 209
Blocks per group: 32768
Fragments per group: 32768
Inodes per group: 7952
Inode blocks per group: 497
Flex block group size: 16
Filesystem created: Fri Feb 14 00:15:48 2020
Last mount time: Sat Aug 15 04:54:28 2020
Last write time: Sat Aug 15 05:22:08 2020
Mount count: 3
Maximum mount count: -1
Last checked: Fri Feb 14 00:15:48 2020
Check interval: 0 (<none>)
Lifetime writes: 3052 MB
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 256
Required extra isize: 32
Desired extra isize: 32
Journal inode: 8
Default directory hash: half_md4
Directory Hash Seed: 6006b686-6f27-4ca7-b7ea-d1d803c3da46
Journal backup: inode blocks
说明正确读出了该分区下文件系统的超级快。可以挂载。
第4步:挂载文件系统
在当前文件夹下新建一个root文件夹作为分区的挂载点。
#mkdir root
挂载
#mount -t ext4 -o rw /dev/loop0 root
挂载成功后,通过当前目录下root挂载点访问img中根分区的内容。需要修改什么内容就尽情修改吧!
修改完成后使用sync
命令同步写入磁盘。
#sync
#sync
#sync
退出root文件夹后,卸载当前目录下root挂载点
#umount root
取消映射关系,分离
#losetup -d /dev/loop0
插入tf卡后,用dd指令把img文件写入,假设内存卡设备为/dev/sdc
dd if=./2020-02-13-raspbian-buster.img of=/dev/sdc
镜像比较大,而且tf卡性能较差的情况下,写入的速度比较慢,需要耐心等待。
写入成功后,sync同步,即可取出tf卡。树莓派系统刷写完毕。