Linux 之旅 5:磁盘与文件系统管理
Linux文件系统
文件系统特性
我们知道,对于一个新的存储设备,无论是移动硬盘还是U盘,在第一次连接电脑后一般都会提示要格式化后使用,那格式化是做什么用的呢?
简单来说,每种操作系统都有独特的数据存储和组织方式,也就是所谓的“文件系统”,而格式化就是将空白存储器,或者说非当前操作系统支持的文件系统格式转化为支持的一种文件系统格式的过程。虽然这么说有点拗口,但意思应该还是比较明确的。
所以说存储器只有经过格式化以后才能正常挂载到操作系统中供操作系统使用。
一般来说文件系统由以下几部分组成:
- 超级区块(super block):记录文件系统的整体信息,包括inode与数据区块的总量、使用量、剩余量以及文件系统的格式与相关信息等。
- inode:记录文件的属性,一个文件占用一个inode,同时记载此文件所在的数据区块(block)号码。
- 数据区块(block):记录文件内容,如果一个区块不够,会占用多个区块。
这里的文件属性指读写权限、拥有者、所属组以及修改时间等非文件内容的相关信息。
虽然大部分文件系统都由这几部分构成,但在实现上有所不同,比如Linux文件系统ext2中,一个文件所关联的所有block都会记载在其对应的inode中,也就是说操作系统在读取这个文件的时候只要读取该文件的inode就可以知道其所有的block块,然后一次性读入内存(见图7.1.1)。但在老式的Windows文件系统FAT中不是如此,FAT中的inode仅会记录第一个block块的编号,要想知道第二个block编号则需要先读取第一个block之后才能知道,也就是说操作系统必须要依次读取:inode–>block1–>block2这样,一直到最后一个文件块(见图7.1.2)。如果文件系统的存储介质是SSD也问题不大,但如果是机械硬盘就很影响性能,想想看,前者最多也就是磁头扫描两圈(第一圈找inode,第二圈找block)就结束了,但后者的最差情况很可能是inode+block数量的圈数(如果block块过于分散的话),这也是为什么老式的Windows会建议用户定期进行碎片整理的原因,将一个文件的block整理在一起显然是有利于FAT之类的文件系统的执行效率。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5mSjZQmM-1628498323736)(https://image2.icexmoon.xyz/image/image-20210808143611114.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ESuRrpr-1628498323741)(https://image2.icexmoon.xyz/image/image-20210808143625217.png)]
(图片引用自鸟哥的私房菜)
在分类上,ext2这种inode记录所有关联block的文件系统称作索引式文件系统(indexed allocation)。
Linux 的 ext2 文件系统(inode)
上边说了,ext2的文件系统是分为超级区块、inode和block的,具体到实际操作,因为即使是单个分区而非整块磁盘,也可能容量巨大,所以ext2文件系统会将一个分区划分为多个区块群组(block group)进行管理,每个区块群组会有各自的inode、block和超级区块(见图7.1.3)。
(图片用自鸟哥的私房菜)
数据区块
ext2的block有1k、2k和4k三种大小,且在格式化的时候就会确定下来,不可更改。
此外,ext2的block还有以下限制:
- 区块的大小与数量在格式化完毕后就不可更改(除非重新格式化)。
- 每个区块仅能放置最多一个文件的数据。
- 如果文件大于区块大小,会占用多个区块。
- 如果文件小于区块大小,会浪费掉剩余空间。
因为ext2的block有如上特性,所以对于大型文件存储来说,如果ext2的区块太小,存储单个大型文件就会占用很多个区块,inode也需要记录多个区块的编号,这可能会影响到文件系统的读写性能。
inode
之前说了,inode记录文件属性,具体包括以下数据:
- 读写属性(read、write、execute)
- 拥有者和群组(owner、group)
- 大小
- 建立或状态改变的时间(ctime,status time)
- 最后一次读取时间(atime,access time)
- 最后一次修改时间(mtime,modify time)
- 特殊属性,如SetUID
- 真正的内容指向(pointer)
与block类似,inode的大小和数量也是在格式化的时候就确定了的,除此以外还具有以下特点:
- 每个inode大小固定为128B(新的 ext4 与 xfs 可设置到 256B)
- 每个文件都只会占用一个inode
- 因为上一条的原因,文件系统能够建立的文件数量上限为inode的数量上限
- 系统在读取或执行文件的时候,会先查找到对应的inode,并依据inode中记录的文件权限进行判断,来决定用户是否有权限进行读取或执行该文件。
之前说了,单个文件最多也只会有一个inode,可能会有比较机灵的人会质疑,单个inode大小有限,如果某个大文件占用很多个区块,导致其inode需要存储的block编号过多,超过了单个inode大小怎么办。
其实不用担心,并非所有的inode–>block指向都是直接指向,如果占用的block过多,inode会间接指向一个记录了实际block编号的block,用这种间接指向的方式来记录超过自身容量的block编号,在此基础上甚至还可以双间接或三间接(也就是提升间接的层次),所以无论是关联多少个block编号,无论多大的文件,都是可以用一个inode完成记录的(可以参考图7.1.4)。
(图片引用自鸟哥的私房菜)
超级区块
超级区块记录了整个文件系统的相关信息,具体包括:
- block与inode的总量
- 未使用与已使用的block和inode数量
- block与inode的大小
- 文件系统的挂载时间、最后一次写入数据的时间、最近一次检验磁盘(fsck)的时间等文件系统相关信息。
- 一个有效位数值,表示该文件系统是否已经挂载(0是,1否)
虽然我们上边说过,在实际中一个ext2文件系统是由多个区块群组构成的,每个群组都有自己的超级区块,也就是会有多个超级区块。但是,除了第一个区块群组的超级区块是真正使用中起作用的超级区块以外,其它区块群组的超级区块仅起到备份的作用,即真实超级区块出现问题的时候可以用其它的超级区块来修复。
与目录树的关系
在文件系统中,目录会占用一个inode和至少一个block,inode中存放该目录的权限和其它属性数据以及block编号,block中会存放目录下的文件和子目录名称,以及对应的inode编号。
要观察文件或目录的inode编号可以这样:
[icexmoon@xyz ~]$ ls -ali
总用量 44
67 drwx------. 15 icexmoon icexmoon 4096 8月 8 15:01 .
64 drwxr-xr-x. 3 root root 22 7月 24 14:45 ..
75 -rw-------. 1 icexmoon icexmoon 860 8月 7 15:09 .bash_history
68 -rw-r--r--. 1 icexmoon icexmoon 18 4月 1 2020 .bash_logout
69 -rw-r--r--. 1 icexmoon icexmoon 193 4月 1 2020 .bash_profile
70 -rw-r--r--. 1 icexmoon icexmoon 231 4月 1 2020 .bashrc
此外我们还可以观察目录的大小:
[icexmoon@xyz ~]$ ls -ldh / /boot /usr/sbin /proc /sys
dr-xr-xr-x. 17 root root 224 7月 24 14:45 /
dr-xr-xr-x. 5 root root 4.0K 7月 24 14:47 /boot
dr-xr-xr-x. 227 root root 0 8月 8 15:00 /proc
dr-xr-xr-x. 13 root root 0 8月 8 15:00 /sys
dr-xr-xr-x. 2 root root 20K 7月 24 14:38 /usr/sbin
可以看到/boot
占用了4k,这并不是说/boot
目录及其下的内容实际大小是4k,而是目录本身占用了一个block,大小是4k,而/usr/sbin
因为其下的文件和子目录很多,所以占用了5个block,所以是20k。知道了这个就会理解ls
命令输出的目录大小是怎么一回事了。
/boot
和/proc
大小是0是因为他们是虚拟文件系统,存在于内存而非本地存储中,所以是0。/
的大小为224B可能是因为这里我的文件系统是xfs
,使用了不同大小的block所致。
目录树读取
理解了上边这些文件系统相关的知识后,关于文件的读写和执行权限应该会有更清楚的认识,即目录树对文件的权限影响。
我们知道,文件系统是由inode和block组织起来的,具体到我们要读取一个文件,操作系统必须先从根目录开始,先读取根目录的inode,然后是根目录的block,然后才能知道根目录的子目录的inode,这样依次进行,直到读取最终文件的inode和block。如果在中途的某个中间目录的inode读取的时候,操作系统发现你并没有相应的权限,就会终止整个行为。
这也就是为什么有时候明明我们拥有某个文件的读写权限,但是不能操作,这很可能是因为缺少其父目录的权限导致的。
ext2/ext3/ext4 文件的存取与日志式文件系统的功能
前面我们说了文件系统中的文件读取操作,对于写入操作,文件系统需要执行以下步骤:
- 先查看相应目录,确认用户是否有新建文件的权限。
- 在超级区块中的inode对照表中找一个没有使用的inode,并写入文件相关的属性数据。
- 在超级区块中的block对照表中找一个或多个没有使用的block,并写入文件内容,并在inode中记录这些block编号。
- 将使用的inode和block编号更新到inode对照表和block对照表,并更新超级区块的内容。
数据不一致(inconsistent)状态
就像上面展示的那样,实际使用文件系统的时候,每个新建文件或者修改文件都涉及到多个地方数据的修改,一旦没有得到完整执行,比如使用了某个inode或者多个block的时候还没来得及更新超级区块就断电关机了,就会导致文件系统出现错误,即超级区块的记录与实际使用情况不一致。
在这时候就必须进行数据一致性检查,但是传统的文件系统,如ext2,对所有数据进行一致性检查极其耗费时间,所以就产生了新的日志式文件系统。
日志式文件系统
日志式文件系统的核心思想是如果进行类似的新建或修改等影响到多个地方数据同步的操作,就在执行的同时在相应的日志中添加一条记录,记录对某某文件执行某某操作,如果完整执行了操作,就消除该记录,如果没有,在我们进行数据一致性检查的时候就只要检查相应的日志即可,就可以快速发现问题并处理,并不需要检查文件系统上的所有数据,自然修复速度也就快的多。
具体分为以下步骤:
- 预备:当需要写入一个文件时,在日志记录区块记录准备写入某文件。
- 实际写入:写入文件的权限和数据,并更在超级区块中更新相应数据。
- 结束:完成数据与超级区块更新后,在日志记录区块中完成该文件的记录。
Linux 文件系统的运行
在使用Linux进行文件读写的时候,出于效率的考虑,Linux会使用异步处理(asynchronously)的方式,将文件加载到内存中进行操作,并不会及时写入磁盘,因为内存的读写速度要比磁盘快的多。操作系统仅会在需要的时候,比如你手动执行了保存或者电脑关机的时候将已经修改了但还没有写入磁盘的内容(称为Dirty)进行磁盘写入操作。但这就面临一个和上边类似的问题,即如果没有写入就非正常关机,就会导致磁盘上的数据不一致。
我们可以通过sync
命令显式地将内存中的Dirty数据写入磁盘。
挂载点的意义
我们已经直到了何为文件系统,而一个文件系统必须要结合到目录树才能使用,而将文件系统与目录树结合的操作我们称之为挂载。
挂载点一定是目录,且该目录就是进入文件系统的入口。
# 观察本机的挂载点
[icexmoon@xyz ~]$ df -h
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 470M 0 470M 0% /dev
tmpfs 487M 0 487M 0% /dev/shm
tmpfs 487M 8.6M 478M 2% /run
tmpfs 487M 0 487M 0% /sys/fs/cgroup
/dev/mapper/centos-root 10G 3.9G 6.2G 39% /
/dev/sda2 1014M 172M 843M 17% /boot
/dev/mapper/centos-home 5.0G 119M 4.9G 3% /home
tmpfs 98M 24K 98M 1% /run/user/1000
# 打印挂载点的inode
[icexmoon@xyz ~]$ ls -dil / /boot /home
64 dr-xr-xr-x. 17 root root 224 7月 24 14:45 /
64 dr-xr-xr-x. 5 root root 4096 7月 24 14:47 /boot
64 drwxr-xr-x. 3 root root 22 7月 24 14:45 /home
从上边的示例可以看到,虽然/
、/boot
、/home
是三个不同的挂载点和目录,但它们的inode相同,都是64。这是因为它们是三个不同的文件系统,而且在各自文件系统中都是顶层目录,inode都是64(其它属性的不同也说明了这三者的确是不同的目录)。
我们需要牢记inode仅代表在所在文件系统中的编号,并不能拿不同的文件系统的inode来比较,就像上面三个inode编号相同的情况。
其它Linux支持的文件系统和VFS
常见的文件系统主要有:
- 传统文件系统:ext2\minix\FAT(用vfat模块)\iso9660(光盘)等
- 日志式文件系统:ext3\ext4\ReiserFS\Windows’ NTFS\IBM’s JFS
- 网络文件系统:NFS\SMBFS
想要直到当前的Linux系统支持哪些文件系统,可以:
[icexmoon@xyz ~]$ ls -l /lib/modules/$(uname -r)/kernel/fs
总用量 20
-rw-r--r--. 1 root root 5992 10月 20 2020 binfmt_misc.ko.xz
drwxr-xr-x. 2 root root 25 7月 24 14:35 btrfs
drwxr-xr-x. 2 root root 30 7月 24 14:35 cachefiles
drwxr-xr-x. 2 root root 24 7月 24 14:35 ceph
drwxr-xr-x. 2 root root 24 7月 24 14:35 cifs
drwxr-xr-x. 2 root root 26 7月 24 14:35 cramfs
drwxr-xr-x. 2 root root 23 7月 24 14:35 dlm
drwxr-xr-x. 2 root root 26 7月 24 14:35 exofs
drwxr-xr-x. 2 root root 24 7月 24 14:35 ext4
drwxr-xr-x. 2 root root 60 7月 24 14:35 fat
drwxr-xr-x. 2 root root 27 7月 24 14:35 fscache
drwxr-xr-x. 2 root root 42 7月 24 14:35 fuse
drwxr-xr-x. 2 root root 24 7月 24 14:35 gfs2
drwxr-xr-x. 2 root root 25 7月 24 14:35 isofs
查看系统已加载到内存中的对文件系统的支持列表:
[icexmoon@xyz ~]$ cat /proc/filesystems
nodev sysfs
nodev rootfs
nodev ramfs
nodev bdev
nodev proc
nodev cgroup
nodev cpuset
nodev tmpfs
nodev devtmpfs
nodev debugfs
Linux VFS(Virtual Filesystem Switch)
因为有如此之多的文件系统,所以对于操作系统而言,管理不同的文件系统自然是个大麻烦。
事实上所有Linux都通过一个名为VFS的内核功能读取具体的文件系统(可以视作不同文件系统的兼容层)。
可以参考下图:
(图片引用自鸟哥的私房菜)
XFS文件系统简介
之前介绍ext2的时候我们说过,ext2会在格式化的时候就确定所有的inode与block,自然也就需要对所有空间进行初始化工作,这就意味着超长时间的格式化,分区的容量越大就需要越久。
和ext2相比,xfs在格式化方面灵活的多,所需时间更是少的多。简单的说这是一个适合高容量磁盘和巨型文件且性能不错的文件系统。
XFS文件系统的配置
XFS文件系统主要分为这几个部分:
-
数据区(data section):
包括inode、block和超级区块等,与ext2类似,也分为多个存储区群组(allocation groups),每个群组都包含:(1)整个文件系统的超级区块(2)剩余空间的管理机制(3)inode的分配与追踪。此外,inode和block都是需要时才动态分配产生,所以可以快速格式化。
xfs的inode和block有多种不同的容量可供设置(512B~64KB),但因为Linux页面文件容量(pagesize)的限制,最大只能使用4KB的block。
-
文件系统活动日志区(log section):
日志区块,记录文件系统的变化。可以指定外部磁盘作为xfs文件系统的日志区块,例如使用SSD。
-
实时运行区(realtime section):
可以看作实际执行存储分配的区块。当有文件需要新建时,xfs会在这个区段中找一个到多个extend区块,将文件放置在这个区块中,等到分配完毕,会写入到数据区的inode与区块中。
原书这里将
log section
写作登录区,我认为是翻译错误,这里和登录不搭边,应当是日志区。
XFS 文件系统的描述数据观察
[icexmoon@xyz ~]$ df -h
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 470M 0 470M 0% /dev
tmpfs 487M 0 487M 0% /dev/shm
tmpfs 487M 8.6M 478M 2% /run
tmpfs 487M 0 487M 0% /sys/fs/cgroup
/dev/mapper/centos-root 10G 3.9G 6.2G 39% /
/dev/sda2 1014M 172M 843M 17% /boot
/dev/mapper/centos-home 5.0G 119M 4.9G 3% /home
tmpfs 98M 24K 98M 1% /run/user/1000
tmpfs 98M 0 98M 0% /run/user/0
[icexmoon@xyz ~]$ xfs_info /dev/sda2
meta-data=/dev/sda2 isize=512 agcount=4, agsize=65536 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=0 spinodes=0
data = bsize=4096 blocks=262144, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=1
log =internal bsize=4096 blocks=2560, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
[icexmoon@xyz ~]$ xfs_info /
meta-data=/dev/mapper/centos-root isize=512 agcount=4, agsize=655360 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=0 spinodes=0
data = bsize=4096 blocks=2621440, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=1
log =internal bsize=4096 blocks=2560, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
可以使用xfs_info 挂载点|设备文件名
查看xfs类型的文件系统信息。
其中agcount=4
表示有4个群组,agsize=655360 blks
表示每个群组有655360
个区块,从data
部分的bsize=4096
可以看到每个区块大小是4KB,所以根目录的总容量是4*655360*4KB
。
文件系统简单操作
磁盘与目录的容量
df
df
(default filesystem)命令可以列出磁盘整体的使用情况:
[icexmoon@xyz ~]$ df -h
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 470M 0 470M 0% /dev
tmpfs 487M 0 487M 0% /dev/shm
tmpfs 487M 8.6M 478M 2% /run
tmpfs 487M 0 487M 0% /sys/fs/cgroup
/dev/mapper/centos-root 10G 3.9G 6.2G 39% /
/dev/sda2 1014M 172M 843M 17% /boot
/dev/mapper/centos-home 5.0G 119M 4.9G 3% /home
tmpfs 98M 24K 98M 1% /run/user/1000
也可以在后边跟具体的目录,查看该目录所在的文件系统的情况:
[icexmoon@xyz ~]$ df -h /
文件系统 容量 已用 可用 已用% 挂载点
/dev/mapper/centos-root 10G 3.9G 6.2G 39% /
du
我们之前说过,使用ls
查看到的目录大小是目录关联的block大小,并不能代表目录包含的内容的实际大小,而我们如果要看某个目录的实际占用大小就需要使用du
(disk usage)这个命令。
比如我常用的命令是这个:
[icexmoon@xyz ~]$ du -hd 1
60M ./.mozilla
27M ./.cache
4.0K ./.dbus
112K ./.config
324K ./.local
0 ./桌面
0 ./下载
0 ./模板
0 ./公共
0 ./文档
0 ./音乐
0 ./图片
0 ./视频
86M .
参数-d 1
的意思是仅显式下边一层子目录的大小,通过这个命令就可以查看当前目录下哪个子目录最占用空间。
硬链接与符号链接
可以使用ln
(link)命令创建链接。
硬链接(Hard Link)
为某个文件创建一个硬链接指在某个目录下创建一个文件名,并指向该文件的inode编号。
实际演示:
[icexmoon@xyz ~]$ cd /tmp/test/
[icexmoon@xyz test]$ ls -al
总用量 4
drwxrwxr-x. 2 icexmoon icexmoon 21 8月 6 17:56 .
drwxrwxrwt. 22 root root 4096 8月 8 16:55 ..
-rwxr-xr-x. 1 icexmoon icexmoon 0 8月 6 17:56 test.py
[icexmoon@xyz test]$ ln test.py test_link.py
[icexmoon@xyz test]$ ls -ial
总用量 4
26833303 drwxrwxr-x. 2 icexmoon icexmoon 41 8月 8 16:55 .
16797768 drwxrwxrwt. 22 root root 4096 8月 8 16:55 ..
26833311 -rwxr-xr-x. 2 icexmoon icexmoon 0 8月 6 17:56 test_link.py
26833311 -rwxr-xr-x. 2 icexmoon icexmoon 0 8月 6 17:56 test.py
这里使用ln
命令为test.py
这个文件创建了一个硬链接test_link.py
。
可以看到表示文件链接数目的数字从一开始的1
变成了2
,且两个文件的inode
编号也完全相同,这表示两个文件其实是同一个。
我们来实际测试一下:
# 写入一行 hellow world代码
[icexmoon@xyz test]$ nano test_link.py
[icexmoon@xyz test]$ cat test.py
print("hellow world!")
[icexmoon@xyz test