使用 loop 设备访问虚拟磁盘
涛叔loop 设备是 Linux 下一种虚拟设备。它广泛应用于跟虚拟磁盘、ISO镜像等文件的处理领域。最近在折腾 ARM 虚拟系统,需要使用 loop 设备制作系统镜像。多方调研才对它有了相对全面的认识。为了把文章讲清楚,本文还会介绍 Linux 磁盘管理的基础知识,也欢迎新手阅读。
磁盘设备
Linux 以文件的形式管理磁盘。一般第一块设备对应的文件是/dev/sda
。大家可能不知道,我们是可以直接读写这个文件的。在 Linux 系统中,这就是一个普通的文件。唯一的不同点就是文件的大小是固定的,也就是磁盘的容量。我们没法在这个文件结尾处追加数据。但整个文件所有数据都可以读写。比如我们可以把写入hello
:
⚠️ 不要在自己的系统上操作,会清空整个磁盘上的数据!⚠️
echo -n hello > /dev/sda
然后我们还可以读出这五个字节:
head -c 5 /dev/sda
hello%
Linux 内核的驱动会将对应的读写调用转换成磁盘操作指令,最终hello
这五个字节的数据会被保存到底层的磁盘上。以这种方式操作磁盘,不同程序之间还可能相互影响,程序还需要自行处理文件名跟文件内容的映射。所以内核提供解决了这些基础问题,于是便有了分区和文件系统。
分区表
所谓分区就是把磁盘划分成几块不同的空间。每一块的类型和开始结束位置等信息都保存硬盘开头的某段空间内。这一段也叫分区表。主流的分区表有 DOS 和 GPT 两种。Linux 下常见的分区工具是fdisk
和gdisk
,都支持两种分区表。
一旦完成分区,Linux 就会自动识别分区表信息,然后为每一个分区创建对应的设备文件。比如我们把/dev/sda
分成两个区,内核就会分别创建/dev/sda1
和/dev/sda2
两个文件。
这两个文件跟/dev/sda
一样,也可以直接读写。唯一的不同是读写/dev/sda1
的程序不会影响读写/dev/sda2
的程序。这是操作系统提供的最小粒度的隔离。但同样不支持通过文件名读写数据。
文件系统
为此,我们还需要为每个分区做格式化处理,也就创建文件系统信息。Linux 支持很多文件系统,比如 EXT4/XFS 等等。最常见的是 EXT4,很多 Linux 发行版都用它作 root 文件系统。每种文件系统都提供对应的工具。比如创建 EXT4 系统需要使用mkfs.ext4
:
mkfs.ext4 /dev/sda1
注意,创建文件系统的时候需要指定分区的设备文件/dev/sda1
,而不能是磁盘的设备文件。理论上也可以直接用/dev/sda
,但我没试过。读者可以试试,后果自负😄
创建好 EXT4 文件系统后,我们还是不能直接使用。相反,我们需要执行所谓的挂载(mount)操作:
mount /dev/sda1 /mnt
mount
第一个参数是分区设备,第二个参数是要挂载的文件夹,也叫挂载点。/mnt
是 Linux 下传统的挂载点,一般临时挂载设备都用它。你也可以自己任意创建挂载目录。不管挂载目录里原来有什么文件,一但完成挂载,里就会展示对应设备里的文件和目录。
挂载对应的卸载umount
,不是un-mount
😂
umount /mnt
卸载后挂载点目录会重新显示原来的内容。所以初学者不要慌😄
自动挂载
其实大家可以把挂载跟前面的/dev/sda1
对比。因为分区设备的路径是内核确定的,所以内核会根据分区表信息自动创建分区设备。这本身就是在「挂载」分区表信息。但到了文件系统就不能这么操作了。因为内核不知道要把文件系统挂载到哪个文件夹,所以内核没办法自动挂载。需要用户手工操作。正因如此,早期的 Linux 系统对用户非常不友好。因为不明真相,初学者老是觉得 Linux 不好用。其实确实不好用。早期在挂载的时候还要用户手工指定文件系统类型!
Linux 的这种设计也是有初衷的。UNIX 系统无条件信任用户。哪怕用户输入rm -rf /
它也会执行!因为相信,所以就要求用户充分了解自己的系统,也就要求用户对自己的行为负责。所以能力越大,责任越大。作为 UNIX 用户,你不能无脑执行一个命令然后要求系统为你的行为兜底。这是 Linux 从 UNIX 继承的哲学。
我是很喜欢这种设计,但它确实对新人不友好。随着时间的发展,Linux 通过 udev 系统实现了自动挂载功能。典型的领域就是插上优盘后自动弹出对应的文件夹。这套体系只是前面所说内容的外挂,本质上还是在用mount
命令来挂载。只不过新系统对应挂载点的位置做了统一约定,所以才能实现自动打开优盘或者光盘的功能。
以上都是背景知识。说了半天,还没提今天的主角 loop 设备!
虚拟光盘
我们前面说过,内核会把写入/dev/sda
文件的内容直接保存到真正的硬盘设备。但有时候我们不想直接操作设备,比如读取ISO镜像。所谓ISO镜像就是从光盘里复制的数据文件。正常情况下要读取该文件的内容需要先把它记录到一张新光盘,然后用光驱设备读取。对应的 Linux 内核则是在/dev/cdrom
设备文件(不同发行版可能会略有差异)。也就是说从/dev/cdrom
这个文件读取的内容就是来自光盘的内容。
可以现在社会光驱已经很少见了,刻录机更是稀有。即便所有的设备都置办齐全,应用程序能读取的也不过是另一个文件/dev/cdrom
。也就是说经历了ISO文件➜刻录机➜光盘➜光驱➜/dev/cdrom
一系列过程,应用程序只不过是读了另外一个文件😂哪能不能省掉所有的中间流程,直接读ISO文件不香吗?
这就要用到 loop 设备。loop 是一种虚拟设备,对应设备文件为/dev/loop0
。如果有多个则依次编号。操作 loop 设备需要用到losetup
命令。
比如我们先把ISO文件关联到 loop 设备:
losetup /dev/loop0 alpine-virt-3.16.3-x86_64.iso
关联之后可以查看对应关系:
losetup
NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC
/dev/loop0 0 0 0 0 /home/ts/alpine-virt-3.16.3-x86_64.iso 0 512
这个时候我们就可以挂载光盘了:
mount /dev/loop0 /mnt
ls /mnt
apks boot efi
也就是说现在/dev/loop0
设备等同于原来的/dev/cdrom
设备。挂载之后就可以在/mnt
目录查看光盘镜像里的内容了。
以上两步甚至可以合二为一:
mount -o loop alpine-virt-3.16.3-x86_64.iso
用完之后后需要先卸载/mnt
,再取消关联:
losetup -d /dev/loop0
光盘比较特殊,不能分区。所以不能算是典型使用场景。但是这里边有一个关键,所有对 loop 设备的读写操作都会被「转发」给对应的普通文件,上例中是ISO镜像文件。也就是说我们不需要真正的光驱设备,却实现了读取ISO镜像内容。这就是所谓的虚拟光驱。
虚拟硬盘
同样的原理也适用于普通磁盘。我们在使用虚拟机的时候会创建各种虚拟磁盘,这些本质上也是一种镜像文件。跟ISO文件不同的是虚拟磁盘文件可以写入内容,而且容量还可以调整。但处理的原理一样的,都需要通过 loop 设备关联。
并非所有虚拟磁盘都能被内核直接处理。像 cow2 这种被 QEMU 压缩过,内核无法处理。但我们可以把它们转换成所谓的 raw 格式文件,内核就能处理了。
在 Linux 下,创建虚拟磁盘就是创建一个普通的文件:
dd if=/dev/zero of=disk1.img bs=1M count=256M
然后跟 loop 设备关联:
losetup /dev/loop0 disk1.img
这个时候/dev/loop0
就变成跟/dev/sda
设备一样的磁盘设备了。唯一的区别是/dev/sda
会把数据真正保存到硬盘里,而此时的/dev/loop0
则会把数据保存到disk1.img
文件。
我们可以使用fdisk
对/dev/loop0
进行分区。分区后内核会自动创建类似/dev/loop0p1
这样的分区设备文件。然后我们用mkfs.ext4
给/dev/loop0p1
等分区创建文件系统。最后将其挂载到某个文件夹。这之后在挂载点内的所有文件操作都会被记录到disk1.img
这个虚拟的磁盘中。
也有文章说需要执行kpartx -a /dev/loop0
系统才会创建对应的分区设备节点,而且是在/dev/mapper
目录。但我实验下来不需要这一步。可能是新系统更加智能了。但如果大家执行了此命令,那么在解除关联之前还需要执行kpart -d /dev/loop0
。
如果disk1.img
是 QEMU 创建的并且安装了操作系统,那么我们也可以在挂载之后查看对应系统的根目录,甚至还可以修改里面的文件。但前提是当前系统支持对应的文件系统。
以上就是全部内容,希望能对大家有所帮助。
参考链接: