理解ext文件系统

理解ext文件系统

@(0001学习博客)
注意:本文参考骏马金龙的博客,详情请移步浏览

一、一些常见的文件系统

  • Linux的文件系统: ext2(无日志功能), ext3, ext4, xfs, reiserfs, btrfs
  • 光盘:iso9660
  • 交换文件系统:swap (虚拟内存)
  • 网络文件系统:nfs, cifs
  • 集群文件系统:gfs2, ocfs2
  • 内核级分布式文件系统:ceph
  • windows的文件系统:vfat, ntfs
  • 伪文件系统:proc, sysfs, tmpfs, hugepagefs
  • Unix的文件系统:UFS, FFS, JFS
  • 用户空间的分布式文件系统:mogilefs, moosefs, glusterfs

二、文件系统的组成

1. block:块

Linux文件系统中使用“block”块为读写单元,块的大小一般为1024bytes(1k)或2048bytes(2k)或4096bytes(4k)。比如需要读一个或多个块时,文件系统的IO管理器通知磁盘控制器要读取哪些块的数据,硬盘控制器将这些块按扇区读取出来,再通过硬盘控制器将这些扇区数据重组返回给计算机。但是其缺点就是会照成空间浪费,比如一个只有96字节的文件也要完整的占有一个块,那么剩余的空间就会造成浪费。

2. inode(index node,索引节点)、inode表

inode的作用主要是高效、有序的查找文件,而文件存储于数量不一的block中。
inode中存储了inode号、文件类型、权限、文件所有者、大小、时间戳等元数据信息,还存储了指向属于该文件block的指针,这样读取inode就可以找到属于该文件的block,进而读取这些block并获得该文件的数据。可以将inode同类理解为目录。
使用stat /FILE命令可以查看文件的元数据:

[root@CentOS7 ~]#stat /etc/passwd
  File: ‘/etc/passwd’  
  Size: 2400        Blocks: 8          IO Block: 4096   regular file
Device: 802h/2050d  Inode: 68307519    Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:passwd_file_t:s0
Access: 2018-11-23 08:05:31.449000177 +0800
Modify: 2018-11-20 18:53:20.214867983 +0800
Change: 2018-11-20 18:53:20.215867983 +0800
 Birth: -

这里再提出一个概念:inode表。
假如每个inode128字节,一个4K的block就可以存放32个inode,再将这些存放inode的块组合起来就行成了inode table(inode表)。
举个例子,每一个家庭都要向派出所登记户口信息,通过户口本可以知道家庭住址,而每个镇或街道的派出所将本镇或本街道的所有户口整合在一起,要查找某一户地址时,在派出所就能快速查找到。inode table就是这里的派出所。

3. bmap(bitmap index)、imap(inode map)位图索引

3.1 bmap

在向硬盘存储数据时,文件系统需要知道哪些块是空闲的,哪些块是已经占用了的,bmap的作用就是总览硬盘中哪些block被占用,哪些是空闲的,这样可以高效写入数据。
位图只使用0和1标识对应block是空闲还是被占用,0和1在位图中的位置和block的位置一一对应,第一位标识第一个块,第二个位标识第二个块,依次下去直到标记完所有的block。比如:对于一个block大小为1KB、容量为1G的文件系统而言,block数量有10241024个,所以在bmap位图中使用10241024个位共1024*1024/8=131072字节=128K,即1G的文件只需要128个block做位图就能完成一一对应。通过扫描这100多个block就能知道哪些block是空闲的,速度提高了非常多。

注意:bmap优化针对的是写入优化,对读取优化使用的是inode。

3.2 imap

imap的作用同理bmap,也是为了让系统迅速了解哪些inode在使用,哪些处于空闲状态

4.块组

为解决bmap、inode table和imap太大的问题,比如100G文件就需要128k*100=12.5M的bmap空间,系统扫描这个空间也是蛮费时间,因此我们将占用的block分成block groups(块组)。
注意:在物理层面上的划分是将磁盘按柱面划分为多个分区,即多个文件系统;在逻辑层面上的划分是将文件系统划分成块组。每个文件系统包含多个块组,每个块组包含多个元数据区和数据区:元数据区就是存储bmap、inode table、imap等的数据;数据区就是存储文件数据的区域。注意块组是逻辑层面的概念,所以并不会真的在磁盘上按柱面、按扇区、按磁道等概念进行划分。

下面介绍如何划分块组:

它只需确定一个数据——每个block的大小,再根据bmap至多只能占用一个完整的block的标准就能计算出块组如何划分。如果文件系统非常小,所有的bmap总共都不能占用完一个block,那么也只能空闲bmap的block了。
(注意:每个block的大小在创建文件系统时可以人为指定,不指定也有默认值。)
假如现在block的大小是1KB,一个bmap完整占用一个block能标识1024*8= 8192个block(当然这8192个block是数据区和元数据区共8192个,因为元数据区分配的block也需要通过bmap来标识)。每个block是1K,每个块组是8192K即8M,创建1G的文件系统需要划分1024/8=128个块组,如果是1.1G的文件系统呢?128+12.8=128+13=141个块组。
可以使用dumpe2fs /dev/sda1 查看相关信息:
ext4文件系统的信息:

[root@CentOS6 ~]#dumpe2fs /dev/sda1
dumpe2fs 1.41.12 (17-May-2010)
Filesystem volume name:   <none>
Last mounted on:          /boot    # 挂载点
Filesystem UUID:          db5da648-a6e9-41e2-b9bb-1ec771e61499
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags:         signed_directory_hash 
Default mount options:    user_xattr acl    # 开通acl功能
Filesystem state:         clean 
Errors behavior:          Continue   #错误时继续进行
Filesystem OS type:       Linux
Inode count:              65536   # inode号数量
Block count:              262144  # block数量
Reserved block count:     13107   # 保存的block数量
Free blocks:              239740  # 空闲的block数量
Free inodes:              65497   # 空闲的inode数量
First block:              0       # 第一个block号
Block size:               4096    # block大小4k
Fragment size:            4096
Reserved GDT blocks:      63      # 保留的GDT block数量
Blocks per group:         32768   # 每个块组的block数量
Fragments per group:      32768
Inodes per group:         8192    # 每个块组的inode数量
Inode blocks per group:   512     # 每个块组inode占用的块数量,即inode表大小,512*4k
Flex block group size:    16      # 
Filesystem created:       Tue Oct 23 18:35:07 2018  
Last mount time:          Fri Nov 23 02:43:10 2018
Last write time:          Fri Nov 23 02:43:10 2018
Mount count:              31
Maximum mount count:      -1
Last checked:             Tue Oct 23 18:35:07 2018
Check interval:           0 (<none>)
Lifetime writes:          88 MB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:           256        # inode大小
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8      # 日志文件的inode数
Default directory hash:   half_md4
Directory Hash Seed:      50ef58d8-3fa1-473a-84d0-2e0a5156c604
Journal backup:           inode blocks
Journal features:         (none)
Journal size:             32M   
Journal length:           8192
Journal sequence:         0x0000002b
Journal start:            0

可见:该分区中共有262144个block,每个块大小4k,所以该分区容量为1G,每个块组包含32768个块,一个分了8个块组。

5.块组里的其他block

5.1 Boot Block 引导块

Boot Block也称为boot sector。它位于分区上的第一个块,占用1024字节,并非所有分区都有这个boot sector,只有装了操作系统的主分区和装了操作系统的逻辑分区才有。里面存放的也是boot loader,这段boot loader称为VBR(主分区装操作系统时)或EBR(扩展分区装操作系统时),这里的Boot loader和mbr上的boot loader是存在交错关系的。开机启动的时候,首先加载mbr中的bootloader,然后定位到操作系统所在分区的boot serctor上加载此处的boot loader。

5.2 Superblock(超级块)

超级块(superblock)用于存储文件系统本身的属性信息:如各种时间戳、block总数量和空闲数量、inode总数量和空闲数量、当前文件系统是否正常、什么时候需要自检等等。
超级块占用1024字节,也需要一个block,所以这个块称为superblock,他的块号可能为0,也可能为1。如果block大小为1K,则引导块正好占用一个block,这个block号为0,所以superblock的号为1;如果block大小大于1K,则引导块和超级块同置在一个block中,这个block号为0。总之superblock的起止位置是第二个1024(1024-2047)字节。
df 命令读取的就是每个文件系统的超级块内的信息,所以其速度非常快。相反,用du命令查看一个较大目录的已用空间就非常慢,因为不可避免地要遍历整个目录的所有文件。

[root@CentOS6 ~]#df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda2       50264772 4376228  43328544  10% /
tmpfs            1019176      76   1019100   1% /dev/shm
/dev/sda1         999320   40360    906532   5% /boot
/dev/sda3       30106576   45032  28525544   1% /data
/dev/sr0         3878870 3878870         0 100% /media/CentOS_6.9_Final

superblock对于文件系统而言是至关重要的,超级块丢失或损坏必将导致文件系统的损坏,所以超级块的信息会在块组上有备份。

dumpe2fs -h /dev/sda1获取超级块信息(dumpe2fs /dev/sda1 略同)

[root@CentOS6 ~]#dumpe2fs -h /dev/sda1

dumpe2fs 1.41.12 (17-May-2010)
Filesystem volume name:   <none>
Last mounted on:          /boot
Filesystem UUID:          db5da648-a6e9-41e2-b9bb-1ec771e61499
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags:         signed_directory_hash 
Default mount options:    user_xattr acl
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              65536
Block count:              262144
Reserved block count:     13107
Free blocks:              239740
Free inodes:              65497
First block:              0
Block size:               4096
Fragment size:            4096
Reserved GDT blocks:      63
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    16
Filesystem created:       Tue Oct 23 18:35:07 2018
Last mount time:          Fri Nov 23 02:43:10 2018
Last write time:          Fri Nov 23 02:43:10 2018
Mount count:              31
Maximum mount count:      -1
Last checked:             Tue Oct 23 18:35:07 2018
Check interval:           0 (<none>)
Lifetime writes:          88 MB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:           256
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8
Default directory hash:   half_md4
Directory Hash Seed:      50ef58d8-3fa1-473a-84d0-2e0a5156c604
Journal backup:           inode blocks
Journal features:         (none)
Journal size:             32M
Journal length:           8192
Journal sequence:         0x0000002b
Journal start:            0

5.3 GDT(块组描述符表)

记录每个块组的信息和属性等元数据,大小为32个字节。

虽然每个块组都需要块组描述符来记录块组的信息和属性元数据,但是不是每个块组中都存放了块组描述符。ext文件系统的存储方式是:将它们组成一个GDT,并将该GDT存放于某些块组中,存放GDT的块组和存放superblock和备份superblock的块相同,也就是说它们是同时出现在某一个块组中的。读取时也总是读取Group0中的块组描述符表信息。

假如block大小为4KB的文件系统划分了143个块组,每个块组描述符32字节,那么GDT就需要143*32=4576字节即两个block来存放。这两个GDT block中记录了所有块组的块组信息,且存放GDT的块组中的GDT都是完全相同的。
dumpe2fs /dev/sda1命令后面的显示信息就是GDT
Alt text

5.4 Reserved GDT(保留GDT)

保留GDT用于以后扩容文件系统使用,防止扩容后块组太多,使得块组描述符超出当前存储GDT的blocks。保留GDT和GDT总是同时出现,当然也就和superblock同时出现了。

完整的文件系统结构图

Alt text

6.Data block

数据所占用的block由文件对应inode记录中的block指针找到,不同的文件类型,数据block中存储的内容是不一样的。以下是Linux中不同类型文件的存储方式:

  • 对于常规文件,文件的数据正常存储在数据块中。
  • 对于目录,该目录下的所有文件和一级子目录的目录名存储在数据块中。
    • 文件名不是存储在其自身的inode中,而是存储在其所在目录的data block中。
  • 对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
  • 设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。

6.1 目录文件

对于目录文件,其inode记录中存储的是目录的inode号、目录的属性元数据和目录文件的block指针,这里面没有存储目录自身文件名的信息。
目录的data block中并没有直接存储目录中文件的inode号,它存储的是指向inode table中对应文件inode号的指针。
* block指针:每个inode号指向的block
* inode指针:目录文件其inode指向inode表
比如:对于没有执行权限的目录文件,我们ll /FILE/TO/并不能读取到该目录下的文件信息,只能读到目录本身的信息。
Alt text

所以,目录文件的读权限(r)和写权限(w),都是针对目录文件的数据块本身。由于目录文件内只有文件名、文件类型和inode指针,所以如果只有读权限,只能获取文件名和文件类型信息,无法获取其他信息,尽管目录的data block中也记录着文件的inode指针,但定位指针是需要x权限的,因为其它信息都储存在文件自身对应的inode中,而要读取文件inode信息需要有目录文件的执行权限通过inode指针定位到文件对应的inode记录上。

补充:硬链接与软链接的区别:

  • 硬链接:指向同一个inode的多个文件路径,但需在同一个分区中
    • 目录不支持硬链接;
    • 硬链接不能跨文件系统(跨分区);
    • 创建硬链接会增加inode引用计数
    • 修改符号链接文件的权限,变动的是源文件的权限
    • 无论修改硬链接中的哪个文件,全部的文件都会跟着改动权限
    • ln file1 file2
  • 软链接:指向一个文件路径的另一个文件路径
    • 符号链接与文件是两人个各自独立的文件,各有自己的inode;对原文件创建符号链接不会增加引用计数;
    • 支持对目录创建符号链接,可以跨文件系统;
    • 删除符号链接文件不影响原文件;但删除原文件,符号指定的路径即不存在,此时会变成无效链接;
    • 符号链接文件的大小是其指定的文件的路径字符串的字节数
    • ln -s file1 file2

三、示例解读:cat /var/log/messages

  1. 找到根文件系统的块组描述符表所在的blocks,读取GDT(已在内存中)找到inode table的block号。
  1. 在inode table的block中定位到根"/"的inode,找出"/"指向的data block。
  2. 在"/"的datablock中记录了var目录名和指向var目录文件inode的指针,并找到该inode记录,inode记录中存储了指向var的block指针,所以也就找到了var目录文件的data block。
  3. 在var的data block中记录了log目录名和其inode指针,通过该指针定位到该inode所在的块组及所在的inode table,并根据该inode记录找到log的data block。
  4. 在log目录文件的data block中记录了messages文件名和对应的inode指针,通过该指针定位到该inode所在的块组及所在的inode table,并根据该inode记录找到messages的data block。
  5. 最后读取messages对应的datablock。

简言之:找到GDT-->找到"/"的inode-->找到/的数据块读取var的inode-->找到var的数据块读取log的inode-->找到log的数据块读取messages的inode-->找到messages的数据块并读取它们。

猜你喜欢

转载自www.cnblogs.com/sstjustdoit/p/10135281.html