前言:
本文是笔者自己在学习文件系统中的一些体会,写出来和大家分享一下。本文首先是介绍了下文件系统的一些理论概念,然后分析了ext2文件系统的原理和部分源码。
文件系统是什么:
人们在认识一件陌生事物时一开始总是从事物的定义、作用和结构入手的。那么首先文件系统的定义是什么呢?从网上抓下来的:“文件系统是操作系统用于明确磁盘或分区上的文件的方法和数据结构;即在磁盘上组织文件的方法。也指用于存储文件的磁盘或分区,或文件系统种类。”简单地说文件系统就是在磁盘上组织文件的方法。
可是为什么需要用文件系统呢?也许朋友们会这么认为:磁盘是自己的,想怎么放文件就怎么放,还要弄个文件系统来管多麻烦啊!那让我们来打个比方好了,比如你花了100万买了一套100平米的房子,如果没有一个预先规划,今天在厨房放个洗衣机,明天在阳台放个双人床,后天你就会发现新买的冲水马桶只能放在卧室了。。。当然这种局面是我们不希望看到了,为了避免这种情况的出现,我们在放东西之前就找一个能帮我们放东西的“管家”,一来这个“管家”知道东西怎么放可以在100万的房子里放尽量多的“东西”,二来“管家”可以帮我们快速地找“东西”,不会发生在卧室找到冲水马桶的尴尬。甚至这个有些“管家”还能在我们不小心“丢掉东西”以后还能帮我们找回来。而我们在放东西或取东西的时候只需要委托“管家”,让她帮我们实现放和找。将这个例子推演到文件系统中就可以得知文件系统(“管家”)的主要作用:一、优化磁盘空间利用率;二、提高磁盘查找数据的效率;三、能够提供N多“增值”服务,如:磁盘恢复、压缩、访问许可、磁盘配额等等,当然要视不同的文件系统而言;四、由于我们是委托“管家”帮我们操作的,只要接口统一,“管家”是可以更换的,这样一来系统的耦合性就降低了。当然,我们要请到这个超有能力的“管家”是要付出代价的,首先,“管家”是需要住在你的“家里的”,因此需要空间上的代价;然后,你要利用“管家”帮你“放东西”,当然需要去了解“管家”,因此需要时间上的代价;最后,还需要承担“管家”做错事的风险,因为如果文件系统一旦出错,有可能损失的将是整个磁盘的宝贵数据。
目前在各种操作系统中存在着各种各样的文件系统,在Windows平台主流的有:FAT,FAT16,FAT32,NTFS等,在Unix平台主流的有:EXT2、EXT3、EXT4等以及在应用光盘中的文件系统有ISO-9660、UDF光盘文件系统标准等。其实这里列举的还是文件系统大家族的冰山一角,在“海水”下藏着的还有N多各种各样的文件系统。为什么文件系统要有这么多呢?就搞一个通用的国际标准的难道不好吗?这个愿望是美好的,不过这种对于标准的定义正是各大利益方角力的焦点,正所谓“一流公司做标准,二流公司做技术,三流公司做生产”也就是这个道理。
那么到底这些“管家”是如何在工作的呢?这么多的“管家”在管理磁盘时都有自己的一套策略和方法,当然随之也带来不同的优点和缺点,下面就以EXT2为例,分析下EXT2文件系统的原理。
EXT2是 GNU/Linux 系统中标准的文件系统,其特点为存取文件的性能极好,对于中小型的文件更显示出优势,从EXT2、EXT3、EXT4的名称定义中不能看出,EXT3和EXT4都是在EXT2的基础上扩展出来的,EXT2才是本源,下面那就让我们一起来探究下最本源的东西吧。
? EXT2的结构
EXT2会将磁盘空间分为一个个BlockGroup(块组),这些块组的大小都是一致的,每个磁盘空间中至少有一个块组。大致就相当于EXT2这个“管家”会把我们的家先分成一个个大小一致的“房间”。如图:
Boot Sector (启动块) |
BlockGroup 0 |
BlockGroup 1 |
。。。 |
BlockGroup N |
图1
当然你也许会问,磁盘不是已经有扇区了吗?为什么还要把磁盘再分成一个个块呢?如图:
图2(摘自百度百科http://baike.baidu.com/view/1298929.htm)
设想一下,如果没有将磁盘分成一个个块,而是直接将数据放在扇区中,这时我们来考虑这个场景用户要写和读一个大小为N(假设磁盘的扇区大小也为N)的文件,首先在写的场景时,由于找不到这么大的连续磁盘空间,就要先进行移动扇区,空出N个扇区用来存放这个文件;在读取这个文件的时候,磁盘的寻道时间将会很慢,因为每次读一个扇区的数据需要重复N次。那么如果EXT2将磁盘分为一个个快来进行管理,这时情况将会怎么样呢?在写的场景时,不需要进行移动操作,而是通过指针的方式,将文件物理上分散地存储到空闲的块中。在读的场景时,假设快的大小为磁盘扇区的4倍,就只需花费1/4之一的时间来读取这个大小为N的文件。
这里要出来一些概念上的东西,一、是两个术语来定义这里的磁盘扇区和块:磁盘扇区=物理块,块=逻辑块,下文中提到块,只要不是特殊说明的都是指逻辑块;二、是同一文件系统中的逻辑块大小是一致的(请注意前提是同一文件系统中);三、不管数据多大,必须占据1个以上的逻辑块,打个比方,如果一个文件就只有1k字节,那么这个文件也必须占据一个逻辑块(就算这个逻辑块的大小为4K字节,剩下的3K字节将浪费了)。四、物理块的大小是由硬件厂商决定的,目前主流的磁盘扇区大小为512字节,注意:自 2009 年 12 月起,硬盘制造商开始引入使用 4096 字节扇区的磁盘,而不是常见的 512 字节扇区磁盘。关于大扇区的实际建议请查看:http://www.ibm.com/developerworks/cn/linux/l-4kb-sector-disks/?ca=drs-tp4608,而逻辑块的大小是可以由文件系统决定的,但必须是物理块的整数倍,那么在EXT2中逻辑块的大小的定义:
#define EXT2_MIN_BLOCK_SIZE 1024 #define EXT2_MAX_BLOCK_SIZE 4096 |
那么逻辑块能否比物理块还小呢?理论上当然可以了(事实上在09年很多硬盘使用高级格式化技术后,逻辑块很有可能是比事实上的物理块要小),但是出于性能和可用上的考量,几乎没有这么做的。
再来回过头看逻辑块的结构图(图1),这里怎么多出了一个Boot Sector呢?这个不是EXT2弄的,而是硬件厂商的规则,必须在一个磁盘空间的最前面留出1K字节的空间作为启动扇区,其实这个启动扇区就相当于我们现实中的门牌号的作用。(关于启动扇区的详细解释有兴趣的话可以看看Wiki:http://en.wikipedia.org/wiki/Boot_sector )
当然只是简单地将磁盘空间分为一个个块组还是不足以很好地进行管理的,于是EXT2会进一步地将每个块组分成以下格式:
? SuperBlock(超级块):这里记录整个文件系统的信息,包括:Block和Inode的大小、Block和Inode的数量、Block和Inode的已使用和未使用数量、文件系统版本号、最近一次挂载时间等等,完整的信息可以查看内核源代码:/usr/src/kernels/… /include/linux/ext2_fs.h。EXT2为了安全考量,会将超级块备份到每个组块的开头(也就是说有多少个块组就有多少个超级块备份)。具体结构体如下:
struct ext2_super_block { __le32 s_inodes_count; /* 索引节点的总数 */ __le32 s_blocks_count; /* 块总数(所有的块) */ __le32 s_r_blocks_count; /* 保留的块数 */ __le32 s_free_blocks_count; /* 空闲块数 */ __le32 s_free_inodes_count; /* 空闲索引节点数 */ __le32 s_first_data_block; /* 第一个使用的块号(总为1 ?) */ __le32 s_log_block_size; /* 块的大小 */ __le32 s_log_frag_size; /* 片的大小 */ __le32 s_blocks_per_group; /* 每组中的块数 */ __le32 s_frags_per_group; /* 每组中的片数 */ __le32 s_inodes_per_group; /* 每组中的索引节点数 */ __le32 s_mtime; /* 最后一次安装操作的时间 */ __le32 s_wtime; /* 最后一次写操作的时间 */ __le16 s_mnt_count; /* 被执行安装操作的次数 */ __le16 s_max_mnt_count; /* 检查之前安装操作的次数 */ __le16 s_magic; /* 魔术签名 */ __le16 s_state; /* 文件系统状态标志 */ __le16 s_errors; /* 当检测到错误时的行为 */ __le16 s_minor_rev_level; /* 次版本号 */ __le32 s_lastcheck; /* 最后一次检查的时间 */ __le32 s_checkinterval; /* 两次检查之间的时间间隔 */ __le32 s_creator_os; /* 创建文件系统的操作系统 */ __le32 s_rev_level; /* 主版本号 */ __le16 s_def_resuid; /* 保留块的缺省UID */ __le16 s_def_resgid; /* 保留块的缺省用户组ID */ /* * These fields are for EXT2_DYNAMIC_REV superblocks only. * * Note: the difference between the compatible feature set and * the incompatible feature set is that if there is a bit set * in the incompatible feature set that the kernel doesn‘t * know about, it should refuse to mount the filesystem. * * e2fsck‘s requirements are more strict; if it doesn‘t know * about a feature in either the compatible or incompatible * feature set, it must abort and not try to meddle with * things it doesn‘t understand... */ __le32 s_first_ino; /* 第一个非保留的索引节点号 */ __le16 s_inode_size; /* 磁盘上索引节点结构的大小 */ __le16 s_block_group_nr; /* 这个超级块的块组号 */ __le32 s_feature_compat; /* 具有兼容特点的位图 */ __le32 s_feature_incompat; /* 具有非兼容特点的位图 */ __le32 s_feature_ro_compat; /* 只读兼容特点的位图 */ __u8 s_uuid[16]; /* 128位文件系统标识符 */ char s_volume_name[16]; /* 卷名 */ char s_last_mounted[64]; /* 最后一个安装点的路径名 */ __le32 s_algorithm_usage_bitmap; /* 用于压缩 */ /* * Performance hints. Directory preallocation should only * happen if the EXT2_COMPAT_PREALLOC flag is on. */ __u8 s_prealloc_blocks; /* 预分配的块数 */ __u8 s_prealloc_dir_blocks; /* 为目录预分配的块数 */ __u16 s_padding1; /* 按字对齐 */ …… __u32 s_reserved[190]; /* 用null填充1024字节 */ }; |
此结构的翻译摘自http://blog.csdn.net/yunsongice/archive/2010/06/22/5685562.aspx
通读一下以上的结构体,有助于对超级块的理解。
? GDT:块组描述表是由一个个块组描述符组成的,有多少个块组就有多少个,块组描述符记录了以下信息:
struct ext2_group_desc { __le32 bg_block_bitmap; /* Blocks bitmap block 本块组的块占用位图从第几块开始*/ __le32 bg_inode_bitmap; /* Inodes bitmap block 本块组的索引节点占用位图从第几块开始*/ __le32 bg_inode_table; /* Inodes table block 本块组的索引节点表从第几块开始*/ __le16 bg_free_blocks_count; /* Free blocks count本块组的空余块*/ __le16 bg_free_inodes_count; /* Free inodes count本块组的空余索引节点*/ __le16 bg_used_dirs_count; /* Directories count 本块组的目录数量/ __le16 bg_pad; __le32 bg_reserved[3]; }; |
当然块组描述表也是非常重要的,如果一旦损坏失去的将去整个块组的数据,因此EXT2也会将块组信息拷贝到每个块组的开头(具体位置在超级块之后)。
? Block Bitmap:块使用情况,关于块的位图,表示的就是块是否被占用,简单的真假关系:1为占用,0为空闲。
? Inode Bitmap:索引节点使用情况,关于索引节点的位图,表示的就是索引节点是否被占用,简单的真假关系:1为占用,0为空闲。(关于索引节点的信息将在下文详述)
? Inode Table:索引节点表
? DataBlocks:真正存放数据的空间。
http://blog.csdn.net/wh8_2011/article/details/52209454