目录
Linux EXT2文件系统的实现机制
摘要: Linux 是现在非常热门的一种新型的操作系统,其最重要的特征之一就是支持多种文件系统,使其更加灵活,从而与许多其它的操作系统共存。Linux支持EXT,EXT2, MINIX HPFS,以及UFS 等多种文件系统。The SecondExtended File System(EXT2)文件系统是Linux系统中的标准文件系统,是通过对Minix的文件系统进行扩展而得到的,其存取文件的性能极好。本文将主要详细介绍主流的文件系统EXT2,从EXT2的布局、文件的实现、目录的实现、磁盘空间的管理等方面进行论述。
关键字: Linux; EXT2; 文件; 目录;
1.引言
1.1 Linux操作系统的简介
Linux 的源头要追溯到最古老的 UNIX 。1969 年 Bell 实验室的 Ken Thompson 开始利用一台闲置的 PDP-7 计算机开发了一种多用户多任务操作系统。很快 Dennis Richie 加入了这个项目在他们共同努力下诞生了最早的 UNIX。 Richie 受一个更早的项MULTICS 的启发将此操作系统命名为Unix 早期 UNIX 是用汇编语言编写的但其第三个版本用一种崭新的编程语言 C 重新设计了 C 是 Richie 设计出来并用于编写操作系统的程序语言通过这次重新编写 Unix 得以移植到更为强大的 DEC PDP-11/45与 11/70 计算机上运行后来发生的一切正如他们所说已经成为历史 Unix 从实验室走出来并成为了操作系统的主流现在几乎每个主要的计算机厂商都有其自有版本的Unix[1]。
Linux 起源于一个学生的简单需求 Linus Torvalds, Linux 的作者与主要维护者在其上大学时所买得起的唯一软件是 Minix. Minix 是一个类似 Unix 被广泛用来辅助教学的简单操作系统 Linus 对 Minix 不是很满意于是决定自己编写软件。他以学生时代熟悉的Unix 作为原型在一台 Intel 386 PC 上开始了他的工作他的进展很快受工作成绩的鼓舞。他将这项成果通过互联网与其他同学共享主要用于学术领域。有人看到了这个软件并开始分发每当出现新问题时,有人会立刻找到解决办法并加入其中,很快Linux成为了一个操作系统。值得注意的是 Linux 并没有包括 Unix 源码,它是按照公开的 POSIX标准重新编写的 Linux 大量使用了由麻省剑桥免费软件基金的 GNU 软件,同时 Linux 自身也是用它们构造而成。
现今,Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。它主要用于基于Intel x86系列CPU的计算机上。这个系统是由全世界各地的成千上万的程序员设计和实现的。其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品[2]。
1.2 Linux文件系统的简介
文件系统是操作系统用于明确磁盘或分区上的文件的方法和数据结构;即在磁盘上组织文件的方法。也指用于存储文件的磁盘或分区,或文件系统的种类。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。从系统角度来看,文件系统是对文件存储器空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。
Linux 的最重要特征之一就是支持多种文件系统 这样它更加灵活并可以和许多其它种操作系统共。Linux 和 Unix 并不使用设备标志符如设备号或驱动器名称来访问独立文件系统而是通过一个将整个文件系统表示成单一实体的层次树结构来访问它。 Linux 每安装(mount)一个文件系统时都会其加入到文件系统层次树中不管是文件系统属于什么类型 都被连接到一个目录上且此文件系统上的文件将取代此目录中已存在的文件这个目录被称为安装点或者安装目。当卸载此文件系统时这个安装目录中原有的文件将再次出现。
当磁盘初始化时使用 fdisk 磁盘中将添加一个描叙物理磁盘逻辑构成的分区结构。每个分区可以拥有一个独立文件系统如 EXT2 文件系统将文件组织成包含目录。软连接等存在于物理块设备中的逻辑层次结构。包含文件系统的设备叫块设备。 Linux 文件系统认为这些块设备是简单的线性块集合。它并不关心或理解底层的物理磁盘结构,这个工作由块设备驱动来完成,由它将对某个特定块的请求映射到正确的设备上去,此块所在硬盘的对应磁道扇区及柱面数都被保存起来,不管哪个设备持有这个块,文件系统都必须使用相同的方式来寻找并操纵此块,Linux 文件系统不管至少对系统用户来说,系统中有哪些不同的控制器控制着哪些不同的物理介质且这些物理介质上有几个不同的文件系统。文件系统甚至还可以不在本地系统而在通过网络连接的远程硬盘上。
2. EXT2文件系统的实现
2.1 EXT2简介
Linux 最早的文件系统是 Minix 。它受限甚大且性能低下,其文件名最长不能超过 14
个字符,虽然比 8.3 文件名要好,且最大文件大小为 64M 字节 64M 字节看上去很大,但实际上一个中等的数据库将超过这个尺寸 ,第一个专门为 Linux 设计的文件系统被称为扩展文件系统Extended File System 或 EXT 。它出现于 1992 年四月,虽然能够解决一些问
题但性能依旧不好 。1993 年扩展文件系统第二版或 EXT2 被设计出来并添加到 Linux 中,
它是本节将详细讨论的文件系统。
第二代扩展文件系统由 Rey Card 设计,其目标是为 Linux 提供一个强大的可扩展文件
系统。它同时也是 Linux 界中设计最成功的文件系统。
2.2 EXT2文件系统的实现
2.2.1 EXT2文件系统的硬盘布局
硬盘分区首先被划分为一个个的 block,一个 ext2 文件系统上的每个 block 都是一样大小的,但是对于不同的ext2 文件系统,block 的大小可以有区别。典型的 block 大小是 1024 bytes 或者 4096 bytes。这个大小在创建 ext2 文件系统的时候被决定,它可以由系统管理员指定,也可以由文件系统的创建程序根据硬盘分区的大小,自动选择一个较合理的值。任何Ext2分区中的第一个块从不受Ext2文件系统的管理,因为这一块是为分区的引导扇区所保留的。这些 blocks 被聚在一起分成几个大的 blockgroup。每个 block group 中有多少个 block 是固定的。
图 1 EXT2文件系统的物理分布
每个 block group 都相对应一个 group descriptor,这些 group descriptor 被聚在一起放在硬盘分区的开头部分,跟在 superblock 的后面。所谓 super block,下面还要讲到。在这个descriptor 当中有几个重要的 block 指针。我们这里所说的 block 指针,就是指硬盘分区上的 block 号数,比如,指针的值为 0,我们就说它是指向硬盘分区上的 block 0;指针的值为 1023,我们就说它是指向硬盘分区上的 block 1023。我们注意到,一个硬盘分区上的 block 计数是从 0 开始的,并且这个计数对于这个硬盘分区来说是全局性质的。
在 block group 的 group descriptor 中,其中有一个 block 指针指向这个 block group 的 block bitmap,block bitmap 中的每个 bit 表示一个 block,如果该 bit 为 0,表示该 block 中有数据,如果 bit 为 1,则表示该 block 是空闲的。注意,这个 block bitmap 本身也正好只有一个 block 那么大小。假设 block 大小为 S bytes,那么 block bitmap 当中只能记载 8*S 个 block 的情况(因为一个 byte 等于 8 个 bits,而一个 bit 对应一个 block)。这也就是说,一个 block group 最多只能有 8*S*S bytes 这么大。
在 block group 的 group descriptor 中另有一个 block 指针指向 inode bitmap,这个 bitmap 同样也是正好有一个 block 那么大,里面的每一个 bit 相对应一个 inode。硬盘上的一个 inode 大体上相对应于文件系统上的一个文件或者目录。关于 inode,我们下面还要进一步讲到。
在 block group 的 descriptor 中另一个重要的 block 指针,是指向所谓的 inode table。这个 inode table 就不止一个 block 那么大了。这个 inode table 就是这个 block group 中所聚集到的全部 inode 放在一起形成的。
一个 inode 当中记载的最关键的信息,是这个 inode 中的用户数据存放在什么地方。我们在前面提到,一个 inode 大体上相对应于文件系统中的一个文件,那么用户文件的内容存放在什么地方,这就是一个 inode 要回答的问题。一个 inode 通过提供一系列的 block 指针,来回答这个问题。这些 block 指针指向的 block,里面就存放了用户文件的内容。
现在对主要数据结构进行分析:
(1) 超级块(Super Block)
每个ext2文件系统都必须包含一个超级块,其中存储了该文件系统的大量基本信息,包括块的大小、每块组中包含的块数等。同时,系统会对超级块进行备份,备份被存放在块组的第一个块中。超级块的起始位置为其所在分区的第1024个字节,占用1KB的空间,其结构如下:
struct ext2_super_block {
__le32 s_inodes_count; // 文件系统中inode的总数
__le32 s_blocks_count; // 文件系统中块的总数
__le32 s_r_blocks_count; // 保留块的总数
__le32 s_free_blocks_count; // 未使用的块的总数(包括保留块)
__le32 s_free_inodes_count; // 未使用的inode的总数
__le32 s_first_data_block; // 块ID,在小于1KB的文件系统中为0,大于1KB的文件系统中为1
__le32 s_log_block_size; // 用以计算块的大小(1024算术左移该值即为块大小)
__le32 s_log_frag_size; // 用以计算段大小(为正则1024算术左移该值,否则右移)
__le32 s_blocks_per_group; // 每个块组中块的总数
__le32 s_frags_per_group; // 每个块组中段的总数
__le32 s_inodes_per_group; // 每个块组中inode的总数
__le32 s_mtime; // POSIX中定义的文件系统装载时间
__le32 s_wtime; // POSIX中定义的文件系统最近被写入的时间
__le16 s_mnt_count; // 最近一次完整校验后被装载的次数
__le16 s_max_mnt_count; // 在进行完整校验前还能被装载的次数
__le16 s_magic; // 文件系统标志,ext2中为0xEF53
__le16 s_state; // 文件系统的状态
__le16 s_errors; // 文件系统发生错误时驱动程式应该执行的操作
__le16 s_minor_rev_level; // 局部修订级别
__le32 s_lastcheck; // POSIX中定义的文件系统最近一次检查的时间
__le32 s_checkinterval; // POSIX中定义的文件系统最近检查的最大时间间隔
__le32 s_creator_os; // 生成该文件系统的操作系统
__le32 s_rev_level; // 修订级别
__le16 s_def_resuid; // 报留块的默认用户ID
__le16 s_def_resgid; // 保留块的默认组ID
// 仅用于使用动态inode大小的修订版(EXT2_DYNAMIC_REV)
__le32 s_first_ino; // 标准文件的第一个可用inode的索引(非动态为11)
__le16 s_inode_size; // inode结构的大小(非动态为128)
__le16 s_block_group_nr; // 保存此超级块的块组号
__le32 s_feature_compat; // 兼容特性掩码
__le32 s_feature_incompat; // 不兼容特性掩码
__le32 s_feature_ro_compat; // 只读特性掩码
__u8 s_uuid[16]; // 卷ID,应尽可能使每个文件系统的格式唯一
char s_volume_name[16]; // 卷名(只能为ISO-Latin-1字符集,以’\0’结束)
char s_last_mounted[64]; // 最近被安装的目录
__le32 s_algorithm_usage_bitmap; // 文件系统采用的压缩算法
// 仅在EXT2_COMPAT_PREALLOC标志被设置时有效
__u8 s_prealloc_blocks; // 预分配的块数
__u8 s_prealloc_dir_blocks; // 给目录预分配的块数
__u16 s_padding1;
// 仅在EXT3_FEATURE_COMPAT_HAS_JOURNAL标志被设置时有效,用以支持日志
__u8 s_journal_uuid[16]; // 日志超级块的卷ID
__u32 s_journal_inum; // 日志文件的inode数目
__u32 s_journal_dev; // 日志文件的设备数
__u32 s_last_orphan; // 要删除的inode列表的起始位置
__u32 s_hash_seed[4]; // HTREE散列种子
__u8 s_def_hash_version; // 默认使用的散列函数
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; // 块组的第一个元块
__u32 s_reserved[190];
};
其中,__u8、__u16及__u32数据类型分别表示长度为8、16及32位的无符号数,而__s8、__s16及__s32数据类型表示长度为8、16及32位的有符号数。为清晰地表示磁盘上字或双字中字节的存放顺序,内核又使用了__le16、__le32、__be16和__be32数据类型,前两种类型分别表示字或双字的“小尾(little-endian )”排序方式(低阶字节在高位地址),而后两种类型分别表示字或双字的“大尾(big-endian )”排序方式(高阶字节在高位地址)。
(2) 块组描述符(Group Descriptor)
一个块组描述符用以描述一个块组的属性。存放于超级块所在块的下一个块中。一个块组描述符的结构如下:
struct ext2_group_desc
{
__le32 bg_block_bitmap; // 块位图所在的第一个块的块ID
__le32 bg_inode_bitmap; // inode位图所在的第一个块的块ID
__le32 bg_inode_table; // inode表所在的第一个块的块ID
__le16 bg_free_blocks_count; // 块组中未使用的块数
__le16 bg_free_inodes_count; // 块组中未使用的inode数
__le16 bg_used_dirs_count; // 块组分配的目录的inode数
__le16 bg_pad;
__le32 bg_reserved[3];
};
(3) 块位图和inode位图
块位图和inode位图的每一位分别指出块组中对应的那个块或inode是否被使用。
(4) inode表
inode表用于跟踪定位每个文件,包括位置、大小等(但不包括文件名),一个块组只有一个inode表。一个inode的结构如下:
struct ext2_inode {
__le16 i_mode; // 文件格式和访问权限
__le16 i_uid; // 文件所有者ID的低16位
__le32 i_size; // 文件字节数
__le32 i_atime; // 文件上次被访问的时间
__le32 i_ctime; // 文件创建时间
__le32 i_mtime; // 文件被修改的时间
__le32 i_dtime; // 文件被删除的时间(如果存在则为0)
__le16 i_gid; // 文件所有组ID的低16位
__le16 i_links_count; // 此inode被连接的次数
__le32 i_blocks; // 文件已使用和保留的总块数(以512B为单位)
__le32 i_flags; // 此inode访问数据时ext2的实现方式
union {
struct {
__le32 l_i_reserved1; // 保留
} linux1;
struct {
__le32 h_i_translator; // “翻译者”标签
} hurd1;
struct {
__le32 m_i_reserved1; // 保留
} masix1;
} osd1; // 操作系统相关数据
__le32 i_block[EXT2_N_BLOCKS]; // 定位存储文件的块的数组,前12个为块号,第13个为一级间接块号,第14个为二级间接块号,第15个为三级间接块号
__le32 i_generation; // 用于NFS的文件版本
__le32 i_file_acl; // 包含扩展属性的块号,老版本中为0
__le32 i_dir_acl; // 表示文件的“High Size”,老版本中为0
__le32 i_faddr; // 文件最后一个段的地址
union {
struct {
__u8 l_i_frag; // 段号
__u8 l_i_fsize; // 段大小
__u16 i_pad1;
__le16 l_i_uid_high; // 文件所有者ID的高16位
__le16 l_i_gid_high; // 文件所有组ID的高16位
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; // 段号
__u8 h_i_fsize; // 段大小
__le16 h_i_mode_high;
__le16 h_i_uid_high; // 文件所有者ID的高16位
__le16 h_i_gid_high; // 文件所有组ID的高16位
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; // 段号
__u8 m_i_fsize; // 段大小
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; // 操作系统相关数据
};
(5) 数据块
数据块中存放文件的内容,包括目录表、扩展属性、符号链接等。
2.2.2 目录结构
在ext2文件系统中,目录是作为文件存储的。根目录总是在inode表的第二项,而其子目录则在根目录文件的内容中定义。目录项在include/linux/ext2_fs.h文件中定义,其结构如下:
struct ext2_dir_entry_2 {
__le32 inode; // 文件入口的inode号,0表示该项未使用
__le16 rec_len; // 目录项长度
__u8 name_len; // 文件名包含的字符数
__u8 file_type; // 文件类型
char name[255]; // 文件名
};
符号链接:如果符号链接的路径名小于等于60个字符,就把它存放在索引节点的i_blocks字段,该字段是由15个4字节整数组成的数组,因此无需数据块。但是,如果路径名大于60个字符,就需要一个单独的数据块。
设备文件、管道和套接字:这些类型的文件不需要数据块。所有必要的信息都存放在索引节点中。
2.2.3 创建一个EXT2文件系统
在磁盘上创建一个文件系统通常有两个阶段。
第一步格式化磁盘,以使磁盘驱动程序可以将块号转换成对应的磁道和扇区,从而可以读和写磁盘上的物理块。现在的硬磁盘已经由厂家预先格式化,因此不需要重新格式化;在Linux上可以使用superformat或fdformat等实用程序对软盘进行格式化。
第二步才涉及创建文件系统,这意味着建立前文中详细描述的那些磁盘文件系统数据结构。
Ext2文件系统是由C程序mke2fs创建的[3]。mke2fs采用下列缺省选项,用户可以用命令行的标志修改这些选项:
- 块大小:1024字节(小文件系统的缺省值)
- 片大小:等于块的大小(因为块的分片还没有实现)
- 所分配的索引节点个数:每8192字节的组分配一个索引节点
- 保留块的百分比:5%
mke2fs程序执行下列操作: 1. 初始化超级块和组描述符。 2. 作为选择,检查分区是否包含有缺陷的块;如果有,就创建一个有缺陷块的链表。 3. 对于每个块组,保留存放超级块、组描述符、索引节点表及两个位图所需要的所有磁盘块。4. 把索引节点位图和每个块组的数据映射位图都初始化为0。 5. 初始化每个块组的索引节点表。 6. 创建/root目录。 7. 创建lost+found目录,由e2fsck使用这个目录把丢失和找到的缺陷块连接起来。 8. 在前两个已经创建的目录所在的块组中,更新块组中的索引节点位图和数据块位图。 9. 把有缺陷的块(如果存在)组织起来放在lost+found目录中。
3. 总结
通过对EXT2的论述,我们可以得到Linux下的EXT2文件系统有如下特点:
1、当创建Ext2文件系统时,系统管理员可以根据预期的文件平均长度来选择最佳的块大小(从1024B——4096B)。例如,当文件的平均长度小于几千字节时,块的大小为1024B是最佳的,因为这会产生较少的内部碎片——也就是文件长度与存放块的磁盘分区有较少的不匹配。另一方面,大的块对于大于几千字节的文件通常比较合合适,因为这样的磁盘传送较少,因而减轻了系统的开销。
2、当创建Ext2文件系统时,系统管理员可以根据在给定大小的分区上预计存放的文件数来选择给该分区分配多少个索引节点。这可以有效地利用磁盘的空间。
3、文件系统把磁盘块分为组。每组包含存放在相邻磁道上的数据块和索引节点。正是这种结构,使得可以用较少的磁盘平均寻道时间对存放在一个单独块组中的文件并行访问。
4、在磁盘数据块被实际使用之前,文件系统就把这些块预分配给普通文件。因此当文件的大小增加时,因为物理上相邻的几个块已被保留,这就减少了文件的碎片。
5、支持快速符号链接。如果符号链接表示一个短路径名(小于或等于60个字符),就把它存放在索引节点中而不用通过由一个数据块进行转换。
通过论述,可以得知,EXT2文件系统,已经相当的成熟和稳定,现在又出现了,EXT3文件系统,支持了日志记录系统。然而,现在伴随着社会的发展,随着64位计算机的普及,文件系统的大变革也正在进行。但是以往的技术,还是给新的变化提供了支持。
4. 参考文献
[1] Robert Love,《Linux 内核设计与实现》第二版,陈莉君,康华,张波译,机械工业出版社2006.
[2]百度百科:词条Linux:http://baike.baidu.com/link?url=vV7JTwO_VjWjVgYhrdAr6HwxZX0TfmLRWY9gQQOTuxFPfEgvTsTGyJ87XqUzgep3
[3]Daniel P.Bovet,Marco Cesati,《Understandingthe Linux Kernel, 3rd Edition》,O’Reilly,2006