Openwrt学习笔记(二)——Flash Layout and file system

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lee244868149/article/details/57076615

在大多数系统中,闪存不像RAM一样可以直接执行指令,所以闪存中的数据和指令需要拷贝到RAM中执行,比如存放在flash中的kernel需要bootload的帮忙,将kernel拷贝到RAM里面才能运行。

大多数router都是没有硬盘的,它使用闪存来完成相应的存储功能(包括存储固件以及系统数据),这种非易失性的闪存可以避免掉电丢失的问题。

我们常接触的闪存主要有两种:NOR flash 和NAND flash。如果闪存芯片和SOC直接连接并且又linux直接寻址,我们将它称作raw flash; 如果在SOC和flash芯片间有多加一个外部控制芯片,我们称它作“FTL (Flash Translation Layer) flash”。我们大多数嵌入式系统都是用raw flash,很少用SSD和USB这样的FTL flash。

老的router上用nor flash多,但是新的router上很多开始用nand flash和emmc等;一般nor flash有4/8/16/32M,相对nand flash来说存储量较小,价格更贵,但是它的优点是不会坏块, nand flash一般都是32~256M或者更大,但是它容易坏块,openwrt系统中squashFS的一个致命弱点是不能很好的处理坏块问题,一旦坏块就好重新烧写系统了,一个可选择的方式就是使用UBIFS文件系统。当然当前也有另外一种方案,就是使用emmc,坏块问题交由外部处理器完成。

There is a generic problem when running SquashFS on NAND: The issue is that SquashFS has no bad block management at all and requires all blocks on order; but for proper NAND bad block management you also need to be able to skip bad blocks and occasionally relocate blocks (see squashfs and NAND flash). That's why raw SquashFS is a bad idea on NAND (it works if you use a FTL like UBIFS).


如果使用的是raw flash,openwrt将会将这个寻址空间看作MTD,我们可以通过在bootload或者kernel中对这块存储空间进行分区的划分,我们通常描述为"partitionkernel starts at offsetx and ends at offsety". 一般我们都会对每个分区进行命名,方便更好的操作。

一般我们都会对flash进行如下分区的划分,从左到右分别存放bootloader,bootloader相关参数,kernel,文件系统,用户数据等.



下面是一些router的flash划分状况:


layer0 表示这个flash一共有8M

layer1表示这个flash总共划分了4个部分,分别存放uboot, uboot-env, factory参数(MAC,PIN)和FW;命名为MTD0,1,2,3

layer2表示firmware又分成两个部分,分别是kernel和rootfs,命名为MTD4,5
layer3表示rootfs分成两个部分/dev/root和rootfs_data

需要注意的是,mtd3和mtd4的起始位置是一样的,只是大小不一样。openwrt将文件系统分成两类,/dev/root分区使用squash FS,rootfs_data分区使用JFFS2;我们知道squash FS是只读不可写的,但是JFFS2可读可写,所以我们修改系统文件以后,一般保存在rootfs_data分区。
当前系统flash分区的划分信息可以在开机log中找到,也可以在/proc/mtd中查看:



文件系统

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

在非嵌入式系统中,文件系统通常储存在可直接读写的硬盘上,因此直接挂载到根目录后(例如:mount /dev/sda1 /mnt)就可以进行读写操作。

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

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

CramFS

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

SquashFS

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

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


挂载点

对于使用openwrt的嵌入式系统来说,因为硬件绝大多数采用flash,因此一般使用squashfs文件系统和jffs2文件系统。前者是只读的,后者是可写的。我们一般会把jffs2 mount到某个目录下。这样就存在某些目录如/bin是只读的(squashfs),某些目录是可读写的(jffs2),这样对文件的操作会依赖于文件系统的属性和文件的路径。    

openwrt使用了mini_fo文件系统,从用户的使用角度看,会觉得整个文件系统都是可写的。用户可以任意修改、删除、添加文件。这种文件系统可以认为是在squashfs和jffs2的文件系统之上实现了一个符号链接,如果用户读取只读文件,则链接到squashfs文件系统读取,如果用户对只读文件进行了修改,则将修改的文件放到jffs2文件系统上,并修改链接。    如果用户的系统不采用jffs2系统,openwrt会使用ramfs,依旧可以实现上面的功能,不过系统重启后,修改会丢失。

“/”是整个文件系统,它由“/rom”和“/overlay”组成,前者是squashFS,后者是JFFS2,我们平常访问文件的时候可以不用管这两个文件,只要访问“/”就可以了。

“/rom”目录包含一些基本的文件,比如busybox, dropbear or iptables,也包括一些基本的配置文件,但是不包括kernel,哪些文件全都放在SquashFS分区,因此这些文件不能修改与删除。但是因为我们使用OverlayFS文件系统,所以overlay-whiteout-symlinks可以创建在JFFS2分区上。

“/overlay”是一个可写的文件系统,它和“/rom”合并,同时在根目录下创建一个惟一的/-tree。它可以记录在router启动以后所有文件的修改,比如配置文件的修改或者包的安装。

每次系统访问“/”目录下的文件的时候,它首先会搜索“/overlay”文件系统,如果没有找到,然后搜索“/rom”文件系统。因此,“/overlay”覆盖在“/rom”文件系统上,从而创建出一种“/”根目录可写的效果,并且保证大多数内容是安全的,能够快速的访问“/rom”下面的内容。

如果需要删除一个“/rom”系统中的文件,那么系统就会在“/overlay”里面创建一个文件入口(或理解为一个透明软链接),这个软链接指向overlay-whiteout,而当我们访问overlay-whiteout的时候,返回的结果就是“该文件不存在”。

需要区分的是“/tmp”文件系统,这个文件系统是运行在RAM中的,并不是在flash中,所以/tmp是可实时读写的,但是squashFS是一个高度压缩的文件系统,存放在flash中,所以不能写入,写入的问题由JFFS2格式的overlay来解决,下面举个例子:

1. 在flash中的rootfs分区有两个文件系统,一个是squashFS(/dev/root),一个是JFFS2(rootfs_data,开机时创建)

2. 在文件系统挂载的时候会将“/rom”挂载到“/”, 将“/overlay”挂载到“/etc”目录(当然我们也可以挂载到其他目录,比如挂载到“/”就可以让整个文件系统可读写),这时候可以理解为给“/etc”目录铺上了一条通往jffs2文件系统的通道,这时“/etc”目录下的文件就可读可写了,因为jffs2文件系统是可写的。

3. 假设/etc目录下有一个文件b.conf和c.config,这时我们需要修改b.conf中的内容,那么系统就会先到/overlay/etc下找有没有b.conf,因为一开始b.conf没有改动过,所以在/overlay/etc下是找不到b.conf的。它接着会到/rom/etc目录下找,找到以后就会提取到RAM缓存让用户修改,修改完以后就会将b.conf保存到/overlay/etc目录下,并写进jffs2文件系统中,这时在/overlay/etc下就多了一个b.conf。这样,给我们的感觉就是,我们成功修改并写入了/etc/b.conf文件;如果我们要修改/sbin下面的文件,因为没有到达/overlay文件系统的通道,所以是不能写入并保存的,它会提示错误,给我们的感觉就是/sbin目录不可写。

4. 如果我们需要删除一个文件c.config, 那么系统就会在/overlay目录下面创建一个c.config文件的软链接,这软链接指向“overlay whiteout”,有点像我们常用的/dev/null,在JFFS2文件系统中,只要该文件指向“overlay whiteout”就表示该文件不存在,下面是在/overlay下找文件的command:


总结:

在路由器的FLASH上,内核中所使用的驱动是MTD设备驱动。

MTD(Memory Technology Devices,内存技术设备)是用于访问内存类设备(ROM、FLASH)的Linux驱动子系统。它的主要目的使FLASH类设备更加容易被访问,为此它在硬件和上层提供了一个抽象的接口,使得在操作系统下我们可以像操作硬盘一样操作这个设备。仔细观察过Linux启动信息的朋友会看到这么一段话:

[ 0.690000] 5 tp-link partitions found on MTD device spi0.0
[ 0.700000] Creating 5 MTD partitions on "spi0.0":
[ 0.700000] 0x000000000000-0x000000020000 : "u-boot"
[ 0.710000] 0x000000020000-0x00000012a290 : "kernel"
[ 0.730000] 0x00000012a290-0x0000007f0000 : "rootfs"
[ 0.760000] 0x000000300000-0x0000007f0000 : "rootfs_data"
[ 0.760000] 0x0000007f0000-0x000000800000 : "art"
[ 0.770000] 0x000000020000-0x0000007f0000 : "firmware"

这些信息表示当前系统识别到的FLASH分区。我们可以用电脑中的计算器计算一下,打开计算器,选择科学型、十六进制,输入名为art的分区容量用(800000-7f0000)结果为10000(十六进制),这个时候点击十进制,系统会自动将结果转换为十进制,再除以1024结果为64(K)表示这个分区容量为64k。在openwrt的系统中现在对atheros方案实现了自动查找分区结尾。

上面的几个分区,我来说明下(分区名称、分区容量、分区作用):

  • "u-boot":128KB,设备初始化程序+引导程序代码本身
  • "kernel" :1MB,存放系统内核的二进制代码,按照x86下的讲法是Raw分区,就是这里只有内核的二进制,不存在文件系统。
  • "rootfs":6.7MB,完整的系统文件包含只读和可写
  • "rootfs_data":4.9MB,在rootfs中的可写部分的位置
  • "art":64KB,EEPROM分区,在Atheros的方案中这个分区保存了无线的硬件参数
  • "firmware":7.9MB,完整的固件位置包含了除"u-boot"和"art"之外全部的内容

看的晕了? 这,我马上画个简单的图给大家看看:


这个是它的分区逻辑。请不要太在意这个地方,有点晕也没关系,继续往后面看,这个地方留着后边慢慢理解。

在系统中,可以执行以下指令查看当前系统分区:

每个分区在flash中的位置是/dev/mtdblockX这样的位置,比如你想把art分区里的数据读出来看看,那么就执行:

然后执行hexdump -C /tmp/1就可以看到这个分区的内容了。

系统的文件结构

前面说过系统在第一次启动的时候会格式化"可写分区",这在逻辑上到底是啥关系呢?

  1. 首先uboot启动了kernel完成之后,由kernel加载"ROM分区"(就是rootfs减去rootfs_data得到的那一块分区)
  2. ROM分区采用的是Linux内核支持的squashFS文件系统(一种压缩只读文件系统),加载完毕后将其挂载到/rom目录(同时也挂载为根文件系统)。
  3. 系统将使用JFFS2文件系统格式化rootfs_data这部分并且将这部分挂载到/overlay目录。
  4. 将/overlay透明挂载为/分区。
  5. 将一部分内存挂载为/tmp目录。

这个时候大家一定有一个问题:到底根文件系统是哪个?这个是OpenWRT设计的一个优点,它采用了一种叫Overlay透明挂载技术,首先将/rom挂载为/根文件,然后再用/overlay覆盖在/之上,这样,当你进行文件系统的变更,修改,所做的操作将在overlay中记录。rom是不改变的。而最简单的恢复出厂设置方法,即是删除掉/overlay下所有文件。

但是有时候,我们希望我们的系统是可写的,但是重开机以后希望上次的改动不要保留,恢复到原来的状态,那么应该有三种办法:

1. 关机的时候删除/overlay下面的所有内容(在reboot或reset命令里面做)

2. 开机的时候删除/overlay下面的所有内容(在/etc/init.d/boot脚本里面做,或者preinit中完成overlay文件系统挂载以后做)

3. 创建/tmp/root, 将root文件系统("/")挂载到 /tmp/root目录下,然后将/overlay挂载到“/”,这样就可以实现文件可读写,而且重开机以后修改的文件恢复原貌。因为/tmp文件是可写的,所以往/overlay中写入的东西实际存放到了RAM中,重开机的时候就会不见,从而继续从/rom中搜索。

后面将分析一下openwrt是怎么开机挂载文件系统的。

root:/# mount
rootfs on / type rootfs (rw)
/dev/root on /rom type squashfs (ro,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
root on /tmp/root type tmpfs (rw,noatime,mode=755)
overlayfs:/tmp/root on / type overlayfs (rw,noatime,lowerdir=/,upperdir=/tmp/root)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600)
debugfs on /sys/kernel/debug type debugfs (rw,noatime)
root:/# df
Filesystem           1K-blocks      Used Available Use% Mounted on
rootfs                  253324        68    253256   0% /
/dev/root                12288     12288         0 100% /rom
tmpfs                   253324       504    252820   0% /tmp
root                    253324        68    253256   0% /tmp/root
overlayfs:/tmp/root     253324        68    253256   0% /
tmpfs                      512         0       512   0% /dev

下面这种挂载方式是重启后文件修改还在的:

root@OpenWrt:/# mount
rootfs on / type rootfs (rw)
/dev/root on /rom type squashfs (ro,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
/dev/mmcblk0p16 on /overlay type ext4 (rw,noatime,data=ordered)
overlayfs:/overlay on / type overlayfs (rw,noatime,lowerdir=/,upperdir=/overlay)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600)
debugfs on /sys/kernel/debug type debugfs (rw,noatime)
root@OpenWrt:/# df
Filesystem           1K-blocks      Used Available Use% Mounted on
rootfs                 1257344      2144   1173280   0% /
/dev/root                14592     14592         0 100% /rom
tmpfs                   247992       332    247660   0% /tmp
/dev/mmcblk0p16        1257344      2144   1173280   0% /overlay
overlayfs:/overlay     1257344      2144   1173280   0% /
tmpfs                      512         0       512   0% /dev
root@OpenWrt:/# 


更多关于openwrt文件系统的介绍

没有更多推荐了,返回首页