下面以Linux的Ext2爲例介紹文件系統的組成。
Ext2採用了分立式目錄結構,即一個文件的目錄分爲目錄項和索引節點兩個部分。
Ext2的索引節點
在一個實際分立式目錄的文件系統中,索引節點(inode)主要需要兩部分內容來支持:一是inode結構;二是對於節點的操作函數。
Ext2的索引節點
Ext2的每個文件(或目錄)都有唯一的i節點ext2_inode,它保存了一個文件所有與存儲有關的屬性。
Linux在文件include/linux/ext2_fs.h中定義的i節點結構ext2_inode如下:
struct ext2_inode {
__le16 i_mode; /* 文件模式 */
__le16 i_uid; /* 文件擁有者uid的低16位 */
__le32 i_size; /* 文件大小 */
__le32 i_atime; /* 最後訪問時間 */
__le32 i_ctime; /* 創建時間 */
__le32 i_mtime; /* 修改時間 */
__le32 i_dtime; /* 刪除時間 */
__le16 i_gid; /* 塊組id的低16位 */
__le16 i_links_count; /* 鏈接計數,即文件別名的數目 */
__le32 i_blocks; /* 文件佔用的存儲塊數 */
__le32 i_flags; /* 標誌 */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* 文件索引表 */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* 碎片地址 */
union {
struct {
__u8 l_i_frag; /* 碎片數目 */
__u8 l_i_fsize; /* 碎片大小 */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; /* 碎片數目 */
__u8 h_i_fsize; /* 碎片大小 */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; /* 碎片數目 */
__u8 m_i_fsize; /* 碎片大小 */
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* 與操作系統相關的數據 */
};
其中,最重要的成員i_mode和指針i_block[]。i_mode指定文件類型;而指針數組i_block[]則是文件索引表。
i_block[]指針數組的示意圖如下:
i_block[]共有15項,其中前12項爲直接指向文件數據塊的指針,後3項分別爲採用多級索引結構的“一次間接指針”、“二次間接指針”和“三次間接指針”。其作用與內存管理中的多級頁表類似,便於大型文件的存儲處理。
也就是說:如果文件比較小,其數據塊少於12個,其數據塊索引就放在i_block[]的前12項中,如果文件比較大,超過12個數據塊就需要分配間接塊來保存數據塊索引。
Ext2的i節點操作函數
爲了對Ext2的i節點進行操作,系統還定義了Ext2文件i節點的操作函數集:
struct inode_operations ext2_file_inode_operations = {
.truncate = ext2_truncate,
#ifdef CONFIG_EXT2_FS_XATTR
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.listxattr = ext2_listxattr,
.removexattr = generic_removexattr,
#endif
.setattr = ext2_setattr,
.permission = ext2_permission,
};
可以看到,這裏操作集中沒有熟悉的文件操作函數,這是因爲這都是對磁盤文件的底層操作,文件還要再經一層乃至多層的封裝才能變成我們所熟悉的函數。
Ext2的目錄文件及目錄項
Ext2的目錄文件實質上是一個目錄項列表,其中每一項都是一個ext2_dir_entry_2結構的數據。它所包含的主要信息:
- 目錄項中文件名所對應的文件i節點號;
- 文件類型;
- 文件名稱。
在文件include/linux/ext2_fs.h中定義的目錄項結構ext2_dir_entry_2如下:
struct ext2_dir_entry_2 {
__le32 inode; /* 文件的i節點號 */
__le16 rec_len; /* 目錄項的長度 */
__u8 name_len; /* 文件名的長度 */
__u8 file_type; //文件類型
char name[EXT2_NAME_LEN]; /* 文件名 */
};
結構中的域file_type描述文件類型。不同文件類型的取值用枚舉定義如下:
enum {
EXT2_FT_UNKNOWN,
EXT2_FT_REG_FILE, //普通文件
EXT2_FT_DIR, //目錄
EXT2_FT_CHRDEV, //字符設備文件
EXT2_FT_BLKDEV, //塊設備文件
EXT2_FT_FIFO, //管道文件
EXT2_FT_SOCK, //Sock文件
EXT2_FT_SYMLINK,
EXT2_FT_MAX
};
按照通常的概念,目錄文件應該是ext2_dir_entry_2類型的數組,但Ext2沒有這樣做。爲了用戶方便,結構ext2_dir_entry_2中的文件名是一個可以根據文件名的長度變化的數組,這種做法就使得各個目錄項的長度並不相等,從而難以用數組來組成目錄文件。所以Ext2的目錄文件採用一個比較特殊的鏈表結構,如下圖:
在這種結構中,目錄項是連續存放的,而目錄項的連接則通過結構ext2_dir_entry_2中的域rec_len來實現的,即程序通過rec_len作爲偏移量來查找下一個目錄項。
每個目錄文件中的前兩項爲代表目錄自身的“.”和代表其上一級目錄(父目錄)的“..”。
每當用戶需要打開一個文件需要打開一個文件時,首先要指定待打開文件的路徑和名稱,文件系統會根據路徑和名稱搜索對應的目錄項;然後用該目錄項中的i節點號找到該文件的i節點;最後通過訪問i節點結構中的i_block[]數據塊來訪問文件。
目錄項、索引節點與文件數據塊之間的關係如下所示:
Ext2在磁盤上的存儲結構
Ext2文件系統把它所佔用的磁盤空間分成若干個塊組,如下所示:
每個塊組的內部結構如下圖所示:
每個塊組中都有一個內容完全相同的塊——超級塊,這個塊保存着Ext2整個文件系統的信息。在超級塊的後面,依次排序有:用來描述本塊組信息的塊組描述符表、用來表示本組內存儲塊使用情況的存儲塊管理位圖、用來記錄本塊組所有i節點被佔用情況的i節點管理位圖、本塊組的i節點表以及用來存儲各種文件的數據塊五個部分。
Ext2的超級塊
像一本書需要一個前言一樣,文件系統也需要有個類似的說明部分,但它說明的是文件系統基本信息,目的是使文件系統的使用者(操作系統)可以瞭解文件系統的結構、類型等,這個說明部分叫做文件系統的超級塊。不同的文件系統具有不同的超級塊。系統管理員及系統可以利用超級塊中的信息來對文件系統進行維護。
照理說,每個文件系統只要有一個超級塊就夠了,但Ext2爲了保險起見,在每一個塊組中都配置了一個超級塊。在正常情況下,Ext2只使用第一個塊組(塊組0)中的超級塊,而其他塊組中的超級塊只是一個備份。
在文件include/linux/ext2_fs.h中定義的超級塊數據結構ext2_super_block如下:
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; /* 第一個數據塊 */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment 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; /* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
__le32 s_lastcheck; /* time of last check */
__le32 s_checkinterval; /* max. time between checks */
__le32 s_creator_os; /* OS */
__le32 s_rev_level; /* Revision level */
__le16 s_def_resuid; /* Default uid for reserved blocks */
__le16 s_def_resgid; /* Default gid for reserved blocks */
__le32 s_first_ino; /* First non-reserved inode */
__le16 s_inode_size; /* size of inode structure */
__le16 s_block_group_nr; /* 本超級塊所在的塊組號 */
__le32 s_feature_compat; /* compatible feature set */
__le32 s_feature_incompat; /* incompatible feature set */
__le32 s_feature_ro_compat; /* readonly-compatible feature set */
__u8 s_uuid[16]; /* 卷的128位uuid */
char s_volume_name[16]; /* 卷名 */
char s_last_mounted[64]; /* directory where last mounted */
__le32 s_algorithm_usage_bitmap; /* For compression */
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16 s_padding1;
__u8 s_journal_uuid[16]; /* uuid of journal superblock */
__u32 s_journal_inum; /* inode number of journal file */
__u32 s_journal_dev; /* device number of journal file */
__u32 s_last_orphan; /* start of list of inodes to delete */
__u32 s_hash_seed[4]; /* HTREE hash seed */
__u8 s_def_hash_version; /* Default hash version to use */
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; /* First metablock block group */
__u32 s_reserved[190]; /* Padding to the end of the block */
};
由上述定義中可知,Ext2中的超級塊中主要具有如下一些內容:
- 幻數。文件系統的一個標識,在安裝文件系統時用於確認Ext2文件系統;
- 文件系統的版本號;
- 文件系統安裝計數;
- 超級塊所在的塊組號;
- 數據塊的大小;
- 塊組中數據塊的數目;
- 文件系統中空閒塊的數目;
- 文件系統中空閒索引節點的數目;
- 文件系統中第一個索引節點的號碼。
在Ext2文件系統中,第一個索引節點時根目錄的入口。
塊組描述符表
Ext2的一個塊組可以看做文件系統空間的一個分區,與超級塊的用途類似,爲了向使用者提供塊組的相關組織信息,每個塊組有一個塊組描述符表,其中主要提供塊組的位圖存放位置和i節點位圖存放位置等信息。
Linux在文件include/linux/ext2_fs.h中定義的塊組描述符表結構ext2_group_desc如下:
struct ext2_group_desc
{
__le32 bg_block_bitmap; /* 指向塊組的塊位圖的指針 */
__le32 bg_inode_bitmap; /* 指向i節點位圖的指針 */
__le32 bg_inode_table; /* i節點表的首地址 */
__le16 bg_free_blocks_count; /* 本組塊空閒塊的數目 */
__le16 bg_free_inodes_count; /* 本組塊空閒i節點的數目 */
__le16 bg_used_dirs_count; /* 本組塊分配給目錄文件的i節點數目 */
__le16 bg_pad;
__le32 bg_reserved[3];
};
塊組的塊位圖
Ext2文件系統用位圖來記錄塊組中數據塊的使用情況。數據塊位圖中的每一位表示該塊組中每一個塊的使用情況,如果爲1,則表示該對應塊已經被分配佔用;如果爲0,則表示該位還未被分配,是空閒塊。
塊組的i節點位圖
Ext2文件系統用位圖來記錄塊組中i節點使用情況。i節點位圖中的每一位表示該塊組中每一個i節點的使用情況,如果爲1,則表示該結點已被分配佔用;如果爲0,則表示結點還未被分配,是空閒節點。
塊組的i節點表
顧名思義,i節點表就是存放文件i節點的表格。每個塊組中所有i節點都按i節點號的順序存儲在i節點表中。i節點表通常需要佔用若干個數據塊。
Ext2文件的用戶操作函數集
作爲一個爲用戶服務的文件系統,Ext2位用戶提供的文件操作函數集如下:
const struct file_operations ext2_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.unlocked_ioctl = ext2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext2_compat_ioctl,
#endif
.mmap = generic_file_mmap,
.open = generic_file_open,
.release = ext2_release_file,
.fsync = ext2_sync_file,
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};