- 作者: 陈孝松
- 主页: chenxiaosong.com
- 哔哩哔哩教学视频: 陈孝松
- 课程: chenxiaosong.com/courses
- 博客: chenxiaosong.com/blog
- 贡献: chenxiaosong.com/contributions
- 邮箱: chenxiaosong@chenxiaosong.com
- 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合入内核。
相关文档网站:
磁盘和内存数据结构的关系如下,动态缓存指文件关闭或数据块被删除后页框回收算法从高速缓存中删除数据:
- 超级块: 磁盘
ext2_super_block
,内存ext2_sb_info
,总是缓存 - 组描述符: 磁盘和内存都是
ext2_group_desc
,总是缓存 - 块位图和inode位图: 磁盘是块中的位数组,内存是缓冲区中的位数组,动态缓存
- 索引节点: 磁盘
ext2_inode
,内存ext2_inode_info
,动态缓存,空闲索引节点从不缓存 - 数据块: 磁盘是字节数组,内存是VFS缓冲区,动态缓存,空闲块从不缓存
超级块
VFS的struct super_block
中的s_fs_info
指向struct ext2_sb_info
类型的结构:
/*
* 第二扩展文件系统的内存中超级块数据 */
*/
struct ext2_sb_info {
unsigned long s_inodes_per_block;/* 每个块的 inode 数量 */
unsigned long s_blocks_per_group;/* 每组中的块数 */
unsigned long s_inodes_per_group;/* 每组中的 inode 数量 */
unsigned long s_itb_per_group; /* 每组的 inode 表块数 */
unsigned long s_gdb_count; /* 组描述符块的数量 */
// 组描述符的个数,可以放在一个块中
unsigned long s_desc_per_block; /* 每个块的组描述符数量 */
unsigned long s_groups_count; /* 文件系统中的组数 */
unsigned long s_overhead_last; /* 最近一次计算的开销 */
unsigned long s_blocks_last; /* 最近一次看到的块数 */
// 包含磁盘超级块的缓冲区的缓冲区头
struct buffer_head * s_sbh; /* 包含超级块的缓冲区 */
// 指向磁盘超级块所在的缓冲区
struct ext2_super_block * s_es; /* 指向缓冲区中超级块的指针 */
// 指向一个缓冲区(包含组描述符的缓冲区)首部数组
struct buffer_head ** s_group_desc;
unsigned long s_mount_opt;
unsigned long s_sb_block;
kuid_t s_resuid;
kgid_t s_resgid;
unsigned short s_mount_state;
unsigned short s_pad;
int s_addr_per_block_bits;
int s_desc_per_block_bits;
int s_inode_size;
int s_first_ino;
spinlock_t s_next_gen_lock;
u32 s_next_generation;
unsigned long s_dir_count;
u8 *s_debts;
struct percpu_counter s_freeblocks_counter;
struct percpu_counter s_freeinodes_counter;
struct percpu_counter s_dirs_counter;
struct blockgroup_lock *s_blockgroup_lock;
/* 每个文件系统预留窗口树的根 */
spinlock_t s_rsv_window_lock;
struct rb_root s_rsv_window_root; // ext2_reserve_window_node的所有实例
struct ext2_reserve_window_node s_rsv_window_head;
/*
* s_lock 保护 s_mount_state、s_blocks_last、s_overhead_last 和由 sbi->s_es 指向的
* 超级块缓冲区内容的并发修改。
*
* 注意: 在 ext2_show_options() 中使用它来提供挂载选项的一致视图。
*/
spinlock_t s_lock;
struct mb_cache *s_ea_block_cache;
struct dax_device *s_daxdev;
u64 s_dax_part_off;
};
各个数据结构之间的关系如下图:
ext2 partition
+-------+----------+----------+----------+
| super |group |group |group |
| block |descriptor|descriptor|descriptor|
+-------+----------+----------+----------+
^ ^ ^ ^
| | | |
| +------+ +--------+ +----------+
| | | |
+-----------+ +-----------+ +-----------+ +-----------+
+---------------------+ | buffer | | buffer | | buffer | | buffer |
| super_block | +--->+-----------+ +-----------+ +-----------+ +-----------+
| | | ^ ^ ^ ^
| .s_fs_info | | |b_data |b_data |b_data |b_data
| +--------------+----|--s_es--+ | | | |
| | ext2_sb_info |----|----s_sbh--->+-----------+ +-----------------------------------------+
| +--------------+ | |buffer_head| |+-----------+ +-----------+ +-----------+|
| | | +-----------+ ||buffer_head| |buffer_head| |buffer_head||
+---------------------+ |+-----------+ +-----------+ +-----------+|
| +-----------------------------------------+
s_group_desc ^
| |
+------------------------------------------------------+
挂载时struct file_system_type ext2_fs_type
的ext2_mount()
方法再执行到ext2_fill_super()
从磁盘读取超级块。
ext2超级块的操作实现是struct super_operations ext2_sops
。
索引节点
/*
* 第二扩展文件系统在内存中的 inode 数据
*/
struct ext2_inode_info {
__le32 i_data[15];
__u32 i_flags;
__u32 i_faddr;
__u8 i_frag_no;
__u8 i_frag_size;
__u16 i_state;
__u32 i_file_acl;
__u32 i_dir_acl;
__u32 i_dtime;
/*
* i_block_group 是包含此文件 inode 的块组的编号。
* 在 inode 的整个生命周期中保持不变,它用于进行块分配决策 -
* 我们试图将文件的数据块放置在其 inode 块附近,并将新的 inode 放置在其父目录的 inode 附近。
*/
__u32 i_block_group;
/* 块预读 */
struct ext2_block_alloc_info *i_block_alloc_info;
__u32 i_dir_start_lookup;
#ifdef CONFIG_EXT2_FS_XATTR
/*
* 扩展属性可以独立于主文件数据进行读取。即使在读取时也获取 i_mutex 会导致扩展属性的读取者和常规文件数据的写入者之间产生竞争,
* 因此我们在读取或更改扩展属性时,会改为在 xattr_sem 上进行同步。
*/
struct rw_semaphore xattr_sem;
#endif
rwlock_t i_meta_lock;
/*
* truncate_mutex 用于将 ext2_truncate() 与 ext2_getblock() 串行化。
* 它还保护 inode 的预留数据结构的内部: ext2_reserve_window 和
* ext2_reserve_window_node。
*/
struct mutex truncate_mutex;
struct inode vfs_inode; // 虚拟文件系统的索引节点
struct list_head i_orphan; /* 已解除链接但仍打开的 inodes */
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
};
struct ext2_block_alloc_info {
/* 预留窗口信息 */
struct ext2_reserve_window_node rsv_window_node;
/*
* 是曾经 ext2_inode_info 结构中的 i_next_alloc_block
* 是文件中最近分配的块的逻辑(文件相对)编号。
* 我们用这个来检测线性递增的分配请求。
*/
__u32 last_alloc_logical_block;
/*
* 曾是 ext2_inode_info 结构中的 i_next_alloc_goal
* 是 i_next_alloc_block 的物理对应项。它是最近分配给该文件的块的物理块编号。
* 当我们检测到线性递增的请求时,这为我们提供了下一次分配的目标。
*/
ext2_fsblk_t last_alloc_physical_block;
};
struct ext2_reserve_window_node {
struct rb_node rsv_node;
__u32 rsv_goal_size; // 预留窗口的预期长度, 最大为 EXT2_MAX_RESERVE_BLOCKS
__u32 rsv_alloc_hit; // 预分配的命中数
struct ext2_reserve_window rsv_window; // 预留窗口
};
由struct super_operations ext2_sops
的ext2_alloc_inode()
分配索引节点对象。
ext2索引节点操作实现:
- 常规文件:
struct inode_operations ext2_file_inode_operations
- 目录:
struct inode_operations ext2_dir_inode_operations
- 快速符号链接(路径名小于60字节):
struct inode_operations ext2_fast_symlink_inode_operations
- 普通符号链接(路径名大于60字节):
struct inode_operations ext2_symlink_inode_operations
ext2_inode_info->vfs_inode->i_mapping->a_ops
的实现是ext2_aops
和ext2_dax_aops
(DAX,Direct Access,允许文件系统直接访问持久性内存(如非易失性内存,NVDIMM)上的数据,而无需经过缓存。这可以显著提高I/O性能,特别是在读取和写入小文件时)。