- 作者: 陈孝松
- 主页: chenxiaosong.com
- 哔哩哔哩教学视频: 陈孝松
- 课程: chenxiaosong.com/courses
- 博客: chenxiaosong.com/blog
- 贡献: chenxiaosong.com/contributions
- 邮箱: [email protected]
- QQ交流群: 544216206, 点击查看群介绍
一般的Linux书籍都是先讲解进程和内存相关的知识,但我想先讲解文件系统。
第一,因为我就是做文件系统的,更擅长这一块,其他模块的内容我还要再去好好看看书,毕竟不能误人子弟嘛;第二,是
因为文件系统模块更接近于用户态,是相对比较好理解的内容(当然想深入还是要下大功夫的),由文件系统入手比较适合初学者。
英文全称Extended file system,翻译为扩展文件系统。Linux内核最开始用的是minix文件系统,直到1992年4月,Rémy Card开发了ext文件系统,采用Unix文件系统(UFS)的元数据结构,在linux内核0.96c版中引入。设计上参考了BSD的快速文件系统(Fast File System,简称FFS)。1993年1月0.99版本中ext2合入内核, 2001年11月2.4.15版本中ext3合入内核,2006年10月10日2.6.19版本中ext4合入内核。
相关文档网站:
块组
块组(block group)的内容如下:
超级块 | 组描 述符 | 数据块位图 | inode 位图 | inode表 | 数据块 |
---|---|---|---|---|---|
1个块 | k个块 | 1个块 | 1个块 | n个块 | m个块 |
启动扇区和块组:
启动块 | 块组0 | 块组1 | … | 块组n |
---|
对于超级块的存储,ext2的采用了稀疏超级块(sparse superblock)技术,超级块只存储到块组0、块组1和其他ID可以表示为3、5、7的幂的块组中,也就是0、1、3、5、7、9、25、49…
块组中内容的解释:
- 超级块: 存储文件系统自身元数据
- 组描述符: 包含所有块组的状态
- 数据块位图: 每个bit表示对应的数据块是否空闲,1表示占用,0表示空闲
- inode位图: 每个bit表示对应的inode是否空闲
- inode表: 块组中的inode
- 数据块: 文件的有用数据
举个例子,32GB
的磁盘整个盘格式化为ext2文件系统,块大小为4KB
,1个块大小的数据块位图描述8*4K=32K
个数据块,也就是32K*4KB=128MB
,大约有32*1024MB/128MB=256
个块组。总块数为total
,块大小为bsize
字节,块组的总数约为total/(8*bsize)
,套到上面的例子,就是total=32*1024MB/4KB=8192K
,块组的总数约为8192K/(8*4K)=256
个。bsize
越小,块组数越大。
超级块
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 */
// 最小 EXT2_MIN_BLOCK_SIZE,最大 EXT2_MAX_BLOCK_SIZE
__le32 s_log_block_size; /* 块大小,对数表示,值为0时表示2^0*1024=1024,值为1时表示2^1*1024=2048,值为2时表示2^2*1024=4096 */
__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; /* 幻数,EXT2_SUPER_MAGIC */
__le16 s_state; /* 状态标志,挂载时为0,正常卸载为1(EXT2_VALID_FS),错误为2(EXT2_ERROR_FS) */
__le16 s_errors; /* 检测到错误的行为 */
__le16 s_minor_rev_level; /* 次版本号 */
__le32 s_lastcheck; /* 最后检查的时间 */
__le32 s_checkinterval; /* 检查间隔 */
__le32 s_creator_os; /* 在什么操作系统上格式化的 */
__le32 s_rev_level; /* Revision level,主版本号 */
__le16 s_def_resuid; /* 保留块的默认uid */
__le16 s_def_resgid; /* 保留块默认gid */
/*
* 这些字段仅适用于 EXT2_DYNAMIC_REV 超级块。
*
* 注意: 兼容功能集和不兼容功能集之间的区别在于,
* 如果内核不知道不兼容功能集中设置的位,
* 它应该拒绝挂载文件系统。
*
* e2fsck 的要求更加严格;如果它不知道
* 兼容或不兼容功能集中的某个功能,
* 它必须中止操作,而不是尝试处理
* 它不理解的东西...
*/
__le32 s_first_ino; /* 第一个非保留的索引节点号 */
__le16 s_inode_size; /* 磁盘索引节点大小 */
__le16 s_block_group_nr; /* 超级块块组号 */
__le32 s_feature_compat; /* 兼容特性,查看 EXT2_FEATURE_COMPAT_DIR_PREALLOC 等宏定义 */
__le32 s_feature_incompat; /* 非兼容特性 */
__le32 s_feature_ro_compat; /* 只读兼容特性 */
__u8 s_uuid[16]; /* 卷的 128 位 uuid,文件系统标识符 */
char s_volume_name[16]; /* 卷名 */
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]; /* 日志超级块的 uuid */
__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]; /* 填充到块的末尾 */
};
组描述符
struct ext2_group_desc
{
__le32 bg_block_bitmap; /* 数据块位图所在的块号 */
__le32 bg_inode_bitmap; /* inode位图所在的块号 */
__le32 bg_inode_table; /* inode表所在的起始块号 */
__le16 bg_free_blocks_count; /* 组中空闲块个数 */
__le16 bg_free_inodes_count; /* 组中空闲索引节点数 */
__le16 bg_used_dirs_count; /* 组中目录数 */
__le16 bg_pad;
__le32 bg_reserved[3];
};
inode表
struct ext2_group_desc
的bg_inode_table
表示inode表所在的起始块号,磁盘索引节点固定128字节(可以在gdb中打印p sizeof(struct ext2_inode)
),1024字节块大小包含8个inode,4096字节块大小包含32个inode。
注意没有索引节点号,因为可以通过计算出来,比如块大小为4096字节,块组中inode位图占用一个块,一个块组的inode个数为4096,索引节点12345在磁盘上的位置可以这样计算12345/4096=3余57
,所以在第3个块组(从块组0开始算)中索引节点表中的第57个表项。
/*
* 磁盘索引节点结构
*/
struct ext2_inode {
__le16 i_mode; /* 文件类型和访问权限,查看S_ISREG()等函数 */
__le16 i_uid; /* 所有者 Uid 的低 16 位,拥有者id */
// 文件长度,最高位没使用,最大表示2GB文件,大于2GB文件再使用i_dir_acl字段
__le32 i_size; /* 大小(字节) */
__le32 i_atime; /* 访问时间 */
__le32 i_ctime; /* 索引节点创建时间 */
__le32 i_mtime; /* 文件数据最后改变时间 */
__le32 i_dtime; /* 删除时间 */
__le16 i_gid; /* 组 ID 的低 16 位,用户组id */
__le16 i_links_count; /* 硬链接计数 */
__le32 i_blocks; /* 数据块数,以512字节为单位 */
__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,特定操作系统信息 */
// i_block 数据块指针,指向15个块,前12个指向数据,第13个一次间接地址,第14个二次间接地址,第15个三次间接地址
__le32 i_block[EXT2_N_BLOCKS];/* 指向块的指针 */
__le32 i_generation; /* 文件版本,给nfs用的 */
// i_file_acl 访问控制列表,指向一个存放增强属性的块,其他inode如果增强属性一样,可以共享同一个块
__le32 i_file_acl; /* 文件访问控制列表(ACL) */
__le32 i_dir_acl; /* 目录访问控制列表 */
__le32 i_faddr; /* 片地址 */
union {
struct {
__u8 l_i_frag; /* 片编号 */
__u8 l_i_fsize; /* 片大小 */
__u16 i_pad1;
__le16 l_i_uid_high; /* 以前是reserved2[0] */
__le16 l_i_gid_high; /* 以前是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_file_acl
指向一个存放增强属性的块,其他inode如果增强属性一样,可以共享同一个块,系统调用setxattr()
、lsetxattr()
、fsetxattr()
设置文件增强属性,getxattr()
、lgetxattr()
、fgetxattr()
返回文件增强属性,listxattr()
、llistxattr()
、flistxattr()
列出文件所有增强属性。这些系统调用是通过 chacl()
、setfacl()
、getfacl()
调用的。没有正式成为POSIX标准。
struct ext2_xattr_entry {
__u8 e_name_len; /* 名称长度 */
__u8 e_name_index; /* 属性名称索引 */
__le16 e_value_offs; /* 值在磁盘块中的偏移量 */
__le32 e_value_block; /* 属性存储的磁盘块 (n/i) */
__le32 e_value_size; /* 属性值的大小 */
__le32 e_hash; /* 名称和值的哈希值 */
char e_name[]; /* 属性名称,可变数组/柔性数组/零长度数组 */
};
各种文件类型的存储
文件类型如下:
#define FT_UNKNOWN 0 // 未知
#define FT_REG_FILE 1 // 常规文件
#define FT_DIR 2 // 目录
#define FT_CHRDEV 3 // 字符设备
#define FT_BLKDEV 4 // 块设备
#define FT_FIFO 5 // 命名管道
#define FT_SOCK 6 // 套接字
#define FT_SYMLINK 7 // 符号链接
#define FT_MAX 8 // 类型总数
常规文件刚创建时是空的,不需要数据块,可以用truncate()
或open()
系统调用清空,如输入命令> filename
。
设备文件、管道、套接字所有信息都存放在inode中。
符号链接名小于60个字符就放到struct ext2_inode
的i_block
数组中(15个4字节),如果大于60个字符就存到单独数据块中。
最后重点讲一下目录的存储,数据块包含ext2_dir_entry_2
结构:
/*
* 目录项的新版本。由于EXT2结构以英特尔字节顺序存储,并且name_len字段永远不可能大于255个字符,因此可以安全地将额外的一个字节重新分配给file_type字段。
*/
struct ext2_dir_entry_2 {
__le32 inode; /* 索引节点号 */
__le16 rec_len; /* 目录项长度,总是4的倍数 */
__u8 name_len; /* 文件名长度 */
__u8 file_type; // 文件类型,struct ext2_dir_entry中没有
char name[]; /* 文件名,最大EXT2_NAME_LEN (255)字节 */
};
我们举个例子,刚格式化完ext2,然后创建目录mkdir dir
,创建文件touch file
、创建软链接ln -s file link
。
file_type--+
|
name_len--+ |
| |
address inode rec_len | | name
+--+--+--+--|--+--|--|--|--+--+--+--+
0 | 2 | 12 | 1| 2| . \0 \0 \0|
+--+--+--+--|--+--|--|--|--+--+--+--+
12 | 2 | 12 | 2| 2| . . \0 \0|
+--+--+--+--|--+--|--|--|--+--+--+--+--+--+--+--+--+--+--+--+
24 | 11 | 20 |10| 2| l o s t + f o u n d \0 \0|
+--+--+--+--|--+--|--|--|--+--+--+--+--+--+--+--+--+--+--+--+
44 | 15809 | 12 | 3| 2| d i r \0|
+--+--+--+--|--+--|--|--|--+--+--+--+
56 | 12 | 12 | 4| 1| f i l e|
+--+--+--+--|--+--|--|--|--+--+--+--+
68 | 13 | 12 | 4| 7| l i n k|
+--+--+--+--|--+--|--|--|--+--+--+--+
如果删除dir
,就会变成以下样子,删除的目录inode
改为0
,然后前一项的rec_len
加上12
。
file_type--+
|
name_len--+ |
| |
address inode rec_len | | name
+--+--+--+--|--+--|--|--|--+--+--+--+
0 | 2 | 12 | 1| 2| . \0 \0 \0|
+--+--+--+--|--+--|--|--|--+--+--+--+
12 | 2 | 12 | 2| 2| . . \0 \0|
+--+--+--+--|--+--|--|--|--+--+--+--+--+--+--+--+--+--+--+--+
24 | 11 | 32 |10| 2| l o s t + f o u n d \0 \0|
+--+--+--+--|--+--|--|--|--+--+--+--+--+--+--+--+--+--+--+--+
44 | 0 | 12 | 3| 2| d i r \0|
+--+--+--+--|--+--|--|--|--+--+--+--+
56 | 12 | 12 | 4| 1| f i l e|
+--+--+--+--|--+--|--|--|--+--+--+--+
68 | 13 | 12 | 4| 7| l i n k|
+--+--+--+--|--+--|--|--|--+--+--+--+