做人呢,最紧要就系开心啦

Linux内核之IO2:EXT文件系统详解(案例解析)

501次阅读
没有评论

一切都是文件,Linux 通过 VFS 中间层,支持多种文件系统,对 APP 统一接口;

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

文件系统的本质是将用户数据和元数据(管理数据的数据),组织成有序的目录结构。

1 EXT2 文件系统总体存储布局

一个磁盘可以划为多个分区,每个分区必须先用格式化工具 (某种 mkfs) 格式化成某种格式的文件系统,然后才能存储文件,格式化的过程会在磁盘上写一些管理存储布局信息。一个典型的 ext 格式化文件系统存储布局如下:

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

文件系统最小存储单位是 Block,Block 大小格式化时确定,一般 4K;

启动块(BootBlock):

大小 1K,是由 PC 标准规定的,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用启动块。启动块之后才是 EXT 文件系统的开始;

超级块(Superblock):

描述整个分区的文件系统信息,比如块大小,文件系统版本号,上次 mount 时间等;

超级块在每个块组的开头都有一份拷贝;

块组描述符表(GDT, Group Descriptor Table)

由很多 块组描述符(Group Descriptor) 组成,整个分区分成多少个块组就对应多少个 GD;

每个 GD 存储一个块组的描述信息,比如这个块组从哪里开始是 inode 表,哪里开始是数据块,空闲的 inode 和数据块还有多少个等。

和超级块类似,GDT 在每个块组开头也有一份拷贝;

块位图(Block Bitmap)

用来描述整个块组中那些块空闲,本身占用一个块,每个 bit 代表本块组的一个块,bit 为 1 表示对应块被占用,0 表示空闲;

tips:

df命令统计整个磁盘空间非常快,因为只需要查看每个块组的块位图即可;

du命令查看一个较大目录会很慢,因为需要搜索整个目录的所有文件;

inode 位图(inode Bitmap)

和块位图类似,本身占用一个块,每个 bit 表示一个 inode 是否空闲可用;

inode 表(inode Table)

每个文件对应一个 inode,用来描述文件类型,权限,大小,创建 / 修改 / 访问时间等信息;

一个块组中的所有 inode 组成了 inode 表;

Inode 表占用多少个块,格式化时就要确定,mke2fs工具默认策略是每 8K 分配一个 inode。

就是说当文件全部是 8K 时,inode 表会充分利用,当文件过大,inode 表会浪费,文件过小,inode 不够用;

硬链接指向同一个 inode;

数据块(Data Block)

a. 常规文件:

文件的数据存储在数据块中;

b. 目录

该目录下所有文件名和目录名存储在数据块中;

文件名保存在目录的数据块中,ls –l 看到的其他信息保存在该文件的 inode 中;

目录也是文件,是一种特殊类型的文件;

c. 符号链接

如果目标路径名较短,直接保存在 inode 中以便查找,如果过长,分配一个数据块保存。

d. 设备文件、FIFO 和 socket 等特殊文件

没有数据块,设备文件的主,次设备号保存在 inode 中。

2 实例解析文件系统结构:

用一个文件来模拟一个磁盘;

1. 创建一个 1M 文件,内容全是 0;

dd if=/dev/zero of=fs count=256 bs=4k

2. 对文件 fs 格式化

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

格式化后的 fs 文件大小依然是 1M,但内容已经不是全零。

3. 用 dumpe2fs 工具查看这个分区的超级块和块组描述表信息

(base) leon\@pc:\~/nfs/linux\$ dumpe2fs fs
dumpe2fs 1.42.13 (17-May-2015)
Filesystem volume name:  <none>
Last mounted on:     <not available>
Filesystem UUID:     a00715b2-528b-4ca6-8c2b-953389a5ab00
Filesystem magic number: 0xEF53
Filesystem revision #:  1 (dynamic)
Filesystem features:   ext_attr resize_inode dir_index filetype sparse_super
large_file
Filesystem flags:     signed_directory_hash
Default mount options:  user_xattr acl
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:    Fri Aug 21 16:48:02 2020
Last mount time:     n/a
Last write time:     Fri Aug 21 16:48:02 2020
Mount count:       0
Maximum mount count:   -1
Last checked:       Fri Aug 21 16:48:02 2020
Check interval:      0 (<none>)
Reserved blocks uid:   0 (user root)
Reserved blocks gid:   0 (group root)
First inode:       11
Inode size:        128
Default directory hash:  half_md4
Directory Hash Seed:   e5c519af-d42e-43b5-bc8d-c67c5a79bcbe
Group 0: (Blocks 1-1023)

主 superblock at 1, Group descriptors at 2-2
保留的 GDT 块位于 3-5
Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
Inode 表位于 8-23 (+7)
986 free blocks, 117 free inodes, 2 directories
可用块数: 38-1023
可用 inode 数: 12-128
(base) leon\@pc:\~/nfs/linux\$

块大小 1024 字节,一共 1024 个块,第 0 块是启动块,第一个块开始才是 EXT2 文件系统,

Group0 占据 1 到 1023 个块,共 1023 个块。

超级块在块 1,GDT2,预留 3 -5,

块位图在块 6,占用一个块,1024x8=8192bit,足够表示 1023 个块,只需一个块就够了;

inode bitmap 在块 7

inode 表在 8 -23,占用 16 个块,默认每 8K 对应一个 inode,共 1M/8K=128 个 inode。每个 inode 占用 128 字节,128x128=16k

4 用普通文件制作的文件系统也可以像磁盘分区一样 mount 到某个目录

$ sudo mount -o loop fs /mnt/

-o loop 选项告诉 mount 这是一个常规文件,不是块设备,mount 会把它的数据当作分区格式来解释;

文件系统格式化后,在根目录自动生成三个字目录:., .., lost+found

Lost+found 目录由 e2fsck 工具使用,如果在检查磁盘时发生错误,就把有错误的块挂在这个目录下。

现在可以在 /mnt 读写文件,umount 卸载后,确保所有改动都保存在 fs 文件中了。

5. 解读 fs 二进制文件

od  –tx1 –Ax fs

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

* 开头行表示省略全零数据。

000000 开始的 1KB 是启动块,由于不是真正的磁盘,这里全零;

000400 到 0007ff 是 1KB 的超级块,对照 dumpe2fs 输出信息对比如下:

超级块:

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

Ext2 各字段按小端存储。

块组描述符

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

整个文件系统 1M,每个块 1KB,一共 1024 个块,除了启动块 0,其他 1 -1023 全部属于 group0.

Block1 是超级块,

块位图 Block6,

inode 位图 Block7,

inode 表从 Block8 开始,由于超级块中指出每个块组有 128inode,每个 inode 大小 128 字节,因此共占用 16 个块(8-23)从 Block24 开始就是数据块。

查看块位图,6x1024=0x1800

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

前 37 位(ff ff ff ff 1f)已经被占用,空闲块是连续的 Block38-1023=986 free blocks

查看 inode 位图,7x1024=0x1c00

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

已用 11 个 inode 中,前 10 个 inode 是被 ext2 文件系统保留的,其中第二个 inode 是根目录,第 11 个 inode 是 lost+found 目录。块组描述符也指出该组有两个目录,就是根目录和 lost+found 目录。

解析根目录的 inode,地址 Block8*1024+inode2(1*128)=0x2080

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

st_mode 用八进制表示,包含了文件类型和权限,最高位 4 表示为文件类型目录,755 表示权限,size 是大小,说明根目录现在只有一个块。Links= 3 表示根目录有三个硬链接,分别是根目录下的.,
.. 和 lost+found 字目录下的..,

这里的 Blockcount 是以 512 字节为一个块统计的,磁盘最小读写单位一个扇区(Sector)512 字节,而不是格式化文件系统时指定的块大小。所以 Blockcount 是磁盘的物理块数量,而不是分区的逻辑块数量。

根据上图 Block[0]=24 块,在文件系统中 24x1024 字节 =0x6000,从 od 命令查找 0x6000 地址

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

目录数据块由许多不定长记录组成,每条记录描述该目录下的一个文件;

记录 1,inode 号为 2,就是根目录本身,记录长 12 字节,文件名长度 1(“.”),类型 2;

记录 2,inode 号为 2,也是根目录本身,记录长 12 字节,文件名长度(“..”),类型 2;

记录 3,inode 号为 11,记录长 1000 字节,文件名长度(”lost+found”),类型 2;

debugfs 命令,不需要 mount 就可以查看这个文件系统的信息

debugfs fs

stat / cd ls 等

将 fs 挂载,在根目录创建一个 hello.txt 文件,写入内容”hello fs!”,重新解析根目录

查看块位图

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

可见前 38bit 被占用,第 38 块地址 38x1027=0x9800

查看 inode 位图,7x1024=0x1c00

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

由图知,用掉了 12 个 inode

查看根目录的数据块内容

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

debug fs 查看 t.txt 属性

stat t.txt

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

t.txt 文件 inode 号 =12

Inode12 的地址 =Block8*1024+inode12(11*128)=0x2580

查看 t.txt 的 inode

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

文件大小 10 字节 =stlen(“hello fs!”),数据块地址 0x26x1024 = 0x9800

查看内容

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

3 数据块寻址

如果一个文件很大,有多个数据块,这些块可能不是连续存放的,那如何寻址所有块呢?

在上面根目录数据块是通过 inode 的索引项 Blocks[0]找到的,实际上这样的索引项一共有 15 个,从 Blocks[0]到 Blocks[14],每个索引项占 4 字节,前 12 个索引项都表示块编号,例如上面 Blocks[0]保存块 24,如果块大小是 1KB,这样就可以表示 12KB 的文件,剩下的三个索引项 Blocks[12]\~
Blocks[14],如果也这么用,就只能表示最大 15KB 文件,这远远不够。

实际上剩下的这 3 个索引项都是间接索引,Blocks[12]所指向的间接寻址块(Indirect
Block),其中存放类似 Blocks[0]这种索引,再由索引项指向数据块。假设块大小是 b,那么一级间接寻址加上之前的 12 个索引项,最大可寻址 b /4+12 个数据块 =1024/4+12=268KB 的文件。

同理 Blocks[13]作为二级寻址,最大可寻址(b/4)*(b/4)+12=64.26MB

Blocks[14]作为三级寻址,最大可寻址(b/4)*(b/4) *(b/4)+12=16.06GB

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

可见,这种寻址方式对于访问不超过 12 数据块的小文件,是非常快的。访问任意数据只需要两次读盘操作,一次读 Inode,一次读数据块。

而访问大文件数据最多需要 5 次读盘操作,inode,
一级寻址块、二级寻址块、三级寻址块、数据块。

实际上磁盘中的 inode 和数据块往往会被内核缓存,读大文件的效率也不会太低。

在 EXT4,支持 Extents,其描述连续数据块的方式,可以节省元数据空间。

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

4 文件和目录操作的系统函数

Linux 提供一些文件和目录操作的常用系统函数,文件操作命令比如 ls,
cp,mv 等都是基于这些系统调用实现的。

stat:

读取文件的 inode, 把 inode 中的各种文件属性填入 struct stat 结构体返回;

假如读一个文件 /opt/file,其查找顺序是

1. 读出 inode 表中第 2 项,也就是根目录的 inode,从中找出根目录数据块的位置
2. 从根目录的数据块中找出文件名为 opt 的记录,从记录中读出它的 inode 号

  1. 读出 opt 目录的 inode,从中找出它的数据块的位置
  2. 从 opt 目录的数据块中找出文件名为 file 的记录,从记录中读出它的 inode 号
    5. 读出 file 文件的 inode

还有另外两个类似 stat 的函数:fstat(2)函数,lstat(2)函数

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

access(2):

检查执行当前进程的用户是否有权限访问某个文件,access 去取出文件 inode 中的 st_mode 字段,比较访问权限,返回 0 表示允许访问,- 1 不允许。

chmod(2)和 fchmod(2):

改变文件的访问权限,也就是修改 inode 中的 st_mode 字段。

chmod(1)命令是基于 chmod(2)实现的。

chown(2)/fchown(2)/lchown(2):

改变文件的所有者和组,也就是修改 inode 中的 User 和 Group 字段。

utime(2):

改边文件访问时间和修改时间,也就是修改 inode 中的 atime 和 mtime 字段。touch(1)命令是基于 utime 实现的。

truncate(2)/ftruncate(2):

截断文件,修改 inode 中的 Blocks 索引项以及块位图中的 bit.

link(2):

创建 硬链接,就是在目录的数据块中添加一条记录,其中的 inode 号字段与源文件相同。

syslink(2):

创建符号链接,需要创建一个新的 inode,其中 st_mode 字段的文件类型是符号链接。指向路径名,不是 inode,替换掉同名文件,符号链接依然可以正常访问。

ln(1)命令是基于 link 和 symlink 函数实现的。

unlink(2):

删除一个链接,如果是符号链接则释放符号链接的 inode 和数据块,清除 inode 位图和块位图中相应位。如果是硬链接,从目录的数据块中清除文件名记录,如果当前文件的硬链接数已经是 1,还要删除它,同时释放 inode 和数据块,清除 inode 位图和块位图相应位,这时文件就真的删除了。

rename(2):

修改文件名,就是修改目录数据块中的文件名记录,如果新旧文件名不在一个目录下,则需要从原目录数据中清楚记录,然后添加到新目录的数据块中。mv(1)命令是基于 rename 实现的。

readlink(2):

从符号链接的 inode 或数据块中读出保存的数据。

rmdir(2):

删除一个目录,目录必须是空的 (只含. 和..) 才能删除,释放它的 inode 和数据块,清除 inode 位图和块位图的相应位,清除父目录数据块中的记录,父目录的硬链接数减 1,rmdir(1)命令是基于 rmdir 函数实现的。

opendir(3)/readdir(3)/closedir(3):

用于遍历目录数据块中的记录。

目录,是一个特殊的文件,其存放 inode 号与文件名的映射关系;

5 VFS:

Linux 支持各种文件系统格式,ext2,ext3,ext4,fat,ntfs,yaffs 等,内核在不同的文件系统格式之上做了一个抽象层,使得文件目录访问等概念成为抽象层概念,对 APP 提供统一访问接口,由底层驱动去实现不同文件系统的差异,这个抽象层叫虚拟文件系统(VFS, Virtual Filesystem)。

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

File,dentry,inode,super_block 这几个结构体组成了 VFS 的核心概念。

6 icache/dcache

访问过的文件或目录,内核都会做 cache;

inode_cachep = kmem_cache_create()
dentry_cache=KMEM_CACHE()

这两个函数申请的 slab 可以回收,内存自动释放;

Linux 配置回收优先级

(1).free pagecache:

echo 1 >> /proc/sys/vm/drop_caches

(2)free reclaimable slab objects (includes dentries and inodes)

echo 2 >> /proc/sys/vm/drop_caches

(3)free slab objects and pagecache:

echo 3 >> /proc/sys/vm/drop_caches

7 fuse

Linux 支持用户空间实现文件系统,fuse 实际上是把内核空间实现的 VFS 支持接口,放到用户层实现。

Linux 内核之 IO2:EXT 文件系统详解(案例解析)

正文完
 
admin
版权声明:本站原创文章,由 admin 2022-01-04发表,共计6929字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)