1. 前言
在上一篇文章中,我介绍了 VFS 中的重量级嘉宾 inode 结构体。inode 结构体是 VFS 的实现关键,但是俗语有云:"一个篱笆三个桩,一个好汉三个帮"。没有其他相关结构的帮助,仅凭 inode 结构体是没法实现 VFS 这么庞大的系统。
VFS 涉及的内核对象比较多,如果一股脑地搬出来,恐怕会杂乱无章,让人看了头大。一个文件系统要想注册到 VFS 进行使用,一般需要经历注册文件系统到内核、过载文件系统然后就可以对文件系统进行读写操作了。那么我们不妨按照这个顺序,将每个过程涉及到的数据结构一一呈现出来。如此一来,更具逻辑性。
2. 注册文件系统
文件系统实现两种注册进内核的方式,持久化编译到内核,内核的文件系统启动时便注册;另一种时将文件系统实现编译为模块,在相关内核载入内核时进行注册。
这两种注册方法本质上没有区别,都是使用 register_filesystem
函数向内核注册文件系统实现。只是注册时机不同而已。调用 register_filesystem
时,会将文件系统封装成一个file_system_type
结构体,并使用结构体中的next
字段将多个文件系统链接成一个链表。
下面是file_system_type
结构体定义,在调用register_filesystem
函数向内核注册文件系统时,内核会判断是否存在相同的name
。如果相同则阻止注册。
arduino
struct file_system_type {
const char *name; // 文件系统名称
int fs_flags;
struct super_block *(*get_sb) (struct file_system_type *, int,
const char *, void *, struct vfsmount *);
// 获取超级块
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
struct list_head fs_supers;
};
3. 挂载文件系统
类 UNIX 系统大多采用了单一文件系统的层次结构,新的文件系统可以集成到其中。用通俗点的话说就是只有一颗文件树,新的文件系统可以找这颗文件树任意一个叶子进行挂载。这里就产生了一个新的概念,挂载点。
如上图所示,是一颗文件树。其中相同颜色的是同一个文件系统。不难看出,图中所示共有三个文件系统。而其中/mnt
和/mnt/usb
则被称为挂载点,这是因为这些位置是装载文件系统的地方。
每个装载的文件系统都会产生一个vfsmount
实例。vfsmount
中包含指向文件系统的超级块和挂载点的目录。每挂载一个文件系统都在生成对应的vfsmount
结构。
arduino
struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
struct mnt_idmap *mnt_idmap;
};
vfsmount
并非挂载文件系统时需要在内存创建的唯一结构。超级块(superblock)是文件系统挂载的重要结构。文件系统的挂载开始于超级块的读取。
超级块是一个非常关键的数据结构,它存储了有关文件系统的整体信息和状态的元数据。超级块通常位于文件系统的开始部分,并且包含了文件系统的全局参数。
arduino
struct super_block {
struct list_head s_list; /* Keep this first */
dev_t s_dev; /* search index; _not_ kdev_t */
unsigned char s_blocksize_bits; // 块大小--比特
unsigned long s_blocksize; // 块大小
loff_t s_maxbytes; /* 最大文件大小 */
struct file_system_type *s_type; // 关联的文件系统
const struct super_operations *s_op; // 超级块支持的操作
unsigned long s_flags;
unsigned long s_iflags; /* internal SB_I_* flags */
unsigned long s_magic;
struct dentry *s_root; // 挂载的目录
struct rw_semaphore s_umount;
int s_count; // 引用计数
atomic_t s_active;
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
struct block_device *s_bdev;
struct hlist_node s_instances;
struct sb_writers s_writers;
void *s_fs_info; /* Filesystem private info */
/* Granularity of c/m/atime in ns (cannot be worse than a second) */
u32 s_time_gran;
/* Time limits for c/m/atime in seconds */
time64_t s_time_min;
time64_t s_time_max;
char s_id[32]; /* Informational name */
uuid_t s_uuid; /* UUID */
unsigned int s_max_links;
const struct dentry_operations *s_d_op; /* default d_op for dentries */
struct shrinker *s_shrink; /* per-sb shrinker handle */
/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;
/*
* Number of inode/mount/sb objects that are being watched, note that
* inodes objects are currently double-accounted.
*/
atomic_long_t s_fsnotify_connectors;
/* Read-only state of the superblock is being changed */
int s_readonly_remount;
/* per-sb errseq_t for reporting writeback errors via syncfs */
errseq_t s_wb_err;
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;
/*
* The list_lru structure is essentially just a pointer to a table
* of per-node lru lists, each of which has its own spinlock.
* There is no need to put them into separate cachelines.
*/
struct list_lru s_dentry_lru;
struct list_lru s_inode_lru;
struct rcu_head rcu;
struct work_struct destroy_work;
struct list_head s_inodes; /* 文件系统所有的inode */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* 等待写回的inode列表 */
};
超级块除了保存一些文件系统的基本信息之外,还维护了文件系统下所有关联 inode 结构。同时维护所有待写回的inode结构。
4. 参考资料
- 《深入Linux内核架构》
- it.0voice.com/p/t_pc/cour...