父目录:linux文件系统

XFS 文件系统简介

回到顶部

CentOS 7 开始,默认的文件系统已经由原本的 EXT4 变成了 XFS 文件系统! 为什么 CentOS 要舍弃对 Linux 支持度最完整的 EXT 家族而改用 XFS 呢? 这是有一些原因存在的。
EXT 家族当前较伤脑筋的地方:支持度最广,但格式化超慢!
Ext 文件系统家族对于文件格式化的处理方面,采用的是预先规划出所有的 inode/block/meta data 等数据, 未来系统可以直接取用, 不需要再进行动态配置。
这种方式在早期磁盘容量还不大的时候没什么问题,但时至今日,磁盘容量越来越大,连传统的 MBR 都已经被 GPT 所取代, 连我们这些老人家以前听到的超大 TB 容量也已经不够看了!现在都已经说到 PB 或 EB 以上容量 了呢!那妳可以想象得到,当你的 TB 以上等级的传统 ext 家族文件系统在格式化的时候,光是系 统要预先分配 inode 与 block 就消耗你好多好多的人类时间了...
所以,后来立刻改成 xfs 文件系统了。另外,由于虚拟化的应用越来越广泛,而作为虚拟化磁盘来源的巨型文件 (单一文件好几个 GB 以上!) 也就越来越常见了。 这种巨型文件在处理上需要考虑到效能问题,否则虚拟磁盘的效率就会不太好看。因此,从 CentOS 7.x 开始, 文件系统已经由预设的 Ext4 变成了 xfs 这一个较适合高容量磁盘与巨型文件效能较佳的文件系统了。
基本上 xfs 就是一个日志式文件系统,而 CentOS 7.x 拿它当默认的文件系统,自然就是因为最早之 前,这个 xfs 就是被开发来用于高容量磁盘以及高性能文件系统之用, 因此,相当适合现在的系统 环境。此外,几乎所有 Ext4 文件系统有的功能, xfs 都可以具备!也因此在本小节前几部份谈到 文件系统时, 其实大部份的操作依旧是在 xfs 文件系统环境下介绍给各位的哩!
xfs 文件系统在资料的分布上,主要规划为三个部份,一个数据区 (data section)、一个文件系统活动 登录区 (log section)以及一个实时运作区 (realtime section)。 这三个区域的数据内容如下:
资料区 (data section) 基本上,数据区就跟我们之前谈到的 ext 家族一样,包括 inode/data block/superblock 等数据,都 放置在这个区块。 这个数据区与 ext 家族的 block group 类似,也是分为多个储存区群组 (allocation groups) 来分别放置文件系统所需要的数据。 每个储存区群组都包含了
(1)整个文件系统的 superblock
(2)剩余空间的管理机制
(3)inode 的分配与追踪。
此外,inode 与 block 都是系统需要用到时, 这才动态配置产生,所以格式化动作超级快!
另外,与 ext 家族不同的是, xfs 的 block 与 inode 有多种不同的容量可供设定,block 容量可由 512bytes ~ 64K 调配, 不过,Linux 的环境下, 由于内存控制的关系 (页面文件 pagesize 的容量之故),因此最高可以使用的 block 大小为 4K 而已! (initroot尝试格式化 block 成为 16K 是没问题的,不过,Linux 核心不给挂载! 所以格式化完成后也无法使用啦!) 至于 inode 容量可由 256bytes 到 2M 这么大!不过,大概还是保留 256bytes 的默认值就很够用了!
总之, xfs 的这个数据区的储存区群组 (allocation groups, AG),你就将它想成是 ext 家族的 block 群组 (block groups) 就对了! 本小节之前讲的都可以在这个区块内使用。 只是 inode 与 block 是动态产生,并非一开始于格式化就完成配置的。
文件系统活动登录区 (log section)在登录区这个区域主要被用来纪录文件系统的变化,其实有点像是日志区啦!文件的变化会在这里纪录下来,直到该变化完整的写入到数据区后, 该笔纪录才会被终结。 如果文件系统因为某些 缘故 (例如最常见的停电) 而损毁时,系统会拿这个登录区块来进行检验,看看系统挂掉之前, 文件系统正在运作些啥动作,藉以快速的修复文件系统。 因为系统所有动作的时候都会在这个区块做个纪录,因此这个区块的磁盘活动是相当频繁的! xfs设计有点有趣,在这个区域中, 妳可以指定外部的磁盘来作为 xfs 文件系统的日志区块喔! 例如,妳可以将 SSD 磁盘作为 xfs 的登录区,这样当系统需要进行任何活动时, 就可以更快速的进行工作!相当有趣!
实时运作区 (realtime section) 当有文件要被建立时,xfs 会在这个区段里面找一个到数个的 extent 区块,将文件放置在这个区块内, 等到分配完毕后,再写入到 data section 的 inode 与 block 去! 这个 extent 区块的大小得要在格式化的时候就先指定, 最小值是 4K 最大可到 1G。一般非磁盘阵列的磁盘默认为 64K容量, 而具有类似磁盘阵列的 stripe 情况下,则建议 extent 设定为与 stripe 一样大较佳。 这个extent 最好不要乱动,因为可能会影响到实体磁盘的效能喔。
XFS 文件系统的描述数据观察 刚刚讲了这么多,完全无法理会耶~有没有像 EXT 家族的 dumpe2fs 去观察 superblock 内容的相 关指令可以查阅呢?有啦!可以使用 xfs_info 去观察的! 详细的指令作法可以参考如下:

            [root@initroot ~]# xfs_info 挂载点|装置文件名
            
范例一:找出系统 /boot 这个挂载点底下的文件系统的 superblock 纪录
            [root@initroot ~]# df -T /boot
            Filesystem Type 1K-blocks
            /dev/vda2 xfs
            Used Available Use% Mounted on
            1038336 133704
            904632 13% /boot
            # 没错!可以看得出来是 xfs 文件系统的!来观察一下内容吧!
            [root@initroot ~]# xfs_info /dev/vda2
            1 meta-data=/dev/vda2 isize=256 agcount=4, agsize=65536 blks
            2 = sectsz=512 attr=2, projid32bit=1
            3 = crc=0 finobt=0
            = bsize=4096 blocks=262144, imaxpct=25
            = sunit=0 swidth=0 blks
            4
            data
            5
            6 naming =version 2 bsize=4096 ascii-ci=0 ftype=0
            7 log =internal bsize=4096 blocks=2560, version=2
            = sectsz=512 sunit=0 blks, lazy-count=1
            extsz=4096 blocks=0, rtextents=0
            8
            9
            realtime =none
          
上面的输出讯息可以这样解释:
第 1 行里面的 isize 指的是 inode 的容量,每个有 256bytes 这么大。 至于 agcount 则是前面谈到的储存区群组 (allocation group) 的个数,共有 4 个, agsize 则是指每个储存区群组具有 65536 个 block 。 配合第 4 行的 block 设定为 4K,因此整个文件系统的容量应该就是 4*65536*4K 这么大!
第 2 行里面 sectsz 指的是逻辑扇区 (sector) 的容量设定为 512bytes 这么大的意思。
第 4 行里面的 bsize 指的是 block 的容量,每个 block 为 4K 的意思,共有 262144 个 block 在这个文件系统内。
第 5 行里面的 sunit 与 swidth 与磁盘阵列的 stripe 相关性较高。
这部份我们底下格式化的时候会举一个例子来说明。
第 7 行里面的 internal 指的是这个登录区的位置在文件系统内,而不是外部设备的意思。且占用了 4K * 2560 个 block,总共约 10M 的容量。
第 9 行里面的 realtime 区域,里面的 extent 容量为 4K。不过目前没有使用。
由于我们并没有使用磁盘阵列,因此上头这个装置里头的 sunit 与 extent 就没有额外的指定特别的值。 根据 xfs(5) 的说明,这两个值会影响到你的文件系统性能, 所以格式化的时候要特别留意喔!
上面的说明大致上看看即可,比较重要的部份已经用特殊字体圈起来,你可以瞧一瞧先!

文件系统实验

如果要格式化一个分区来研究文件系统格式则必须有一个空闲的磁盘分区,为了方便实验,我们把一个文件当作分区来格式化,然后分析这个文件中的数据来印证上面所讲的要点。 首先创建一个1MB的文件并清零:
            $ dd if=/dev/zero of=fs count=256 bs=4K
            
我们知道cp命令可以把一个文件拷贝成另一个文件,而dd命令可以把一个文件的一部分拷贝成另一个文件。 这个命令的作用是把/dev/zero文件开头的1M(256×4K)字节拷贝成文件名为fs的文件。
刚才我们看到/dev/zero是一个特殊的设备文件,它没有磁盘数据块,对它进行读操作传给设备号为1, 5的驱动程序。 /dev/zero这个文件可以看作是无穷大的,不管从哪里开始读,读出来的都是字节0x00。因此这个命令拷贝了1M个0x00到fs文件。 if和of参数表示输入文件和输出文件,count和bs参数表示拷贝多少次,每次拷多少字节。
做好之后对文件fs进行格式化,也就是把这个文件的数据块合起来看成一个1MB的磁盘分区,在这个分区上再划分出块组。
            $ mke2fs fs
            mke2fs 1.40.2 (12-Jul-2007)
            fs is not a block special device.
            Proceed anyway? (y,n) (输入y回车)
            Filesystem label=
            OS type: Linux
            Block size=1024 (log=0)
            Fragment size=1024 (log=0)
            128 inodes, 1024 blocks
            51 blocks (4.98%) reserved for the super user
            First data block=1
            Maximum filesystem blocks=1048576
            1 block group
            8192 blocks per group, 8192 fragments per group
            128 inodes per group

            Writing inode tables: done
            Writing superblocks and filesystem accounting information: done

            This filesystem will be automatically checked every 27 mounts or
            180 days, whichever comes first. Use tune2fs -c or -i to override.
          
格式化一个真正的分区应该指定块设备文件名,例如/dev/sda1,而这个fs是常规文件而不是块设备文件,mke2fs认为用户有可能是误操作了, 所以给出提示,要求确认是否真的要格式化,输入y回车完成格式化。
现在fs的大小仍然是1MB,但不再是全0了,其中已经有了块组和描述信息。用dumpe2fs工具可以查看这个分区的超级块和块组描述符表中的信息:
            $ dumpe2fs fs
            dumpe2fs 1.40.2 (12-Jul-2007)
            Filesystem volume name: <none>
            Last mounted on: <not available>
            Filesystem UUID: 8e1f3b7a-4d1f-41dc-8928-526e43b2fd74
            Filesystem magic number: 0xEF53
            Filesystem revision #: 1 (dynamic)
            Filesystem features: resize_inode dir_index filetype sparse_super
            Filesystem flags: signed directory hash
            Default mount options: (none)
            Filesystem state: clean
            Errors behavior: Continue
            Filesystem OS type: Linux
            Inode count: 128
            Block count: 1024
            Reserved block count: 51
            Free blocks: 986
            Free inodes: 117
            First block: 1
            Block size: 1024
            Fragment size: 1024
            Reserved GDT blocks: 3
            Blocks per group: 8192
            Fragments per group: 8192
            Inodes per group: 128
            Inode blocks per group: 16
            Filesystem created: Sun Dec 16 14:56:59 2007
            Last mount time: n/a
            Last write time: Sun Dec 16 14:56:59 2007
            Mount count: 0
            Maximum mount count: 30
            Last checked: Sun Dec 16 14:56:59 2007
            Check interval: 15552000 (6 months)
            Next check after: Fri Jun 13 14:56:59 2008
            Reserved blocks uid: 0 (user root)
            Reserved blocks gid: 0 (group root)
            First inode: 11
            Inode size: 128
            Default directory hash: tea
            Directory Hash Seed: 6d0e58bd-b9db-41ae-92b3-4563a02a5981


            Group 0: (Blocks 1-1023)
            Primary superblock at 1, Group descriptors at 2-2
            Reserved GDT blocks at 3-5
            Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
            Inode table at 8-23 (+7)
            986 free blocks, 117 free inodes, 2 directories
            Free blocks: 38-1023
            Free inodes: 12-128

            128 inodes per group, 8 inodes per block, so: 16 blocks for inode table
            
根据上面讲过的知识简单计算一下,块大小是1024字节,1MB的分区共有1024个块,第0个块是启动块,启动块之后才算ext2文件系统的开始,因此Group 0占据第1个到第1023个块,共1023个块。块位图占一个块,共有1024×8=8192个bit,足够表示这1023个块了,因此只要一个块组就够了。 默认是每8KB分配一个inode,因此1MB的分区对应128个inode,这些数据都和dumpe2fs的输出吻合。
用常规文件制作而成的文件系统也可以像磁盘分区一样mount到某个目录,例如:
            $ sudo mount -o loop fs /mnt
            $ cd /mnt/
            $ ls -la
            total 17
            drwxr-xr-x 3 akaedu akaedu 1024 2008-10-25 12:20 .
            drwxr-xr-x 21 root root 4096 2008-08-18 08:54 ..
            drwx------ 2 root root 12288 2008-10-25 12:20 lost+found
            
-o loop选项告诉mount这是一个常规文件而不是一个块设备文件。mount会把它的数据块中的数据当作分区格式来解释。
文件系统格式化之后在根目录下自动生成三个子目录:.,..和lost+found。其它子目录下的.表示当前目录,..表示上一级目录,而根目录的.和..都表示根目录本身。
lost+found目录由e2fsck工具使用,如果在检查磁盘时发现错误,就把有错误的块挂在这个目录下,因为这些块不知道是谁的,找不到主,就放在这里“失物招领”了。
现在可以在/mnt目录下添加删除文件,这些操作会自动保存到文件fs中。然后把这个分区umount下来,以确保所有的改动都保存到文件中了。
            $ sudo umount /mnt
            
注意,下面的实验步骤是对新创建的文件系统做的,如果你在文件系统中添加删除过文件,跟着做下面的步骤时结果可能和我写的不太一样,不过也不影响理解。
现在我们用二进制查看工具查看这个文件系统的所有字节,并且同dumpe2fs工具的输出信息相比较,就可以很好地理解文件系统的存储布局了。
            $ od -tx1 -Ax fs
            000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
            *
            000400 80 00 00 00 00 04 00 00 33 00 00 00 da 03 00 00
            000410 75 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
            ......
            
其中以*开头的行表示这一段数据全是零因此省略了。下面详细分析od输出的信息。
从000000开始的1KB是启动块,由于这不是一个真正的磁盘分区,启动块的内容全部为零。从000400到0007ff的1KB是超级块,对照着dumpe2fs的输出信息,详细分析如下: 图 29.3. 超级块 超级块 超级块中从0004d0到末尾的204个字节是填充字节,保留未用,上图未画出。注意,ext2文件系统中各字段都是按小端存储的, 如果把字节在文件中的位置看作地址,那么靠近文件开头的是低地址,存低字节。各字段的位置、长度和含义详见[ULK]。
从000800开始是块组描述符表,这个文件系统较小,只有一个块组描述符,对照着dumpe2fs的输出信息分析如下:
            ...
            Group 0: (Blocks 1-1023)
            Primary superblock at 1, Group descriptors at 2-2
            Reserved GDT blocks at 3-5
            Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
            Inode table at 8-23 (+7)
            986 free blocks, 117 free inodes, 2 directories
            Free blocks: 38-1023
            Free inodes: 12-128
            ...
            
图 29.4. 块组描述符 块组描述符 整个文件系统是1MB,每个块是1KB,应该有1024个块,除去启动块还有1023个块,分别编号为1-1023,它们全都属于Group 0。其中,Block 1是超级块, 接下来的块组描述符指出,块位图是Block 6,因此中间的Block 2-5是块组描述符表,其中Block 3-5保留未用。块组描述符还指出,inode位图是Block 7,inode表是从Block 8开始的,那么inode表到哪个块结束呢?由于超级块中指出每个块组有128个inode,每个inode的大小是128字节,因此共占16个块, inode表的范围是Block 8-23。
从Block 24开始就是数据块了。块组描述符中指出,空闲的数据块有986个,由于文件系统是新创建的,空闲块是连续的Block 38-1023,用掉了前面的Block 24-37。从块位图中可以看出,前37位(前4个字节加最后一个字节的低5位)都是1,就表示Block 1-37已用:
            001800 ff ff ff ff 1f 00 00 00 00 00 00 00 00 00 00 00
            001810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
            *
            001870 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80
            001880 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
            *
            
在块位图中,Block 38-1023对应的位都是0(一直到001870那一行最后一个字节的低7位),接下来的位已经超出了文件系统的空间,不管是0还是1都没有意义。 可见,块位图每个字节中的位应该按从低位到高位的顺序来看。以后随着文件系统的使用和添加删除文件,块位图中的1就变得不连续了。
块组描述符指出,空闲的inode有117个,由于文件系统是新创建的,空闲的inode也是连续的,inode编号从1到128,空闲的inode编号从12到128。 从inode位图可以看出,前11位都是1,表示前11个inode已用:
            001c00 ff 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00
            001c10 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
            *
            
以后随着文件系统的使用和添加删除文件,inode位图中的1就变得不连续了。
001c00这一行的128位就表示了所有inode,因此下面的行不管是0还是1都没有意义。 已用的11个inode中,前10个inode是被ext2文件系统保留的,其中第2个inode是根目录,第11个inode是lost+found目录,块组描述符也指出该组有两个目录,就是根目录和lost+found。
探索文件系统还有一个很有用的工具debugfs,它提供一个命令行界面,可以对文件系统做各种操作,例如查看信息、恢复数据、修正文件系统中的错误。 下面用debugfs打开fs文件,然后在提示符下输入help看看它都能做哪些事情:
            $ debugfs fs
            debugfs 1.40.2 (12-Jul-2007)
            debugfs: help
            
在debugfs的提示符下输入stat /命令,这时在新的一屏中显示根目录的inode信息:
            Inode: 2 Type: directory Mode: 0755 Flags: 0x0 Generation: 0
            User: 1000 Group: 1000 Size: 1024
            File ACL: 0 Directory ACL: 0
            Links: 3 Blockcount: 2
            Fragment: Address: 0 Number: 0 Size: 0
            ctime: 0x4764cc3b -- Sun Dec 16 14:56:59 2007
            atime: 0x4764cc3b -- Sun Dec 16 14:56:59 2007
            mtime: 0x4764cc3b -- Sun Dec 16 14:56:59 2007
            BLOCKS:
            (0):24
            TOTAL: 1
            
按q退出这一屏,然后用quit命令退出debugfs:
            debugfs: quit
            
把以上信息和od命令的输出对照起来分析: 图 29.5. 根目录的inode 根目录的inode
上图中的st_mode以八进制表示,包含了文件类型和文件权限,最高位的4表示文件类型为目录(各种文件类型的编码详见stat(2)),低位的755表示权限。 Size是1024,说明根目录现在只有一个数据块。Links为3表示根目录有三个硬链接,分别是根目录下的.和..,以及lost+found子目录下的..。 注意,虽然我们通常用/表示根目录,但是并没有名为/的硬链接,事实上,/是路径分隔符,不能在文件名中出现。
这里的Blockcount是以512字节为一个块来数的,并非格式化文件系统时所指定的块大小,磁盘的最小读写单位称为扇区(Sector),通常是512字节, 所以Blockcount是磁盘的物理块数量,而非分区的逻辑块数量。根目录数据块的位置由上图中的Blocks[0]指出,也就是第24个块, 它在文件系统中的位置是24×0x400=0x6000,从od命令的输出中找到006000地址,它的格式是这样:
图 29.6. 根目录的数据块 根目录的数据块 目录的数据块由许多不定长的记录组成,每条记录描述该目录下的一个文件,在上图中用框表示。 第一条记录描述inode号为2的文件,也就是根目录本身,该记录的总长度为12字节,其中文件名的长度为1字节, 文件类型为2(见下表,注意此处的文件类型编码和st_mode不一致),文件名是.。
表 29.1. 目录中的文件类型编码
编码 文件类型
0 Unknown
1 Regular file
2 Directory
3 Character device
4 Block device
5 Named pipe
6 Socket
7 Symbolic link
第二条记录也是描述inode号为2的文件(根目录),该记录总长度为12字节,其中文件名的长度为2字节,文件类型为2,文件名字符串是..。 第三条记录一直延续到该数据块的末尾,描述inode号为11的文件(lost+found目录),该记录的总长度为1000字节(和前面两条记录加起来是1024字节), 文件类型为2,文件名字符串是lost+found,后面全是0字节。如果要在根目录下创建新的文件,可以把第三条记录截短,在原来的0字节处创建新的记录。 如果该目录下的文件名太多,一个数据块不够用,则会分配新的数据块,块编号会填充到inode的Blocks[1]字段。
debugfs也提供了cd、ls等命令,不需要mount就可以查看这个文件系统中的目录,例如用ls查看根目录:
2 (12) . 2 (12) .. 11 (1000) lost+found
列出了inode号、记录长度和文件名,这些信息都是从根目录的数据块中读出来的。
习题
1、请读者仿照对根目录的分析,自己分析lost+found目录的inode和数据块的格式。
2、mount这个文件系统,在里面添加删除文件,然后umount下来,再次分析它的格式,和原来的结果比较一下看哪些字节发生了变化。
数据块寻址 如果一个文件有多个数据块,这些数据块很可能不是连续存放的,应该如何寻址到每个块呢?根据上面的分析,根目录的数据块是通过其inode中的索引项Blocks[0]找到的, 事实上,这样的索引项一共有15个,从Blocks[0]到Blocks[14],每个索引项占4字节。前12个索引项都表示块编号,例如上面的例子中Blocks[0]字段保存着24, 就表示第24个块是该文件的数据块,如果块大小是1KB,这样可以表示从0字节到12KB的文件。如果剩下的三个索引项Blocks[12]到Blocks[14]也是这么用的, 就只能表示最大15KB的文件了,这是远远不够的,事实上,剩下的三个索引项都是间接索引。
索引项Blocks[12]所指向的块并非数据块,而是称为间接寻址块(Indirect Block),其中存放的都是类似Blocks[0]这种索引项,再由索引项指向数据块。设块大小是b,那么一个间接寻址块中可以存放b/4个索引项,指向b/4个数据块。 所以如果把Blocks[0]到Blocks[12]都用上,最多可以表示b/4+12个数据块,对于块大小是1K的情况,最大可表示268K的文件。 如下图所示,注意文件的数据块编号是从0开始的,Blocks[0]指向第0个数据块,Blocks[11]指向第11个数据块,Blocks[12]所指向的间接寻址块的第一个索引项指向第12个数据块,依此类推。
图 29.7. 数据块的寻址 数据块的寻址
从上图可以看出,索引项Blocks[13]指向两级的间接寻址块,最多可表示(b/4)2+b/4+12个数据块, 对于1K的块大小最大可表示64.26MB的文件。索引项Blocks[14]指向三级的间接寻址块,最多可表示(b/4)3+(b/4)2+b/4+12个数据块,对于1K的块大小最大可表示16.06GB的文件。
可见,这种寻址方式对于访问不超过12个数据块的小文件是非常快的,访问文件中的任意数据只需要两次读盘操作,一次读inode(也就是读索引项)一次读数据块。 而访问大文件中的数据则需要最多五次读盘操作:inode、一级间接寻址块、二级间接寻址块、三级间接寻址块、数据块。 实际上,磁盘中的inode和数据块往往已经被内核缓存了,读大文件的效率也不会太低。

相关阅读:
linux基础
linux怎么学
计算机启动过程详解
磁盘构造和磁盘分区
什么是linux shell
什么是linux shell环境变量
linux shell环境配置文件解析
initroot编辑整理,转载请注明www.initroot.com