基于 SquashFS 构建 Linux 可读写文件系统

转载:http://www.oschina.net/question/129540_116839

在当前的嵌入式操作系统开发中,Linux 操作系统通常被压缩成 Image 后存放在 Flash 设备中。在系统启动过程中,这些 Image 被直接挂载到根文件系统, 然而这时的根文件系统是只读的, 用户不能在这个文件系统中进行任何写的操作。 如果把 Image 解压后直接拷贝到内存中,也可以实现写的功能,但是嵌入式系统一直存在内存大小方面的限制,所以将整个 Linux 系统拷入内存是不可取的。 本文将介绍一种直接挂载 Image 到根目录下,同时实现文件系统可读写的功能。

 

嵌入式 Linux 启动过程

本文所描述的的 Linux Image 由 BootLoader、kernel、initrd、rootfs 组成,它们共同存在于一个可以启动的存储设备中(本文以 USB 为例)。组成架构如下:

图 1. 可启动 linux 镜像文件结构 
aaa 

各个模块的作用如下:

  • Boot Loader:由 BIOS 加载,用于将后续的 Kernel 和 initrd 的装载到内存中
  • kernel:为 initrd 运行提供基础的运行环境
  • initrd:检测并加载各种驱动程序
  • rootfs:根文件系统,用户的各种操作都是基于这个被最后加载的文件系统

其调用顺序是 Boot Loader->kernel->initrd->rootfs。

当机器上电时首先 BIOS 会启动,然后装载 USB 设备中的 Boot Loader、kernel,、nitrd 到内存中,由于这些文件大小总和小于 10M,所以我们直接拷贝到内存中再执行不会有问题。

最后要加载的 rootfs 是用户最终进行读写操作的文件系统。

  • 在非嵌入式系统中,这部分文件通常储存在可直接读写的硬盘上,因此直接挂载到根目录后(例如:mount /dev/sda1 /mnt)就可以进行读写操作。
  • 在嵌入式系统中,它是一个压缩的文件系统,大小通常是好几百兆,解压后的大小都超过 1G,如果直接 mount 到系统目录,那么系统目录是只读的,不可进行写入操作。而如果把它加压到内存中可以实现读写的操作,但是这么大的文件直接解压到内存中对于嵌入式设备来说 是不可接受的。因此我们需要找到一种不拷贝 rootfs 到内存中,同时又可以对最终的根文件系统进行读写的方法。

只读式压缩文件系统介绍

在嵌入式的环境之下,内存和外存资源都需要节约使用。如果使用 RAMDISK(把内存当作 disk)方式来使用文件系统,那么在系统运行之后,首先要把外存 (Flash) 上的映像文件解压缩到内存中,构造起 RAMDISK 环境,才可以开始运行程序。但是它也有很致命的弱点。在正常情况下,同样的代码不仅在外存中占据了空间 ( 以压缩后的形式存在 ),而且还在内存中占用了更大的空间 ( 以解压缩之后的形式存在 ),这违背了嵌入式环境下尽量节省资源的要求。以下两种方案的诞生就是为了解决这个问题:

CramFS

CramFS 文件系统是专门针对闪存设计的只读压缩的文件系统,它并不需要一次性地将文件系统中的所有内容都解压缩到内存之中,而只是在系统需要访问某个位置的数据的 时侯,马上计算出该数据在 CramFS 中的位置,将其实时地解压缩到内存之中,然后通过对内存的访问来获取文件系统中需要读取的数据。CramFS 中的解压缩以及解压缩之后的内存中数据存放位置都是由 CramFS 文件系统本身进行维护的,用户并不需要了解具体的实现过程,因此这种方式增强了透明度,对开发人员来说,既方便,又节省了存储空间。

SquashFS

SquashFS 也是一个只读的文件系统,它可以将整个文件系统压缩在一起,存放在某个设备,某个分区或者普通的文件中。如果您将其压缩到一个设备中,那么您可以将其直接 mount 起来使用,而如果它仅仅是个文件的话,您可以将其当为一个 loopback 设备使用。

更多信息请参考“SquashFS”和“CramFS”。

本文主要介绍基于 SquashFS 的可读写文件系统构建。

Squash 压缩文件系统的创建

"下载并安装 Squash 工具"

步骤 1:创建空的根文件系统,文件系统的大小为 65536 × 24000/1024/1024=1.5G。接下来我们会在这个空的根文件系统中存放文件。

dd: 读取源文件的内容并创建一个新文件,if 指定源文件内容,of 指定新文件名字,bs 和 count 指定新文件的大小 
mke2fs: 将新创建的 rootfs 格式化为 Linux 可识别的文件系统


清单 1. 创建空的根文件系统 

1 dd if=/dev/zero of=rootfs bs=65536 count=24000
2 mke2fs -F rootfs

步骤 2:挂载空的根文件系统,将 1.5G 的文件系统挂载在 mnt 目录下,然后通过 mnt 目录将内容写入根文件系统。 

清单 2. 挂载空的根文件系统

1 mkdir mnt
2 mount rootfs mnt -o loop

步骤 3:拷贝根文件目录的内容到文件系统 

清单 3. 拷贝根文件目录统

1 cp -rp yourRootDir mnt
2 umount mnt

拷贝完后根文件系统的内容,如图 2 所示: 

图 2. 根文件系统内容 
aaa 


步骤 4:完成根文件系统的创建,这时的 rootfs 没有被压缩,接下来我们用工具将其压缩成 Squash 格式的文件系统

mksquashfs-4.1 是在安装 Squash 工具的过程中生成的命令,用于将一个文件夹下的内容打包并压缩成一个文件系统。其后第一个参数为文件夹,第二个参数为生成的文件系统。


清单 4. 创建根文件系统

1 mkdir squashfs-dir
2 mv rootfs squshfs-dir
3 mksquashfs-4.1 squashfs-dir squashRootfs

到这里我们就完成了 Squash 压缩文件系统的创建。接下来我们将讨论如何在 Linux 启动的过程中加载这个压缩文件系统。

 

 

加载压缩文件系统所使用的工具

在加载压缩文件系统之前,我们需要确定您的 Linux 内核支持这种文件系统。 Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机制下,用户可以很方便的根据自己的需要制定实现存储资源的管理策略。

确保在 initrd 中已经集成“device-mapper

加载 Squash 压缩文件系统

可读写文件系统原理如图 3 所示:

图 3. 可读写文件系统原理 
aaa 

squashRootfs 里面存储了我们原始的根文件系统,我们在根文件系统中所有的写操作会直接写入 cowfile.out(cow:copy-on-write), 当我们读取根文件系统时,如果读取的内容没有变化,将直接从 squashRootfs 中读取,如果读取的内容被更新过,将从 cowfile.out 中读取。cowfile.out 文件的大小一般要比 squshRootfs 小,如果 cowfile.out 被写满,根文件系统的读写操作将会出错,因此有必要给 cowfile.out 设置一个合理的大小以防止被写满。

由于 rootfs 是被 initrd 加载的,因此我们需要在 initrd 里面加入装载 rootfs 的代码。initrd 整个的执行过程是调用 /sbin/init 这个脚本文件,因此我们在这个脚本的最后加入以下代码逻辑即可。

1. dm-mirror 为 device mapper 工作所需要的驱动 
2. mknod: 创建一些设备文件,参数 1:设备文件名 参数 2:设备类型 参数 3:主设备号 参数 4:次设备号 
3. 将 squashRootfs 通过 loop 设备的形式挂载在目录 /realroot/mnt/ 下 
4. 将 /realroot/mnt/rootfs 设置为 loop 设备,并和 /dev/loop1 绑定 
5. "|"之前的部分是构建 dmsetup 的参数,其中 $(blockdev --getsize /dev/loop1) 表示创建镜像文件的大小,/dev/loop1 /dev/loop2 表示镜像文件是以 /realroot/mnt/rootfs 和 /realroot/mnt/cowfile.out 为蓝本进行创建的(在前面的操作中 loop1 和 loop2 分别进行了绑定操作)


清单 5. 构建可读写文件系统脚本

01 # 加载驱动 ,参见注释 1 modprobe dm-mirror
02  
03 # 创建设备 ,参见注释 2 mknod /dev/zero c 1 5
04 mknod /dev/loop0 b 7 0
05 mknod /dev/loop1 b 7 1
06 mknod /dev/loop2 b 7 2
07 mkdir /dev/cow
08 mknod /dev/cow/ctl b 241 255
09 mknod /dev/cow/0 b 241 0
10  
11 # 挂载 squash 根文件系统,挂载完后您可以在 /realroot/mnt/ 下找到 rootfs 文件 ,参见注释 3 mount /realroot/mnt/squashRootfs /realroot/mnt/ -o loop
12  
13 # 设置 rootfs 为 loop 设备 ,参见注释 4 losetup /dev/loop1 /realroot/mnt/rootfs
14  
15 # 创建 cowfile.out 并挂载为 loop 设备,我们将来的写操作都会写入 cowfile.out
16 dd if=/dev/zero of=/realroot/mnt/cowfile.out bs=2K count=137500
17 losetup /dev/loop2 /realroot/mnt/cowfile.out
18  
19 # 将 /realroot/mnt/rootfs 和 /realroot/mnt/cowfile.out 结合起来创建一个逻辑根文件设备,
20 # 设备文件为 /dev/mapper/root_fs,,参见注释 5 echo "0 $(blockdev --getsize /dev/loop1) snapshot /dev/loop1 /dev/loop2 p 64" |
21 dmsetup create root_fs
22  
23 # 将上面创建的逻辑根文件设备 /dev/mapper/root_fs 挂载就可以看到一个可读写的根文件系统
24 mount /dev/mapper/root_fs /realroot/mnt/Image
25  
26 # 切换到最终可读写的根文件系统
27 cd /realroot/mnt/Image
28 chroot ./sbin/init -i

加下来您就可以看到 rootfs 的所有内容,如图 4 所示: 
图 4. 被挂载的根文件系统内容
aaa 

还可以在这个文件系统中进行写操作,如图 5 所示:

图 5. 根文件系统的写操作
aaa 

最重要的是 rootfs 没有被拷贝到内存中。

结束语

由于篇幅的限制,本文只给出了基本的描述。希望有更一步了解的读者可以通过对以下 linux 命令的学习来深入了解。

构建 Squash 压缩文件系统构所使用的主要命令:

  • mksquashfs:创建 Squash 压缩文件系统

使用 Squash 压缩文件系统构所使用的主要命令:

    • mknod:创建 Squash 压缩文件系统
    • losetup:设置并控制 Loop 设备
    • chroot:改变根目录
    • dmsetup:低水平逻辑卷管理

你可能感兴趣的:(linux)