2011年(13)
分类: LINUX
2011-05-29 23:49:37
我将试图以从无到有的过程来粗浅的解释文件系统是如何运作的,也就是从得到一个分区到在这个文件系统上进行各种操作的过程。
可以通过这种方式得到一个块文件,而把它当作一个分区来使用:
# cd /tmp/
# touch syda
# dd if=/dev/null of=syda count=0 seek=500000
# losetup /dev/loop0 syda
这样子,就可以把环设备loop0看作是一个硬盘的分区了。
得到一个分区,首先要对它进行格式化,之后操作系统才能使用这个分区。传统的磁盘应用中,一个分区只能被格式化成一个文件系统,我们可以说一个文件系统就是一个分区。但由于新技术的利用,如今一个分区可以格式化为多个文件系统(LVM),也可以将将多个分区合成一个文件系统(LVM,RAID)。对于这些内容我无能力介绍。同时,本文只在Linux下ext3日志文件系统类型下来认识文件系统。
对于文件系统来说,磁盘是由一个个“块”(block)组成的。这些块的大小是2的次方,目前支持的块大小是1k、2k和4k三种。所有这些块都被以自然序列编号[0-n ]。Ext3 文件系统将其所管理的分区中的块划分到不同的块组中。每个块组大小相同,当然最后一个块组所管理的块可能会少一些,其大小在文件系统创建时决定,主要取决于文件系统的块大小,对于大小为4k的文件系统块来说,块组大小为 168M。这些块组也被编上号group[0-n ]。其逻辑形式为:
[文件系统]=[块组0|块组1|块组2|……|块组n ]
[块组k] =[超级块|块组描述表|块位图|inode位图|inode表|数据域](k>=0)
每个块组包含一个块位图块,一个inode位图块,一个或多个块用于描述inode表和用于存储文件数据的数据块,除此之外,还有可能包含超级块和所有块组描述符表(取决于块组号和文件系统创建时使用的参数)。下面将对这些元数据作一些简要介绍。
块位图用于描述该块组所管理的块的分配状态。它的作用是标记已经使用了的块。由于块位图仅占一个块,因此这也就决定了块组的大小。
Inode位图用于描述该块组所管理的inode的分配状态。由于其仅占用一个块,因此这也限制了一个块组中所能够使用的最大inode数量。
Inode表用于存储inode信息。它占用一个或多个块(为了有效的利用空间,多个inode存储在一个块中),其大小取决于文件系统创建时的参数,由于inode位图的限制,决定了其最大所占用的空间。
超级块用于存储文件系统全局的配置参数(譬如:块大小,总的块数和inode数)和动态信息(譬如:当前空闲块数和inode数),其处于文件系统开始位置的1k处,所占大小为1k。为了系统的健壮性,最初每个块组都有超级块和组描述表(以下将用GDT)的一个拷贝,但是当文件系统很大时,这样浪费了很多块(尤其是GDT占用的块多),后来采用了一种稀疏的方式来存储这些拷贝,只有块组号是3, 5 ,7的幂的块组(譬如说1,3,5,7,9,25,49…)才备份这个拷贝。通常情况下,只有主拷贝(第0块块组)的超级块信息被文件系统使用,其它拷贝只有在主拷贝被破坏的情况下才使用。
GDT用于存储块组描述符,其占用一个或者多个数据块,具体取决于文件系统的大小。它主要包含块位图,inode位图和inode表位置,当前空闲块数,inode数以及使用的目录数(用于平衡各个块组目录数)。每个块组都对应这样一个描述符,目前该结构占用32个字节,因此对于块大小为4k的文件系统来说,每个块可以存储128个块组描述符。由于GDT对于定位文件系统的元数据非常重要,因此和超级块一样,也对其进行了备份。GDT在每个块组(如果有备份)中内容都是一样的,其所占块数也是相同的。
下面我对刚才得到的分区进行格式化,格式化其实就是对上面所解释的结构进行分配的过程。
# mkfs -t ext3 /dev/loop0
这条命令将/dev/loop0格式化成ext3类型的文件系统。下面通过命令dumpe2fs来查看超级块与块组描述中的内容。我将省略结果中的一些内容,因为太长了。
# dumpe2fs /dev/loop0
dumpe2fs 1.41.12 (17-May-2010)
Filesystem volume name:
Filesystem features: has_journal ext_attr resize_inode dir_index filetype sparse_super
Filesystem state: clean <==文件系统是没问题的(clean)
Filesystem OS type: Linux
Inode count: 62744 <==inode总数
Block count: 250000 <==块总数
Free blocks: 235903 <==空闲块数
Free inodes: 62733 <==空闲inode数
First block: 1 <==它没从块0开始,这里0号块被保留作特殊用途,这里不赘述。
Block size: 1024 <==每个块大小,1k
……
Inode size: 128 <==每个inode大小,128bytes
……
上面这些就是超级块中纪录的内容,下面是块组描述记录的内容,由于每个块组都是一样的,所以只截取Group0出来。
Group 0: (Blocks 1-8192) <==块组0的块起始/结束号码
主 superblock at 1, Group descriptors at 2-2 <==超级块在1号块
保留的GDT块位于 3-258
Block bitmap at 259 (+258), Inode bitmap at 260 (+259)
Inode表位于 261-513 (+260) <==inode表所在的块
7665 free blocks, 2013 free inodes, 2 directories
……
我想现在对一个文件系统在磁盘里的结构已经明朗了。下面来认识inode表与数据域的关系。
数据域(data block)是用来存放文件内容数据的地方,inode表就是存放inode的地方。操作系统通过inode来找到指定的文件存放在哪个数据块中,或存放在哪几个数据块中。一个数据块最多只能存放一个文件,一个大的文件可以占用多个数据块。一个inode只能指向一个文件。inode中记录文件的各种属性数据加若干个指向文件真正内容所在数据块的指针。用ls-li命令所看到内容,除了文件名,都是记录在inode中的。我先将刚才那个分区挂载上再继续说明。
# mount /dev/loop0 /mnt/ <==这样就挂载上了
# cd /mnt/;touch foo1;ls -l
总用量 12
-rw-r--r-- 1 root root 0 4月 27 22:25 foo1
从上面的ls-l的结果中看到,inode中记录了文件的各种权限,属主/属组,创建时间,还有其它各种时间。(ls加上i选项能看到指向这个文件的inode所在的块编号)。
但是系统是怎么通过inode找到数据块的呢?每个inode的大小是128bytes,而inode记录一个块号码要花掉4bytes。假设我一个 400MB大小的文件,每个数据块为4k时, 那么至少也要十万笔块号码癿记录,区区128字节,inode是怎么记录下的?下面给出一张inode的结构图。
上图最左边为inode本身。里边有12个直接指向块地址的指针。而间接指针也是指向块地址,然后利用那个块继续指向别的块。以1k大小的块来计算这样一个inode能指定多少个块。如下:
12个直接: 12*1K=12K
间接: 256*1K=256K
每笔块号码记录花去4bytes,因此1K的块能记录256笔记录,因此一个间接可以记录的档案大小如上;
双间接: 256*256*1K=2562K
第一层块会指定256个第二层,每个第二层可以指定256个号码,因此总额大小如上;再接下来同理:
三间接: 256*256*256*1K=2563K
总额:将直接、间接、双间接、三间接加总,得到 12 + 256 + 256*256 + 256*256*256 (K) =16GB
如此,得出一个文件最大大小为16GB,当块大小为1k的情况。我以为,本文之中,理解了这张图,便理解了全文的一半。
当我们创建一个目录时,ext3文件系统会分配一个inode与至少一个数据块给该目录文件。是的,目录就是一个文件,Linux中,万物皆文件。只不过假如目录中存在文件,这个目录的内容就是其下文件的文件名和与它对应的inode编号。这样想读取其中文件的内容时,实际是找到这个inode,然后找到实际的数据块。如果你知道程序语言中变量的概念,并且了解一点指针的概念,目录中的文件名实际上就是指向这个文件实际内容的inode的变量名。目录名本身是一样的道理。下面通过读取目录中的文件的过程分析这一过程。
# mkdir dir2;touch dir2/foo2
# ls -li / /mnt /mnt/dir2/
2 drwxr-xr-x 4 root root 1024 4月 27 23:41 mnt
10121 drwxr-xr-x 2 root root 1024 4月 27 23:41 dir2
10122 -rw-r--r-- 1 root root 0 4月 27 23:41 foo2
现在要读取foo2的内容,这个读取流程为:
系统设定编号为2的inode指向一个文件系统的根目录所在的块。通过2号inode,读取到mnt所在块的内容,里边记着dir2=10121,这个值就是dir2的inode编号;再通过这个inode读取到dir2所在块的内容,里边记着foo2=10122,从而通过这个指向读取到foo2所在块的内容。
事实上挂载的过程,实际就是2号inode的变量名等于那个目录名,然后在它所在的数据块中存放其下第一层各目录的文件名=inode地址。前面已经说过,一个inode只指向一个文件。下面来看一个例子:
# ls -lid / /. /..
2 drwxr-xr-x 22 root root 4096 4月 15 12:01 /
2 drwxr-xr-x 22 root root 4096 4月 15 12:01 /. <==这个表示当前目录
2 drwxr-xr-x 22 root root 4096 4月 15 12:01 /.. <==根目录的
这三个文件都对应于编号为2的inode,指向的是同一个数据块的内容,这说明这三是同一个文件。下面来区别一下硬链接与符号链接,来加强一下对inode 的认识。希望是更清楚,而不是更混沌,事实上,我隐约感觉到,我把问题说的更不易理解了。先建一个硬链接hlfoo1,再一个符号链接slfoo1,观察 inode编号的变化。
# ln foo1 hlfoo1;ln -s foo1 slfoo1
12 -rw-r--r-- 2 root root 0 4月 27 22:25 foo1
12 -rw-r--r-- 2 root root 0 4月 27 22:25 hlfoo1
13 lrwxrwxrwx 1 root root 4 4月 28 00:22 slfoo1 -> foo1
可见,硬链接只是在某目录文件(这个例子是当前目录)的内容中添加了一笔文件名与相同inode编号的记录。可以理解为两个变量名代表用一个inode 了。正如hlfoo1=foo1。而符号链接文件系统新分配了一个inode,赋予变量名slyoo1,指向一个数据块,存放着链接文件的文件名。观察 slfoo1的文件大小,4bytes,正里存放着“foo1”。
文件系统的内容实在太广,我不准备继续讲了。对于文件系统的学习,我觉得更重要的还是各种相关工具的学习,比如cfdisk、mkfs、fsck、mount/umount、等分区挂载的学习。
这篇文章,不能将这块内容讲得更明白(我担心根本就没讲明白),又不忍删之。实力所不能及,君海涵。
最后,实验完了,卸盘删除
/# umount /dev/loop0
/# losetup -d /dev/loop0
/# rmmod loop