ext4 几个问题
我们可以使用dumpe2fs命令将ext类的文件系统信息全部显示出来,其实bmap是每个块组固定一个block的不用显示,imap 比bmap更小所以也只占用1个block不用显示。下面是一个文件系统的部分信息,也是上面的计算示例信息,在这些信息的后面 还有每个块组的信息,其实这里面的很多信息都可以通过几个比较基本的元数据推导出来。 root@jack-None:/dev# dumpe2fs /dev/sda2 Inode count: 1310720 文件系统inode号数量 Block count: 5242112 文件系统block块总数 Reserved block count: 262105 保留的block块数量 Free blocks: 2735814 空闲的block块数量 Free inodes: 1144046 空闲的inode号数量 First block: 0 第一个block块号 Block size: 4096 block块大小4KB Fragment size: 4096 碎片大小 Reserved GDT blocks: 1024 保留GDP的块总数 Blocks per group: 32768 每个块组的block块数量 Inodes per group: 8192 每个块组的inode号数量 Inode blocks per group: 512 每个块组inode占用的block块数量 Flex block group size: 16 inode表大小 First inode: 11 文件系统的第一个inode号 Inode size: 256 每个inode的大小为256字节 后面省略... 从上面这张表中能计算出文件系统的大小,该文件系统共5242112个blocks,每个block大小为4096B(字节),所以文件系 统大小为5242112*4096÷1024(KB)÷1024(MB)÷1024(GB)=19.997GB。 注意:这19.997GB是实实在在可存储的文件大小,所以我们也就知道为什么有的磁盘买的是8G,可实际也就7GB多些。 也能计算出分了多少个块组,因为每个块组的block数量为32768个,所以块组的数量为159.9765625÷32768=159.976即 160个块组。由于块组从0开始编号,所以最后一个块组编号为Group 159。如下所示是最后一个块组的信息: 组 159:(块 5210112-5242111) 校验和 0xd815 [INODE_UNINIT, ITABLE_ZEROED] 块位图位于 4718607 (bg #144 + 15),校验和 0xa8269ef3 inode 位图位于 4718623 (bg #144 + 31),校验和 0x00000000 inode 表位于 4726304-4726815 (bg #144 + 7712) 32000 个可用块,8192 个可用 inode,0 个目录 ,8192 个未使用的 inode 可用块数: 5210112-5242111 可用 inode 数: 1302529-1310720
目录数据块
对照上图我们可以看出,除了Data Blocks其它的部分都解释过了。我们最好的理解就是Data Blocks是直接存储数据的Block;通过inode里存储的该文件block的指针(逻辑块地址)找到对应的Data blocks里的block信息;Data Blocks确实存储了文件的数据信息,但事实上并不是只存储文件的数据信息,记得我在第一节里的inode讲述中,明确指出inode元数据内并未存储文件名称,所以文件名称存储在哪呢?
对于Linux系统来说,不同的文件类型,数据在block中存储的内容是不一样的;对于常规文件来说数据正常存储在数据块中。但对于文件目录信息来说,该目录下的所有文件和一级子目录的目录名也存储在数据块中,但这个数据块常被人称为目录数据块(注意:Linux中文件目录也是文件的一种哟,只不过它是目录文件类型)。
我在第一节里的inode讲述中还明确强调,文件名称和inode号并不是存储在其自身的inode中,而是存储在其所在目录数据块内(存储目录的data block)。具体的细节就看如下的目录数据块。
(一):目录数据块的存储结构
由图可知,在目录数据块中存储了其目录下的文件名、目录名、目录本身的相对名称".“和上级目录的相对名称”…“,还存储了这些文件名对应的inode号、目录项长度rec_len、文件名长度name_len和文件类型file_type。注意的是除了文件本身的inode记录了文件类型,其所在的目录的数据块也记录了文件类型。由于rec_len只能是4的倍数,所以需要使用”\0"来填充name_len不够凑满4的倍数部分。至于rec_len具体是什么,只需知道它是一种偏移即可。
还需要注意的是,inode table中的inode自身并没有存储每个inode的inode号,而inode号是存储在目录数据块中的,通过inode号可以计算并索引到inode table表中的inode号对应的inode记录,可以认为这个inode号是一个inode指针 (并非真的是指针,但有助于理解通过inode号索引找到对应inode的这个过程,后文将在需要的时候使用inode指针这个词来表示inode号。至此已经知道了两种指针:一种是inode table中每个inode记录指向其对应data block的block指针,另一个是此处的“inode指针”,用来计算inode table表中的inode记录)。
除了inode号,目录的data block中还使用数字格式记录了文件类型,数字格式和文件类型的对应关系如下图。 编码 文件类型 0 Unknown(未知类型) 1 Regular file(常规普通文件类型) 2 Directory(目录类型) 3 Character device(字符设备类型) 4 Block device(块设备类型) 5 Named pipe(管道文件类型) 6 Socket(套接字文件类型) 7 Symbolic link(链接文件类型) 注意到目录的data block中前两行存储的是目录本身的相对名称"."和上级目录的相对名称"..", 它们实际上是目录本身的硬链接和上级目录的硬链接。硬链接的本质后面说明。
(二):为何目录数据块的inode号能找到inode
要知道inode结构里并没有保存inode号(也没有保存文件名),而目录数据块中却保存了该目录中每个文件的inode号。既然inode中没有inode号,那么如何根据目录数据块中的inode号找到inode table中对应的inode呢?
其实只要有了inode号,就可以计算出inode表中对应该inode号,再根据inode号就可以找到inode结构以及引用的数据存储块。在创建文件系统的时候,每个块组中的起始inode号以及inode table的起始地址都已经确定了,所以只要知道inode号,就能知道这个inode号和该块组起始inode号的偏移数量,再根据每个inode结构的大小(256字节或其它大小),就能计算出来对应的inode结构(就像计算数组偏移一样)。
所以目录数据块中的inode number(inode号)和inode table中的inode是通过计算的方式映射起来的。而目录数据块中的inode number(inode号)也是找到inode table中对应inode记录的唯一方式。
有个特殊情况就是目录数据块中的记录已经删除,但是该记录对应的inode结构仍然存在于inode table中。这种inode称为孤儿inode(orphan inode),存在于inode table中,但却无法再索引到它。因为目录中已经没有该inode对应的文件记录了,所以其它进程将无法找到该inode,也就无法根据该inode找到该文件所指向占用的数据存储块(data block),这正是创建后删除所实现的真正临时文件,该临时文件只有当前进程和子进程才能访问。
(三):符号链接的存储方式
Linux中符号链接又被称为软连接,它就像我们在Windows系统创建的快捷方式一样,最终目的就是指向原文件和目录;软链接之所以也被称为特殊的文件原因是:它一般情况下不占用data block,而是通过它对应的原文件inode记录就能将其信息描述完成;软链接的大小是其指向目标路径占用的字符个数,例如某个符号链接的指向方式为"rmt --> …/sbin/rmt",则其链接的文件大小为11字节;只有当软链接指向的目标的路径名称较长(大于60个字节)时文件系统才会划分一个data block给它;还有就是软链接的权限始终为777,它只是一个指向原文件的"工具",最终决定是否能读写执行的权限由原文件决定。
需要注意的是,软链接的目录或文件是存储在目录数据块中的,通过对应目录数据块中存储的inode号找到对应inode table表里的inode,而关键就是这个inode里的数据指针不是指向一个data block,而是存储的是被链接的目标文件名。也就是说,链接文件的一切都依赖于其目标文件名。
符号链接请参考(内含软链接和硬链接说明):详解Linux硬链接与软链接(ln命令)
(四):设备文件
设备文件是与系统中特定设备相对应的文件。在Linux内核中,每种设备类型都对应有设备驱动程序接口,用于处理该设备的所有IO请求。设备驱动程序是内核代码,可以执行一系列操作,通常与相关硬件的输入/输出动作相对应。操作系统提供的设备驱动程序API是固定的,包括开启、关闭、读取、写入、内存映射和ioctl等操作,类似于系统调用中的open()、close()、read()、write()、mmap()和ioctl()。需要注意的是,每个设备驱动程序所提供的接口是一致的,因此每个设备都必须实现这些接口,才能被操作系统调用其指定的功能接口。
还要注意的是设备文件不占用任何数据块(还有FIFO、套接字文件也不占用数据块),所以它们是特殊文件。设备文件的主设备号和次设备号保存在inode中,还要注意到这些信息中没有大小的信息,因为设备文件不占用数据块所以没有大小的概念。
===================================
五:文件系统中文件操作原理
这章将解释在Linux上执行创建、删除、复制、重命名、移动等操作时是如何进行的,并且是如何找到具体的文件并访问此文件。我们只要了解本文上面的一些概念后就会很容易知道文件操作的原理了。
注:下面只是简单阐述单文件系统操作 \color{#f00}{ 注:下面只是简单阐述单文件系统操作} 注:下面只是简单阐述单文件系统操作
(一):读取文件
假设这里执行 “cat /var/log/app.log” 命令会涉及到了cat命令的寻找、权限判断以及app.log文件
的寻找等复杂的过程。这里只解释和本节内容相关的如何寻找到被cat的/var/log/app.log文件。
首先找到根文件系统(“/”)的那个GDT(块组描述符)表所在的逻辑块(Blocks),读取GDT(已在内存中)找到Inode Table(索引表)的Block号。因为GDT总是和Super Block在同一个块组,而Super Block总是在分区的第1024-2047个字节,所以很容易就知道第一个GDT所在的块组以及GDT在这个块组中占用了哪些Block。其实GDT已经在内存中了,因为在系统开机的时候就会把根目录挂载到根文件系统,挂载的时候就已经将所有的GDT放进内存中。
在Inode Table中查找到目录数据块并定位到根"/“的inode,找出”/“指向的data block(因为是目录,所以指向的是Directory Data Block,目录数据块)。前文说过,ext文件系统预留了一些inode号,其中”/“的inode号为2,所以可以根据inode号直接定位根目录文件的data block。
在”/“的目录数据块中记录了var目录名和var的inode号,找到该inode记录,inode记录中存储了指向var的目录数据块指针,所以也就找到了var目录文件的目录数据块。
需要注意的是,通过var目录的inode号,可以寻找到var目录的inode记录,但是在寻找的过程中,还需要知道var的inode记录所在的块组以及所在的inode table,所以需要读取GDT,同样GDT已经缓存到了内存中。
在var的目录数据块中记录了log目录名和其inode号,通过该inode号定位到该inode所在的块组及所在的inode table,并根据该inode记录找到log的目录数据块。在log目录文件的目录数据块中记录了app.log文件名和对应的inode号,通过该inode号定位到该inode所在的块组及所在的inode table,并根据该inode记录找到app.log的data block。最后读取app.log对应的data block。
将上述步骤中GDT部分的步骤简化后比较容易理解。如下:找到GDT–>找到”/“的inode–>找到”/"的数据块读取var的inode–>找到var的数据块读取log的inode–>找到log的数据块读取app.log的inode–>找到app.log的数据块并读取它们。
当然,在每次定位到inode记录后,都会先将inode记录加载到内存中,然后查看权限,如果权限允许,将根据block指针找到对应的data block。
========================
前文已述,Ext4文件系统将磁盘空间划分为若干组,以这一组为单位管理磁盘空间,这个组叫做块组(Block Group)。那么为什么要划分为块组呢?其主要原因是方便对磁盘的管理,由于磁盘被划分为若干组,因此上层访问数据时碰撞的概率就会大大减小,从而提升文件系统的整体性能。简单来说,块组就是一块磁盘区域,而同时其内部有元数据来管理这部分区域的磁盘。
概括来说,我们知道超级块是管理整个文件系统(或者理解为承载整个文件系统的磁盘)的,而块组则是管理本区域内文件系统(或者理解为磁盘),管理的粒度逐渐减小。后面我们还会了解到更细粒度的管理单元。
既然块组管理磁盘的,那就要涉及如何管理的问题,包含两部分的内容,一方面是是用户层要读文件系统的内容时能够找到期望的数据(比如打开文件或者读文件内容),另一方面是写数据的时候能够找到空闲的逻辑磁盘块(比如创建文件或者向文件追加内容)。
那具体管理磁盘的逻辑块呢?这个其实就是块组中的元数据来处理的。这里先插一句,本着先简单后复杂的原则,本文首先介绍不具备Flexible Block Groups特性的文件系统。这种情况下,一个块组只管理本块组内的磁盘,而复杂的情况我们稍后介绍。
如图所示是一个磁盘块组的布局图,块组0和块组2是两个比较典型的块组。其中块组0包含的信息比较丰富,也即包含超级块、块组描述符和数据块位图等等内容,而块组2包含的信息则相对简单,只包含数据块位图、inode位图、inode表和数据区域。
图1 Ext4磁盘数据布局图
对应的,我们通过dumpe2fs命令从磁盘获取关于块组的摘要信息,本文摘取磁盘中前3个块组的描述信息,其中包含块组0和块组2,可以看到摘要信息与上图一致。另外块组1起始跟块组0基本一致,只是我们从下面描述中得知其中包含超级块的备份。我们知道超级块是整个文件系统的入口,因此如果超级块被损坏,将导致整个文件系统不可用。因此Ext4在设计的时候包含一个(实际包含很多)超级块备份,保证即使在磁盘最开始超级块被破坏的情况下,仍然可以通过备份的超级块进行恢复,从而最大限度的保证文件系统的安全。
https://blog.csdn.net/shuningzhang/article/details/89394350
参考:
https://blog.csdn.net/xiaofeng_yang/article/details/138659090
https://blog.csdn.net/shuningzhang/article/details/89394350