最近一直忙于基于android的动态分区的方案和virtual A/B方案通过rust重写升级的框架,里面有将IO子系统的框架学习了,主要是基于deviceMapper的用法和实现原理,只是学习了基本的原理和如何使用,对于其最底下的IO层基本不知道如何实现,准备放到文件IO章节学习。本周末接着学习文件系统,之前了解了以下文件系统的基本知识,本次开始真正进入到内核的虚拟文件系统学习之旅。
我们从一个简单的问题开始,我们如何访问文件?作为一个普通的使用者,我们不太关系我们如何访问计算机上的数据,不管这个数据是存储在机械硬盘上,还是SSD,甚至是远程的设备上;其次,我们也不想关心,我们现在用的是基于windows的ntfs还是Linux的ext4,不关心我该如何去挂载。正是通过设计,linux的设计者需要把我们的大脑从思考数据存储在哪里以及如何存储这样的细节中解放出来,Linux设计了一个文件系统的中间层,上层用户都直接和VFS打交道,文件系统开发者再把VFS转换为自己的格式。这样做的优点主要有:
要先有个大视角,让我们站在万米高空,鸟瞰我们的虚拟文件系统。虚拟文件系统的主要功能,是实现多种文件系统操作接口的统一,既能让上层的调用者使用同一套接口与底层的各种文件系统交互,又能对文件系统提供一个标准接口,使Linux系统能同时支持多种文件系统。虚拟文件系统与上层应用以及底层的各种文件系统之间的关系如图1所示。
我们不能总是从高空去俯瞰虚拟文件系统,其中包括有很多细节,如下图所示
了解所有的细节会导致文档比较长,所以我们开始分解VFS并浏览所涉及的主要数据结构,VFS采用面向对象的设计,主要包括超级块、索引节点、目录项、文件四个结构体对象。由之前的理论学习,我们知道
若要内核能够识别、挂载某一类型的文件系统,内核必须知道文件系统类型,定义在include/linux/fs.h 中
struct file_system_type {
const char *name; //文件系统类型名称,唯一标识一种文件系统
int fs_flags; //文件系统类型标志
#define FS_REQUIRES_DEV 1 //文件系统保存在外部块设备中
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *); //挂载函数
void (*kill_sb) (struct super_block *); //删去超级块实例函数,在卸载文件时调用
struct module *owner; //模块指针
struct file_system_type * next; //单链表成员,指向下一个文件系统类型实例
struct hlist_head fs_supers; //散列链表,链接已挂载相同类型文件系统的超级块实例
struct lock_class_key s_lock_key; //没有选择LOCKDEP配置选项为空结构体
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};
file_system_type结构体内主要成员简介如下:
内核中所有注册的文件系统类型file_system_type实例由单链表管理,表头为file_systems,定义在/fs/filesystem.c文件内,注册file_system_type实例就是将其插入到单链表。
static struct file_system_type *file_systems;
static DEFINE_RWLOCK(file_systems_lock);
文件系统类型实例的操作函数简列如下(fs/filesystem.c):
各文件系统类型的实现代码位于/fs/目录下,每个类型对应一个子目录。例如:ext2文件系统类型的代码位于/fs/ext2/目录下。文件系统类型的初始化函数在内核初始化子系统或加载模块时调用,主要完成文件系统类型的注册,私数据结构的创建、初始化等工作
可以通过以下的命令,查看内核支持的文件系统类型
内核中每挂载文件系统需要创建超级块super_block结构体实例,super_block结构体表示挂载文件系统的整体信息,例如:文件系统类型、数据块的大小核数量等。
通常块设备(分区)中文件系统的开头分出一个指定大小的区域,用于保存此分区文件系统的信息,此区域对应虚拟文件系统中定义的超级块。在挂载文件系统时,内核从此区域中读取信息,填充至超级块super_block实例中,超级块在include/linux/fs.hs中定义
struct super_block {
struct list_head s_list; //将实例链接到全局双链表super_blocks
dev_t s_dev; //所在块设备(分区)设备号
unsigned char s_blocksize_bits; //数据块大小以2的底取对数
unsigned long s_blocksize; //数据块大小,字节数
loff_t s_maxbytes; //最大文件长度,字节数
struct file_system_type *s_type; //指向文件系统类型实例
const struct super_operations *s_op; //超级块操作结构指针
const struct dquot_operations *dq_op; //用户磁盘配额管理
const struct quotactl_ops *s_qcop;
const struct export_operations *s_export_op;
unsigned long s_flags;
unsigned long s_iflags; /* internal SB_I_* flags */
unsigned long s_magic; //魔数,内核为每种文件系统类型分配唯一的标识数字
struct dentry *s_root; //指向文件系统根目录项dentry实例
struct rw_semaphore s_umount;
int s_count;
atomic_t s_active;
#ifdef CONFIG_SECURITY
void *s_security;
#endif
const struct xattr_handler **s_xattr; //扩展属性处理函数
const struct fscrypt_operations *s_cop; //
struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */
struct list_head s_mounts; //挂载mount结构体实例链表,一个分区可以执行多个挂载操作
struct block_device *s_bdev; //块设备(分区)对应块设备数据结构指针
struct backing_dev_info *s_bdi; //后备存储设备信息
struct mtd_info *s_mtd; //MTD设备信息
struct hlist_node s_instances; //散列表节点,链入文件系统类型散列链表,表头fs_supers
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options */
struct sb_writers s_writers;
char s_id[32]; /* Informational name */
u8 s_uuid[16]; /* UUID */
void *s_fs_info; //具体文件系统私有数据指针
unsigned int s_max_links;
fmode_t s_mode;
/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge */
/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype;
/*
* Saved mount options for lazy filesystems using
* generic_show_options()
*/
char __rcu *s_options;
const struct dentry_operations *s_d_op; //赋予所有目录项dentry实例d_op成员
/*
* Saved pool identifier for cleancache (-1 means none)
*/
int cleancache_poolid;
struct shrinker s_shrink; //slab缓存收缩器,用于页面回收机制
/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;
/* Being remounted read-only */
int s_readonly_remount;
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq; //直接读写操作工作队列
struct hlist_head s_pins; //散列链表头
/*
* Owning user namespace and default context in which to
* interpret filesystem uids, gids, quotas, device nodes,
* xattrs and security labels.
*/
struct user_namespace *s_user_ns;
/*
* Keep the lru lists last in the structure so they always sit on their
* own individual cachelines.
*/
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp; //dentry实例LRU链表
struct list_lru s_inode_lru ____cacheline_aligned_in_smp; //inode实例LRU链表
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock; /* sync serialisation lock */
/*
* Indicates how deep in a filesystem stack this SB is
*/
int s_stack_depth;
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
}
super_block结构体中主要成员简介如下:
super_block结构体中包含两个虚拟文件系统的接口数据结构,dentry_operations和super_operations。
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *s b); //在给定的超级块下创建和初始化一个新的索引节点对象
void (*destroy_inode)(struct inode *); //销毁alloc_inode创建实例,释放给定的索引节点对象
/* VFS在索引节点被修改(脏)时会调用此函数。日志文件系统(如ext3/ext4)执行该函数进行日志更新 */
void (*dirty_inode) (struct inode *, int flags); //标记inode脏,表示inode需要回写
/* 用于将给定的索引节点写入磁盘,wbc表示写入时的控制信息 */
int (*write_inode) (struct inode *, struct writeback_control *wbc); //回写inode元数据
/* 最后一个索引节点的引用被释放后,VFS调用该函数 */
int (*drop_inode) (struct inode *); //释放inode时调用
/* 用于从磁盘上删除给定的索引节点对象 */
void (*evict_inode) (struct inode *); //释放inode调用
/* 卸载文件系统时由VFS调用,用来释放超级块。s_lock进行保护 */
void (*put_super) (struct super_block *); //当虚拟文件系统要释放super_block时调用此函数
/* 使文件系统的数据元与磁盘上的文件系统同步。wait指定操作是否同步。*/
int (*sync_fs)(struct super_block *sb, int wait); //同步文件系统中所有脏inode
int (*freeze_super) (struct super_block *);
/*禁止对文件系统做改变,之后使用给定的超级块更新磁盘上的超级块。LVM(逻辑卷标管理)会调用该函数*/
int (*freeze_fs) (struct super_block *);//当虚拟文件系统正在锁定一个文件系统并强制其进入一致状态时调用此函数
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);//当虚拟文件系统解锁文件系统并使其重新可写时调用此函数
/*获取目录项对象状态信息*/
int (*statfs) (struct dentry *, struct kstatfs *); //获取文件系统统计量
/*指定新的安装选项重新安装文件系统时,VFS调用此函数。s_lock进行保护*/
int (*remount_fs) (struct super_block *, int *, char *); //重新挂载文件系统
void (*umount_begin) (struct super_block *); //卸载文件系统前调用
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA //分别用于VFS读写文件系统quota文件
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *, //超级块缓存收缩函数调用此函数
struct shrink_control *);
long (*free_cached_objects)(struct super_block *, //用于扫描可释放对象,并释放它们
struct shrink_control *);
};
每一个类型的文件系统,需要提供这个结构体,例如ext4文件系统
全局super_block链表:这个链表包含了系统中的所有super_block(不止一种文件系统super_block)。通过一个list_head类型的全局变量super_blocks来访问,内核中使用宏实现这个全局变量的初始化:
static LIST_HEAD(super_blocks);
static DEFINE_SPINLOCK(sb_lock);
list_add_tail(&s->s_list, &super_blocks);
hlist_add_head(&s->s_instances, &type->fs_supers);
当有新的文件系统挂载时,它会通过自身super_block结构中list_head类型的成员s_list链接到super_blocks上,形成一个全局的链表
一个文件系统类型(如ext4),可以有多个super_block实例。举例来说,我的Linux系统中,划分了3个分区分别挂载在/,/home,/boot目录上,挂载的文件系统都是ext4,因此,这3个ext4文件系统实例都有自己的super_block,它们通过super_block中hlist_node类型的成员s_instances链接成“ext4的super_block链表”。可以通过file_system_type的fs_supers成员来访问:
目录项中保存了普通目录、文件的名称等少量信息,主要用于文件的管理,构成文件树状层次结构。文件的具体信息,如读写权限、修改时间、文件内容等,保存在被称为节点的结构中。回顾下上一节介绍的目录结构,在实际的文件系统中,保存文件内容的数据块号等信息不是保存在目录项中,而是保存在被称为节点的结构中,如下图所示
内存中,每个文件都有一个inode,一切皆文件,其实应该是说一切皆inode。inode保存了文件系统中一个文件的属性描述,比如文件类型,文件权限,属主,文件创建、读取或修改的时间等等。除了内存中的inode,磁盘中也有对应的inode结构,比如ext4文件系统中,内存中的inode是struct ext4_inode_info,而磁盘中的inode是struct ext4_inode_info,今天我们主要分析下内存中的inode结构。
对于所有文件系统对象类型,所有文件系统中的每个对象都存在一个 inode,即索引节点的缩写。它在include/linux/fs.h中定义
struct inode {
umode_t i_mode; //文件类型及访问权限
unsigned short i_opflags; //进程打开文件的标记
kuid_t i_uid; //文件用户主ID
kgid_t i_gid; //文件用户组ID
unsigned int i_flags; //表示文件属性
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op; //节点操作结构指针
struct super_block *i_sb; //文件系统超级块结构指针
struct address_space *i_mapping; //文件地址空间结构体指针,通常指向i_data成员,指向inode在内存中的pagecache
#ifdef CONFIG_SECURITY
void *i_security;
#endif
/* Stat data, not accessed from path walking */
unsigned long i_ino; //inode编号,inode节点号 ls -i 可以查看文件inode号
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev; //表示设备文件时,保存设备号
loff_t i_size; //文件大小,字节数
struct timespec i_atime; //最后访问时间
struct timespec i_mtime; //最后修改时间
struct timespec i_ctime; //最后修改inode时间
spinlock_t i_lock; //锁定i_blocks, i_bytes, maybe i_size
unsigned short i_bytes; //块大小,字节数
unsigned int i_blkbits; //块大小字节数取对数
blkcnt_t i_blocks; //文件大小,块大小
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state; //状态信息
struct rw_semaphore i_rwsem; //互斥锁用于保护地址空间
unsigned long dirtied_when; //inode变脏的时间
unsigned long dirtied_time_when;
//通过该变量链接到全局inode哈希表inode_hashtable,用于inode的快速查找
struct hlist_node i_hash; //将实例链入全局散列表
struct list_head i_io_list; //将实例链入后备存储设备回写inode链表
#ifdef CONFIG_CGROUP_WRITEBACK //组回写
struct bdi_writeback *i_wb; /* the associated cgroup wb */
/* foreign inode detection, see wbc_detach_inode() */
int i_wb_frn_winner;
u16 i_wb_frn_avg_time;
u16 i_wb_frn_history;
#endif
struct list_head i_lru; //将实例链入超级块LRU链表,表头sb.s_inode_lru
struct list_head i_sb_list; //将实例链入超级块inode链表,表头sb.s_inodes
struct list_head i_wb_list; /* backing dev writeback list */
union {
struct hlist_head i_dentry; //散列链表头,链接关联的dentry实例
struct rcu_head i_rcu;
};
u64 i_version;
atomic_t i_count; //引用计数
atomic_t i_dio_count;
atomic_t i_writecount;
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
const struct file_operations *i_fop; //文件操作结构指针,文件操作的接口
struct file_lock_context *i_flctx; //文件地址空间实例,缓存文件内容(页缓存)
struct address_space i_data;
struct list_head i_devices; //inode表示设备文件时,将实例链接到cdev.list或block_device.bd_inodes链表
union {
struct pipe_inode_info *i_pipe; //管道文件
struct block_device *i_bdev; //指向块设备结构体 block_device
struct cdev *i_cdev; //指向字符设备结构体cdev
char *i_link; //链接文件内容字符串
unsigned i_dir_seq;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
struct fscrypt_info *i_crypt_info;
#endif
void *i_private; //指向具体文件系统私有数据
}
inode结构体主要成员成员简介如下:
i_mode:标记文件类型及访问权限。数据类型为umode_t,它是一个16位的无符号整型数,定义在/include/linux/types.h头文件内。各标记位定义如下:
i_mapping: 文件地址空间address_space结构体指针成员,通常指向i_data成员
i_data: 文件地址空间address_space结构体实例,主要用于在基于块设备的文件系统中管理文件系统内容的页缓存和实现文件缓存页数据与块设备的同步。
i_op: inode_operations结构体指针,此结构主要用于对目录文件内容的操作,目录项的操作,如查找目录项,创建目录项,删去目录项,文件重命名等
i_fop: file_operations结构体,此结构主要用于对文件内容的操作,如读写文件内容等
inode_operations结构体主要包含对目录文件内容操作的函数指针。目录文件的内容是目录项,因此对目录文件内容的操作就是对目录项的操作,例如:在目录下查找指定名称的子目录项、创建新目录项(新文件)、删除目录项、文件重命名等。inode_operations结构体还包含对文件属性的操作函数等。
inode_operations结构体实例由具体文件系统类型实现,在创建inode实例时由具体文件系统类型代码将inode_operations实例指针赋予inode实例i_op成员。inode_operations结构体体定义在/include/linux/fs.h头文件:
struct inode_operations {
/*在特定目录中寻找索引节点,该索引节点要对应于dentry中给出的文件名*/
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); //查找子目录
const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *); //inode表示符号链接,用于查找链接文件
int (*permission) (struct inode *, int); //文件访问权限检查
struct posix_acl * (*get_acl)(struct inode *, int);
int (*readlink) (struct dentry *, char __user *,int);
/*VFS通过系统调用create()和open()来调用该函数,从而为dentry对象创建一个新的索引节点*/
int (*create) (struct inode *,struct dentry *, umode_t, bool); //创建普通文件
/*被系统调用link()调用,用来创建硬连接。名称由最后一个参数指定,连接对象是inode目录中当一个参数(目录项)所代表的文件*/
int (*link) (struct dentry *,struct inode *,struct dentry *); //创建链接
/*系统调用unlink调用*/
int (*unlink) (struct inode *,struct dentry *);
/*系统调用symlink,创建符号连接(软连接)*/
int (*symlink) (struct inode *,struct dentry *,const char *);
/*系统调用mkdir()调用,创建一个新目录*/
int (*mkdir) (struct inode *,struct dentry *,umode_t); //创建目录
/*系统调用rmdir()调用,删除一个目录*/
int (*rmdir) (struct inode *,struct dentry *); //删去目录
/*系统调用mknod()调用,创建特殊文件(设备文件、命名管道或套接字)*/
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);//创建设备节点,命名管道等
/*VFS调用该函数来移动文件。前两个参数是旧路径,后两个是新路径*/
int (*rename) (struct inode *, struct dentry *, //重命名
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *); //用于设置文件属性,在chmod()和相关的系统调用中调用
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);//用于获取文件属性,在stat()和相关的系统调用中调用
ssize_t (*listxattr) (struct dentry *, char *, size_t);//用于列出给定文件所有的扩展属性
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
int (*update_time)(struct inode *, struct timespec *, int); //用于更新特定时间和inode的i_version
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode, int *opened);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
} ____cacheline_aligned;
• lookup:在inode表示目录文件内容中查找子目录项,并填充dentry实例(已创建dentry实例,实例中包含目录项(文件)名称,用于查找),这是一个非常重要的函数,在此函数内需要创建inode实例并初始化。
file_operations结构体主要包含对文件内容的操作函数指针,是内核操作文件的主要接口,是内核中非常重要的一个数据结构。因为外部设备也被视为文件(设备文件),file_operations结构体也是内核操作外部设备的接口,设备驱动程序的主要工作就是实现file_operations结构体实例。
file_operations结构体与inode_operations结构体一样由具体文件系统类型实现,在打开文件创建inode实例时将结构体实例指针赋予inode实例i_fop成员。file_operations结构体定义在/include/linux/fs.h头文件内,这个我们太熟悉了,就不多做介绍了。
内核为inode结构体建立了slab缓存和全局散列表inode_hashtable,实例还关联到超级块中双链表。inode实例与dentry实例关联,添加到内核根文件系统结构中,文件系统超级块super_block结构体中包含两个inode实例双链表,表头分别是super_block.s_inode_lru(LRU链表)和super_block.s_inodes。
内核在搜索文件时为每个目录项dentry实例创建关联的inode实例(或关联到现有inode实例),并将inode实例添加到全局散列表和超级块s_inodes双链表中,释放inode实例时,暂时不用的实例将被添加到超级块s_inode_lru双链表中。
void __init vfs_caches_init(void)
{
//生成names_cache slab缓存,是VFS为保存路径path信息,而临时使用的内存空间缓存
//可以通过__getname()/__putname()等函数使用names_cache slab缓存
names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
//为支持对目录项的缓存做准备,生成目录项的slab缓存后,构建散列表进行管理
//此外设置系统可用内存不足时回收目录项缓存的方式
dcache_init();
//对索引节点执行缓存做准备,和初始化目录项一样,生成索引节点的slab缓存,构建
//散列表后设置回收方式
inode_init();
//VFS使用file结构体显示系统打开的文件,为了管理file结构体
files_init();
files_maxfiles_init();
//Linux系统中所有的文件系统通过VFS层向用户显示,被挂载的文件系统通过vfsmount结构体显示,该结构体还显示VFS层
//生成vfsmount结构体的mnt_cacha_slab缓存,虚拟系统则初始化sysfs,rootfs
mnt_init();
//Linux将与系统连接的所有设备显示为文件,这些文件就是设备文件,注册块设备驱动
bdev_cache_init();
//字符设备初始化
chrdev_init();
}
我们这里主要关注节点初始化,该接口主要是生成索引节点的slab缓存并构建散列表
void __init inode_init(void)
{
unsigned int loop;
//生成索引节点结构体inode_cacha_slab缓存
inode_cachep = kmem_cache_create("inode_cache",
sizeof(struct inode),
0,
(SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|
SLAB_MEM_SPREAD|SLAB_ACCOUNT),
init_once);
/* Hash may have been set up in inode_init_early */
if (!hashdist)
return;
//生成散列表管理各索引节点对象并进行初始化
inode_hashtable =
alloc_large_system_hash("Inode-cache",
sizeof(struct hlist_head),
ihash_entries,
14,
0,
&i_hash_shift,
&i_hash_mask,
0,
0);
for (loop = 0; loop < (1U << i_hash_shift); loop++)
INIT_HLIST_HEAD(&inode_hashtable[loop]);
}
从根文件系统某个目录项至另一个目录项的路径,称为目录。例如:/mnt/abc.txt,表示一个目录。而目录中每个分量,如mnt、abc.txt等称目录项。
VFS中目录项是对具体文件系统中的普通目录项和文件目录项的抽象。目录项在虚拟文件系统中由dentry结构体表示,dentry实例构成了根文件系统的树状层次结构。在打开文件时会创建路径中各目录项对应的dentry实例和inode实例。
内核在打开文件时,将在具体文件系统中查找各目录项分量,提取其中信息,创建denty实例,并将实例添加到根文件系统树状层次结构中。dentry结构体定义在/include/linux/dcache.h头文件:
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; //目录项标志
seqcount_t d_seq; //per dentry seqlock
struct hlist_bl_node d_hash; //散列表节点成员,将实例链表入管理散列表
struct dentry *d_parent; //指向父目录项
struct qstr d_name; //目录项名称,文件名称
struct inode *d_inode; //指向表示文件的inode实例
unsigned char d_iname[DNAME_INLINE_LEN]; //用于存放较短的目录项名称
/* Ref lookup also touches following */
struct lockref d_lockref; //锁和引用计数
const struct dentry_operations *d_op; //目录项操作结构指针
struct super_block *d_sb; //指向所在具体文件系统的超级块实例
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; //指向具体文件系统定义的私有数据结构
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; //链接同一父目录项下的兄弟目录项
struct list_head d_subdirs; //链接子目录项
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; //用于在inode中链接dentry,链接文件具有多个目录项
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
};
dentry_operations结构体包含特定于具体文件系统的目录项操作,须由具体文件系统类型代码实现。dentry结构体中d_op成员指向dentry_operations结构体实例,实例来源于超级块super_block结构体s_d_op成员。在挂载文件系统时,dentry_operations实例由具体文件系统的挂载函数赋予超级块实例,再由超级块实例传递给其下目录项dentry实例。dentry_operations结构体定义在/include/linux/dcache.h头文件内:
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int); //用于网络文件系统,检查释放仍然有效
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *); //计算目录项散列值
int (*d_compare)(const struct dentry *, //比较目录项名称
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *); //引用计数小于等于1时调用d_delete()
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *); //最后销毁目录项前调用d_release()
void (*d_prune)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *); //释放目录项对应的inode实例
char *(*d_dname)(struct dentry *, char *, int); //设置目录项名称
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool);
struct dentry *(*d_real)(struct dentry *, const struct inode *,
unsigned int);
} ____cacheline_aligned;
dentry_operations结构体中函数功能简要说明如下(/Documentation/filesystems/vfs.txt):
同上面inode的初始化一样,内核在打开文件时,会为各路径分量创建对应的dentry实例,新创建的实例首先添加到根文件系统层次结构中,在为dentry实例创建并关联对应的inode实例时会将其添加到全局散列表dentry_hashtable。当dentry实例被释放时被添加到超级块 LRU链表。在关闭文件时,内核会释放dentry实例。dentry实例管理结构如下图所示:
进程task_struct结构体中fs成员表示了进程可见根文件系统的根节点及当前工作目录:
task_struct{
...
struct fs_struct *fs; /*进程目录信息*/
struct files_struct *files; /*进程打开文件信息*/
...
}
fs_struct结构体定义在/include/linux/fs_struct.h头文件:
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd;
};
fs_struct结构体主要成员:
struct path {
struct vfsmount *mnt; /*目录项所在文件系统挂载信息,vfsmount.mnt*/
**struct dentry *dentry**; /*目录项指针*/
};
内核文件由根文件系统统一管理,进程访问文件前,需要先通过打开文件操作建立与内核文件的关联。进程通过**file结构体建立进程与内核文件(inode)**之间的关联,如下图所示。进程task_struct结构体管理着file实例的指针数组(由files_struct结构体表示),通过file建立与内核文件inode之间的关联,数组项索引值即进程文件描述符。进程与内核文件建立关联后,inode指向的file_operations实例将赋予file实例,进程通过此file_operations实例中的函数操作文件。
files成员指向files_struct结构体实例,结构体定义在/include/linux/fdtable.h头文件
struct files_struct {
atomic_t count; /*实例引用计数*/
bool resize_in_progress; /*进程正在扩展fdtab实例*/
wait_queue_head_t resize_wait; /*进程等待队列,等待fdtab扩展的进程*/
struct fdtable __rcu *fdt; /*fdtable结构体指针,初始值指向fdtab成员*/
struct fdtable fdtab; /*fdtable结构体成员*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd; /*下一个打开文件的文件描述符,初始值为0,每次分配描述符后设置*/
unsigned long close_on_exec_init[1]; /*执行execve()系统调用时关闭文件的位图*/
unsigned long open_fds_init[1]; /*打开文件位图*/
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; /*打开文件file指针数组*/
};
进程打开的文件由file结构体表示,结构体定义在/include/linux/fs.h头文件:
struct file {
union {
struct llist_node fu_llist; /*单链表成员*/
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path; /*文件路径信息*/
struct inode *f_inode; /*指向内核文件inode实例*/
const struct file_operations *f_op;
/*文件操作结构指针,通常在打开文件时设为inode->i_fop*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags; /*open()系统调用传递的flags标记参数*/
fmode_t f_mode; /*标记进程以何种模式打开文件*/
struct mutex f_pos_lock;
loff_t f_pos; /*文件当前读写位置,相对于文件开头处的字节偏移量*/
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra; /*文件预读结构体,见第11章*/
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
void *private_data; /*文件私有数据指针,例如设备文件指向驱动程序定义的数据结构*/
#ifdef CONFIG_EPOLL
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif
struct address_space *f_mapping; /*文件地址空间指针,用于具有外部存储介质的文件*/
} __attribute__((aligned(4)));
通过以上的数据结构,我们可以得出一个进程访问一个文件的基本流程如下
我们通过虚拟文件系统的重要的数据结构,可以得出虚拟文件系统的结构如下图所示,
https://www.starlab.io/blog/introduction-to-the-linux-virtual-filesystem-vfs-part-i-a-high-level-tour
linux内核解析
https://www.kernel.org/doc/html/latest/filesystems/vfs.html