《LKD3粗读笔记》(13)虚拟文件系统
- 虚拟文件系统,简称VFS,是内核的子系统,为用户空间程序提供了文件系统相关的接口。系统中所有文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。通过虚拟文件系统,程序可以利用标准的UNIX文件系统调用对不同介质上的不同文件系统进行读写操作。下图使用
cp
命令从ext3文件系统格式的硬盘拷贝数据到ext2文件系统格式的可移动磁盘上。两种不同的文件系统,两种不同的介质,连接到同一个VFS上。
1、通用文件系统
- VFS使得用户可以直接使用
open()
、write()
和read()
这样的系统调用而无需考虑具体文件系统实际物理介质。 - 系统调用还可以在这些不同的文件系统和介质之间执行。
- 正是由于包括
Linux
在内的现代操作系统引入了抽象层,通过虚拟接口访问文件系统,才使得这种协作性和通用性成为可能。 - 新的文件系统和新种类的存储介质都能找到进入Linux之路,程序无需重写,甚至无需重新编译。
2、文件系统抽象层
- 之所以可以使用这种通用接口对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层,该抽象层使Linux能够支持各种文件系统,即便是它们在功能和行为上存在很大差别。为了支持多文件系统,VFS提供了一个通用文件系统模型,该模型囊括了我们所能想到的文件系统的常用功能和行为。
- VFS抽象层之所以能链接各种各样的文件系统,是因为它定义了所有文件系统都支持的基本的、概念上的接口和数据结构。同时实际文件系统也将自身的诸如如何打开文件、目录是什么等概念在形式上与VFS定义保持一致。因为实际文件系统的代码在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS层和内核的其他部分看来,所有的文件系统都是相同的。
- 内核通过抽象层能够方便、简单地支持各种类型的文件系统,实际文件系统通过编程提供VFS所期望的抽象接口和数据结构,这样,内核就可以毫不费力地和任何文件系统系统工作。并且这样提供给用户空间的接口,也可以和任何文件系统无缝连接在一起,完成实际工作。
- 我们在用户空间调用
write()
方法,会首先在VFS找到一个通用系统调用sys_write()
处理,sys_wirte()
会找到所在的文件系统给出的写操作,然后通过该写操作,往物理介质上写数据。
3、Unix文件系统
- Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点。
- 从本质上说文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。在Unix中,文件系统被安装在一个特定的安装点上,所有的已安装文件系统都作为根文件系统树的枝叶出现在系统中。
- 文件其实可以看做是一个有序字节串,字节串中第一个字节是文件的头,最后一个字节时文件的尾。文件通过目录组织起来,文件目录好比一个文件夹,用来容纳相关文件。因为目录也可以包含子目录,所以目录可以层层嵌套,形成文件路径。路径中的每一部分都被称作目录项。
/home/wolfman/butter
的根目录是/
,目录home
、wolfman
和文件butter
都是目录项。在Unix中,目录属于普通文件,它列出包含在其中的所有文件。由于VFS把目录当做文件对待,所以可以对目录和文件执行相同的操作。(注意:目录是目录项的集合,可以类比于书本中的目录) - Unix系统将文件的相关信息和文件本身这两个概念加以区分,文件的相关信息被称为索引节点(inode)。
4、VFS对象及其数据结构
-
VFS有四个主要的数据类型:
- 超级块对象,代表一个具体的已经安装的文件系统;
- 索引节点对象,代表一个文件;
- 目录项对象,代表一个目录项,路径的组成部分;
- 文件对象,代表进程打开的文件;
-
每个主要对象都包含一个操作对象,这些操作对象都包含了内核针对主要对象可以使用的方法:
super_operations
对象,包含内核针对特定文件系统所能调用的方法,例如,write_inode()
和sync_fs()
等;inode_operations
对象,包含内核针对特定文件所能调用的方法,例如,create()
和link()
等;dentry_operations
对象,包含内核针对特定目录所能调用的方法,例如,d_compare()
和d_delete()
等;file_operations
对象,包含进程针对已打开文件所能调用的方法,例如,read()
和write()
;
-
VFS所包含的对象远远多于上面这几种对象。例如,
file_system_type
结构体表示每个注册的文件系统,描述了文件系统及其性能;vfsmount
结构体表示每一个安装点,描述了位置和安装标志等。
5、超级块
-
各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。
-
对于并非基于磁盘的文件系统(如基于内存的文件系统–sysfs),它们会在使用现场创建超级块并将其保存到内存中。
-
超级块对象由
super_block
结构体表示,定义在文件<linux/fs.h>
中,如下(选自kernel版本3.10):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; /* Max file size 文件大小上限*/ 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_magic; /* 文件系统的幻数 */ struct dentry *s_root; /* 目录挂载点 */ 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; /* 扩展的属性操作 */ struct list_head s_inodes; /* all inodes inodes 链表 */ struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting 匿名目录项 */ #ifdef CONFIG_SMP struct list_head __percpu *s_files; #else struct list_head s_files; /* 被分配文件链表 */ #endif struct list_head s_mounts; /* list of mounts; _not_ for fs use */ /* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */ struct list_head s_dentry_lru; /* unused dentry lru 未被使用目录项链表 */ int s_nr_dentry_unused; /* # of dentry on lru 链表中目录项的数目 */ /* s_inode_lru_lock protects s_inode_lru and s_nr_inodes_unused */ spinlock_t s_inode_lru_lock ____cacheline_aligned_in_smp; struct list_head s_inode_lru; /* unused inode lru */ int s_nr_inodes_unused; /* # of inodes on lru */ struct block_device *s_bdev; /* 相关的块设备 */ struct backing_dev_info *s_bdi; struct mtd_info *s_mtd; /* 存储磁盘信息 */ struct hlist_node s_instances; /* 该类型文件系统 */ 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; /* Filesystem private 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; /* default d_op for dentries */ /* * Saved pool identifier for cleancache (-1 means none) */ int cleancache_poolid; struct shrinker s_shrink; /* per-sb shrinker handle */ /* Number of inodes with nlink == 0 but still referenced */ atomic_long_t s_remove_count; /* Being remounted read-only */ int s_readonly_remount; };
-
创建、管理和撤销超级块对象的代码位于文件
fs/super.c
中。 -
超级块对象通过
alloc_super()
函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并将其信息填充到内存中的超级块对象中。
6、超级块操作
-
超级块对象中最重要的一个域就是
s_op
,它指向超级块的操作函数表。 -
超级块操作函数表由
super_oprations
结构体表示,定义在文件<linux/fs.h>
中,如下(选自kernel版本3.10): -
当文件系统需要对其超级块执行操作时,首先要在超级块对象中寻找需要的操作方法:
struct super_operations { struct inode *(*alloc_inode)(struct super_block *sb); void (*destroy_inode)(struct inode *); void (*dirty_inode) (struct inode *, int flags); int (*write_inode) (struct inode *, struct writeback_control *wbc); int (*drop_inode) (struct inode *); void (*evict_inode) (struct inode *); void (*put_super) (struct super_block *); int (*sync_fs)(struct super_block *sb, int wait); int (*freeze_fs) (struct super_block *); int (*unfreeze_fs) (struct super_block *); int (*statfs) (struct dentry *, struct kstatfs *); 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 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); #endif int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t); int (*nr_cached_objects)(struct super_block *); void (*free_cached_objects)(struct super_block *, int); };
-
下表解释
super_oprations
中超级块操作函数的用法:函数方法 描述 struct inode *alloc_inode(struct super_block *sb) 在给定的超级块下创建和初始化一个新的索引节点对象 void destroy_inode(struct inode *inode) 用于释放给定的索引节点 void dirty_inode(struct inode *inode) VFS在索引节点脏(被修改)时会调用此函数。日志文件系统(如ext3和ext4)执行该函数进行日志更新 void write_inode(struct inode *inode,int wait) 用于将给定的索引节点写入磁盘。wait参数指明写操作是否需要同步 void drop_inode(struct inode *inode) 在最后一个指向索引节点的引用被释放后,VFS会调用该函数。VFS只需要简单地删除这个索引节点后,普通的Unix文件系统就不会定义这个函数了 void delete_inode(struct inode *inode) 用于从磁盘上删除给定的索引节点 void put_super(struct super_block *sb) 在卸载文件系统时由VFS调用,用来释放超级块。调用者必须一直持有s_lock锁 void write_super(struct super_block *sb) 用给定的超级块更新磁盘上的超级块。VFS通过该函数对内存中的超级块和磁盘中的超级块进行同步。调用者必须一直持有s_lock锁 int sync_fs(struct super_block *sb,int wait) 使文件系统的数据元与磁盘上的文件系统同步。wait参数指定操作是否同步 void write_super_lockfs(struct super_block *sb) 首先禁止对文件系统做改变,再使用给定的超级块更新磁盘上的超级块。目前LVM(逻辑卷标管理)会调用该函数 void unlockfs(struct super_block *sb) 对文件系统解除锁定,它是write_super_lockfs()的逆操作 int statfs(struct super_block *sb, int *flags,char *data) VFS通过调用该函数获取文件系统状态。指定文件系统相关的统计信息将放置再statfs中 int remount_fs(struct super_block *sb,int *flags,char *data) 当指定新的安装选项重新安装文件系统时,VFS会调用该函数。 调用者必须一直持有s_lock锁 void clear_inode(struct inode *inode) VFS调用该函数释放索引节点,并情况包含相关数据的所有页面 void umount_begin(struct super_block *sb) VFS调用该函数中断安装操作。该函数被网络文件系统使用,如NFS -
以上所有函数都是由VFS在进程上下文中调用。除了
dirty_inode()
其他函数在必要时都可以阻塞。 -
这其中一些函数是可选的。在超级块操作表中,文件系统可以将不需要的函数指针设置成
NULL
。 -
如果VFS发现操作函数指针是
NULL
,那它要么就会调用通用函数执行相应操作,要么什么也不做,如果选择取决于具体操作。
7、索引节点对象
- 索引节点对象包含了内核在操作文件或目录时需要的全部信息。
- 索引节点对象必须在内存中创建,以便于文件系统使用。
- 索引节点对象由inode结构体表示,定义在文件<linux/fs.h>中,如下(选自kernel版本3.10):
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; /* 相关的地址映射 */ #ifdef CONFIG_SECURITY void *i_security; /* 安全模块 */ #endif /* Stat data, not accessed from path walking */ unsigned long i_ino; /* 节点号 */ /* * 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; /* 最后改变时间 */ 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; /* 对 i_size 进行串行计数 */ #endif /* Misc */ unsigned long i_state; /* 状态标志 */ struct mutex i_mutex; unsigned long dirtied_when; /* jiffies of first dirtying 第一次弄脏数据的时间 */ struct hlist_node i_hash; /* 散列表 */ struct list_head i_wb_list; /* backing dev IO list */ struct list_head i_lru; /* inode LRU list */ struct list_head i_sb_list; /* 超级块链表 */ union { struct hlist_head i_dentry; /* 目录项链表 */ struct rcu_head i_rcu; }; u64 i_version; /* 版本号 */ atomic_t i_count; /* 引用计数 */ atomic_t i_dio_count; atomic_t i_writecount; /* 写者计数 */ const struct file_operations *i_fop; /* former ->i_op->default_file_ops 缺省的索引节点操作 */ struct file_lock *i_flock; /* 文件锁链表 */ struct address_space i_data; /* 设备地址映射 */ #ifdef CONFIG_QUOTA struct dquot *i_dquot[MAXQUOTAS]; /* 索引节点的磁盘限额 */ #endif struct list_head i_devices; /* 块设备链表 */ union { struct pipe_inode_info *i_pipe; /* 管道信息 */ struct block_device *i_bdev; /* 块设备驱动 */ struct cdev *i_cdev; /* 字符设备驱动 */ }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct hlist_head i_fsnotify_marks; #endif #ifdef CONFIG_IMA atomic_t i_readcount; /* struct files open RO */ #endif void *i_private; /* fs or device private pointer fs私有指针 */ };
- 一个索引节点代表文件系统中(索引节点仅当文件被访问时,才在内存中创建)的一个文件,也可以是设备或者管道这样的特殊文件。因此索引节点结构体中有一些和特殊文件相关的项。如上述的
i_pipe
、i_bdev
和i_cdev
三个指针代表三个不同的特殊文件,它们被存放在一个联合体(union)中,因为一个给定的索引节点每次只能表示三者之一或者三者均不。
8、索引节点操作
-
索引节点对象中的
inode_operations
项非常重要,描述了VFS用以操作索引节点对象的所有方法,这些方法由文件系统实现。 -
inode_operations
结构体定义在文件<linux/fs.h>
中,如下(选自kernel版本3.10):struct inode_operations { struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); void * (*follow_link) (struct dentry *, struct nameidata *); int (*permission) (struct inode *, int); struct posix_acl * (*get_acl)(struct inode *, int); int (*readlink) (struct dentry *, char __user *,int); void (*put_link) (struct dentry *, struct nameidata *, void *); int (*create) (struct inode *,struct dentry *, umode_t, bool); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct inode *,struct dentry *,const char *); int (*mkdir) (struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *); int (*setattr) (struct dentry *, struct iattr *); int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *); int (*setxattr) (struct dentry *, const char *,const void *,size_t,int); ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*removexattr) (struct dentry *, const char *); int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len); int (*update_time)(struct inode *, struct timespec *, int); int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode, int *opened); } ____cacheline_aligned;
-
上述这些接口由各种函数组成,在给定的节点上,可能由VFS执行这些函数,也可能由具体的文件系统执行。
-
下表解释
inode_operations
中索引节点操作函数的用法。函数方法 描述 int create(struct inode *dir,struct dentry *dentry, int mode) VFS通过系统调用create()和open()来调用该函数,从而为dentry对象创建一个新的索引节点。在创建时使用mode指定的初始模式 struct dentry *lookup(struct inode *dir,strcut dentry *dentry) 读函数在特定目录中寻找索引节点,该索引节点要对应于dentry中给出的文件名 int link(struct dentry *old_dentry,struct inode *dir,struct dentry *dentry) 该函数被系统调用link()调用,用来创建硬链接。硬链接名称由dentry参数指定,连接对象是dir目录中old_dentry目录项所代表的文件 int unlink(struct inode *dir,struct dentry *dentry) 该函数被系统调用unlink()调用,从目录dir中删除由目录项dentry指定的索引节点对象 int symlink(struct inode *dir,struct dentry *dentry,const char *symname) 该函数被系统调用symlink()调用,创建符号链接。该符号链接名称由symname指定,链接对象是dir目录中的dentry目录项 int mkdir(struct inode *dir,struct dentry *dentry,int mode) 该函数被系统调用mkdir()调用,创建一个新目录。创建时使用mode指定的初始模式 int rmdir(struct inode *dir,struct dentry *dentry) 该函数被系统调用rmdir()调用,删除dir目录中的dentry目录项代表的文件 int mknod(struct inode *dir,struct dentry *dentry,int mode, dev_t rdev) 该函数被系统调用mknod()调用,创建特殊文件(设备文件、命名管道或套接字)。要创建的文件放在dir目录中,其目录项为dentry,关联的设备为rdev,初始权限由mode指定 int rename(struct inode *old_dir,struct dentry *old_dentry,struct inode *new_dir,struct dentry *new_dentry) VFS调用该函数来移动文件。文件原路径在old_dir目录中,源文件由old_dentry目录项指定,目标路径在new_dir目录中,目标文件由new_dentry指定 int readlink(struct dentry *dentry,char *buffer,int buflen) 该函数被系统调用readlink()调用,拷贝数据到特定的缓冲buffer中,拷贝的数据来自dentry指定的符号链接,拷贝大小最大可达buflen字节 int follow_link(struct dentry *dentry,struct nameidata *nd) 该函数由VFS调用,从一个符号链接查找它指向的索引节点。由dentry指向的链接被解析,其结果存放在由nd指定的nameidata结构体中 int put_link(struct dentry *dentry,struct nameidata *nd) 在follow_link()调用后,该函数由VFS调用进行清除工作。 void truncate(struct inode *inode) 该函数由VFS调用,修改文件的大小。在调用前,索引节点的i_size项必须设置为预期的大小 int permission(struct inode *inode, int mask) 该函数用来检查给定的inode所代表的文件是否允许特定的访问模式 int setattr(struct dentry *dentry,struct iattr *attr) 该函数被notify_change()调用,在修改索引节点后,通知发生了“改变事件” int getattr(struct vfsmount *mnt,struct dentry *dentry) 在通知索引节点需要从磁盘中更新时,VFS会调用该函数。扩展属性允许key/vlaue这样的一对值与文件相关联 int setxattr(struct dentry *dentry,const char *name,const void *value,size_t size,int flags) 该函数由VFS调用,给dentry指定的文件设置扩展属性,属性名为name,值为value ssize_t getxattr(struct dentry *dentry,const char *name,void *value,size_t size) 该函数由VFS调用,向value中拷贝给定文件的扩展属性name对应的数值 ssize_t listxattr(struct dentry *dentry,char *list,size_t size) 该函数将特定文件的所有属性列表拷贝到一个缓冲列表中 int removexattr(struct dentry *dentry,const char *name) 该函数从给定文件中删除指定的属性
9、目录项对象
- VFS把目录当作文件对待,路径中的每个组成部分都由一个索引节点对象表示。
- 目录项对象由
dentry
结构体表示,定义在文件<linux/dcache.h>
中,如下(选自kernel版本3.10):struct dentry { /* RCU lookup touched fields */ unsigned int d_flags; /* protected by d_lock 目录项标识 */ seqcount_t d_seq; /* per dentry seqlock */ struct hlist_bl_node d_hash; /* lookup hash list */ struct dentry *d_parent; /* parent directory 父目录的目录项对象 */ struct qstr d_name; /* 目录项名称 */ struct inode *d_inode; /* 相关联的索引节点 Where the name belongs to - NULL is * negative */ unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ /* Ref lookup also touches following */ unsigned int d_count; /* protected by d_lock 使用记数 */ spinlock_t d_lock; /* per dentry lock 单目录项锁 */ const struct dentry_operations *d_op; /* 目录项操作指针 -- 重点域 */ struct super_block *d_sb; /* The root of the dentry tree 文件的超级块 */ unsigned long d_time; /* used by d_revalidate 重置时间 */ void *d_fsdata; /* fs-specific data 文件系统特有数据 */ struct list_head d_lru; /* LRU list 未使用的链表 */ /* * d_child and d_rcu can share memory */ union { struct list_head d_child; /* child of parent list 目录项内部形成的链表 */ struct rcu_head d_rcu; /* RCU 加锁 */ } d_u; struct list_head d_subdirs; /* our children 子目录链表 */ struct hlist_node d_alias; /* inode alias list 索引节点别名链表 */ };
- 与前面的两个对象不同,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。而且由于目录项对象并非真正保存在磁盘上,所以目录项结构体没有是否被修改的标志(也就是是否为脏、是否需要写回磁盘的标志)
- 目录项状态——被使用、未被使用和负使用(无效目录项)
- 被使用:一个被使用的目录项对应一个有效的索引节点(即
d_inode
指向相应的索引节点)并且表明该对象存在一个或多个使用者(即d_count
为正值)。一个目录项处于被使用状态,则意味着它正被VFS使用并且指向有效的数据,因此不能被丢弃。 - 未被使用:一个未被使用的目录项对应一个有效的索引节点(即
d_inode
指向一个索引节点),但是应指明VFS当前并未使用它(d_count
为0)。可以撤销未使用的目录项。 - 负使用:一个负状态的目录项没有对应的有效索引节点(即
d_inode
为NULL
),因为索引节点已被删除,或路径不再正确,但是目录项仍然保留,以便快速解析以后的路经查询。可以撤销。
- 被使用:一个被使用的目录项对应一个有效的索引节点(即
- 目录项缓存
- 如果VFS层遍历路径名中的所有的元素并将它们逐个的解析成目录项对象,还要到达最深层目录,将是一件非常费力的工作,会浪费大量的时间,因此内核将目录项对象缓存在目录项缓存(dcache)中。
- 目录项缓冲包括如下三个主要部分:
- “被使用的”目录项链表。该链表通过索引节点对象中的
i_dentry
项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以就可能有多个目录项对象,因此用一个链表来连接它们。 - “最近被使用的”双向链表。该链表含有未被使用的和负状态的目录项对象(该链总是在头部插入目录项)。
- 散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象。
- “被使用的”目录项链表。该链表通过索引节点对象中的
- 散列表由数组
dentry_hashtable
表示,其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。 - 实际的散列值由
d_hash()
函数计算,它是内核提供给文件系统的唯一的一个散列函数。 - 查找散列表要通过
d_lookup()
函数,如果该函数在dcache
中发现了与其相匹配的目录项对象,则匹配的对象被返回,否则返回NULL
指针。
10、目录项操作
-
dentry_operation
结构体指明了VFS操作目录项的所有方法。该结构定义在文件<linux/dcache.h>
中,如下(选自kernel版本3.10):struct dentry_operations { int (*d_revalidate)(struct dentry *, unsigned int); int (*d_weak_revalidate)(struct dentry *, unsigned int); int (*d_hash)(const struct dentry *, const struct inode *, struct qstr *); int (*d_compare)(const struct dentry *, const struct inode *, const struct dentry *, const struct inode *, unsigned int, const char *, const struct qstr *); int (*d_delete)(const struct dentry *); void (*d_release)(struct dentry *); void (*d_prune)(struct dentry *); void (*d_iput)(struct dentry *, struct inode *); char *(*d_dname)(struct dentry *, char *, int); struct vfsmount *(*d_automount)(struct path *); int (*d_manage)(struct dentry *, bool); } ____cacheline_aligned;
-
下表解释
inode_operations
中索引节点操作函数的用法:函数方法 描述 int d_revalidate(struct dentry *dentry, struct nameidata) 该函数判断目录对象是否有效。VFS准备从dcache中使用一个目录项时,会调用该函数。大部分文件系统将该方法置位NULL,因为它们认为dcache中目录项对象总是有效的 int d_hash(struct dentry *dentry,struct qstr *name) 该函数为目录项生成散列值,当目录项需要加入到散列表中时,VFS调用该函数 int d_compare(struct dentry *dentry,struct qstr *name1,struct qstr *name2) VFS调用该函数来比较name1和name2这两个文件名。多数文件系统使用VFS默认的操作,仅仅作字符串比较。注意使用该函数时需要加dcache_lock锁 int d_delete(struct dentry *dentry) 当目录项对象的d_count计数值等于0时,VFS调用该函数。注意使用该函数需要加dcache_lock锁和目录项的d_lock void d_release(struct dentry *dentry) 当目录项对象将要被释放时,VFS调用该函数,默认情况下,它什么也不做。 void d_iput(struct dentry *dentry,struct indoe *inode) 当一个目录项对象丢失了其相关的索引节点时(也就是磁盘索引节点被删除了),VFS调用该函数。默认清空下VFS会调用iput()函数释放索引节点。如果文件系统重载了该函数,那么除了执行文件系统特殊的工作外,还必须调用iput()函数
11、文件对象
- 文件对象表示进程已打开的文件。
- 文件对象是已打开的文件在内存中的表示。
- 文件对象仅仅在进程观点上表示已打开的实际文件,反过来指向目录项对象(反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。
- 虽然一个文件对应的文件对象不是唯一的(因为多个进程可以同时打开和操作同一个文件),但对应的索引节点和目录项对象无疑是唯一的。
- 文件对象由file结构体表示,定义在文件
<linux/fs.h>
中,如下(选自kernel版本3.10):struct file { /* * fu_list becomes invalid after file_free is called and queued via * fu_rcuhead for RCU freeing */ union { struct list_head fu_list; /* 文件对象链表 */ struct rcu_head fu_rcuhead; /* 释放之后的 RCU 链表 */ } f_u; struct path f_path; /* 包含目录项 */ #define f_dentry f_path.dentry struct inode *f_inode; /* cached value */ const struct file_operations *f_op; /* 文件操作表 -- 重要域 */ /* * Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR. * Must not be taken from IRQ context. */ spinlock_t f_lock; /* 单个文件结果锁 */ #ifdef CONFIG_SMP int f_sb_list_cpu; #endif atomic_long_t f_count; /* 文件对象的使用计数 */ unsigned int f_flags; /* 当打开文件时所指定的标志 */ fmode_t f_mode; /* 文件的访问模式 */ loff_t f_pos; /* 文件当前的位移量(文件指针) */ struct fown_struct f_owner; /* 拥有者通过信号量进行异步 I/O 数据的传送 */ const struct cred *f_cred; /* 文件的信任状 */ struct file_ra_state f_ra; /* 预读状态 */ u64 f_version; /* 版本号 */ #ifdef CONFIG_SECURITY void *f_security; /* 安全模块 */ #endif /* needed for tty driver, and maybe others */ void *private_data; /* tty 设备驱动的钩子 */ #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; /* 事件池链表 */ struct list_head f_tfile_llink; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; /* 页缓存映射 */ #ifdef CONFIG_DEBUG_WRITECOUNT unsigned long f_mnt_write_state; /* 调试状态 */ #endif };
- 类似于目录项对象,文件对象实际上没有对应的磁盘数据。所以在结构体中没有代表其对象是否为脏,是否需要写回磁盘的标志。
- 文件对象通过
f_dentry
指针指向相关的目录项对象。目录项会指向相关的索引节点,索引节点会记录文件是否是脏的。
12、文件操作
-
文件对象的操作由
file_operations
结构体表示,定义在文件<linux/fs.h>
中,如下(选自kernel版本3.10):struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); int (*show_fdinfo)(struct seq_file *m, struct file *f); };
- 上述操作与系统调用很类似,这些操作是标准Unix系统调用的基础。
- 具体的文件系统可以为每一种操作做专门的实现,或者如果存在通用操作,也可以直接使用通用操作。
-
下表解释
file_operations
中文件对象操作函数的用法:函数方法 描述 loff_t lleek(struct file *file,loff_t offset,int origin) 该函数用于更新偏移量指针,由系统调用lleek()调用它 ssize_t read(struct file *file,char *buf,size_t count,loff_t *offset) 该函数从给定文件的offset偏移处读取count字节的数据到buf中,同时更新文件指针。由系统调用read()调用它 ssize_t aio_read(struct kiocb *iocb,char *buf,size_t count,loff_t offset) 该函数从iocb描述的文件里,以同步方式读取count字节的数据到buf中。由系统调用aio_read()调用它 ssize_t write(struct file *file,const ,char *buf,size_t count,loff_t *offset) 该函数从给定的buf中取出count字节的数据,写入给定文件的offset偏移处,同时更新文件指针。由系统调用write()调用它 ssize_t aio_write(struct kiocb *iocb,const, char *b uf,size_t count,loff_t offset) 该函数以同步方式从给定的buf中取出count字节的数据,写入由iocb描述的文件中。由系统调用aio_write()调用它 int readdir(struct file *file,void *dirrent,filldir_t filldir) 该函数返回目录列表下的下一个目录。由系统调用readdir()调用它 unsigned int poll(struct file *file,struct poll_table_sturct *poll_table) 该函数睡眠等待给定文件活动。由系统调用poll()调用它 int ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg) 该函数用来给设备发送命令参数对。当文件是一个被打开的设备节点时,可以通过它进行设置操作。由系统调用ioctl()调用它,调用者必须持有BKL int unlocked_ioctl(struct file *file,unsigned int cmd,unsigned long arg) 其实现与ioctl()有类似功能,只不过不需要调用者持有BKL。如果用户空间调用ioctl()系统调用,VFS便可以调用unlock_ioctl()(凡是ioctl()出现的场所),因此文件系统只需要实现其中一个,一般优先实现unlocked_iooctl() int compat_ioctl(struct file *file,unsinged int cmd,unsigned long arg) 该函数是ioctl()函数可移植变种,被32位应用程序用在64位系统上。可以不持有BKL int map(struct file *file,struct vm_area_struct *vma) 该函数将给定的文件映射到指定的地址空间上。由系统调用mmap()调用它 int open(struct inode *inode,struct file *file) 该函数创建一个新的文件对象,并将它和相应的索引节点对象关联起来。由系统调用open()调用它 int flush(struct file *file) 当已打开文件的引用计数减少时,该函数被VFS调用,它的作用根据具体文件系统而定 int release(struct inode *inode,struct file *file) 当文件的最后一个引用被注销时,该函数被VFS调用,作用根据具体文件系统而定 int fsync(struct file *file,struct dentry *dentry,int datasync) 将给定文件的所有被缓冲数据写回磁盘。由系统调用fsync()调用它 int aio_fsync(struct kiocb *iocb,int datasync) 将iocb描述的文件的所有被缓存数据写回到磁盘。由系统调用aio_fsync()调用它 int fasync(int fd,struct file *file,int on) 该函数用于打开或关闭异步 I/O的通告信号(即同步) int lock(struct file *file,int cmd,struct file_lock *lock) 该函数用于给指定文件上锁 ssize_t readv(struct file *file,const struct iovec *vector,unsigned long count,loff_t *offset) 该函数从给定文件上读取数据,并将其写入由vector描述的count个缓冲中区,同时增加文件的偏移量。由系统调用readv()调用它 ssize_t writev(struct file *fiel,const struct iovec *vector,unsigned long count,loff_t *offset) 该函数将由vector描述的count个缓冲中的数据写入到由file指定的文件中去,同时减小文件的偏移量。由系统调用writev()调用它 ssize_t sendifle(struct file *file,loff_t *offset,size_t size,read_actor_t actor,void *target) 该函数用于从一个文件拷贝数据到另一个文件中,它执行的拷贝操作完全在内核中完成,避免向用户空间进行不必要的拷贝,由系统调用sendfile()调用它 ssize_t sendpage(struct file *file,struct page *page,int offset,size_t size,loff_t *pos,int more) 该函数用来从一个文件向另一个文件发送数据 unsigned long get_unmapped_are(struct file *file,unsigned long addr,unsigned long len,unsigned long offset,unsigned long flags) 该函数用于获取未使用的地址空间来映射给定的文件 int check_flags(int flags) 当给出SETFL命令时候,这个函数用来检查传递给fcntl()系统调用的flags的有效性 int flock(struct file *filp,int cmd,struct file_lock *fl) 这个函数用来实现flock()系统调用,该调用提供忠告锁
13、和文件系统相关的数据结构
file_system_type
用来描述各种特定文件系统类型,如ext3
,ext4
或UDF
;vfsmount
用来描述一个安装文件系统的实例。- 因为Linux支持众多不同的文件系统,所以内核必须由一个特殊的结构来描述每种文件系统的功能和行为。
file_system_type
结构体被定义在<linux/fs.h>
中,如下(选自kernel版本3.10):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_USERNS_DEV_MOUNT 16 /* A userns mount does not imply MNT_NODEV */ #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; 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
结构。 - 注意:当文件系统被实际安装时,将有一个
vfsmount
结构体在安装点被创建。该结构体用来代表文件系统的实例,也就是代表要给安装点。 vfsmount
结构体被定义在<linux/mount.h>
中,如下(选自kernel版本3.10):struct vfsmount { struct dentry *mnt_root; /* root of the mounted tree 该文件系统的根目录项 */ struct super_block *mnt_sb; /* pointer to superblock 该文件系统的超级块 */ int mnt_flags; /* 安装标志 */ };
mnt_flags
存储了文件系统在安装时指定的标志信息,如下:
14、和进程相关的数据结构
- 有三个数据结构将VFS层和系统的进程紧密联系在一起,它们分别是:
files_struct
、fs_struct
和namespace
结构体。 files_struct
结构体定义在文件<linux/fdtable.h>
中,该结构体由进程描述符中的files
目录项指向,所有与单个进程(per-process)相关的信息都包含在其中,如下(选自kernel版本3.10):/* * Open file table structure */ struct files_struct { /* * read mostly part */ atomic_t count; /* 结构的使用计数 */ struct fdtable __rcu *fdt; /* 指向其他fd表的指针 */ struct fdtable fdtab; /* 基fd表 */ /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; int next_fd; /* 缓存下一个可用的fd */ unsigned long close_on_exec_init[1]; /* exec()时关闭的文件描述符链表 */ unsigned long open_fds_init[1]; /* 打开的文件描述符链表 */ struct file __rcu * fd_array[NR_OPEN_DEFAULT]; /* 缺省的文件对象数组,指向已经打开的文件对象 */ };
fs_struct
由进程描述符的fs
域指向,它包含系统文件和进程相关的信息,如下:struct fs_struct { int users; /* 用户数目 */ spinlock_t lock; /* 保护该结构体的锁 */ seqcount_t seq; int umask; /* 掩码 */ int in_exec; /* 当前正在执行的文件 */ struct path root, pwd; /* 根目录路径和当前工作目录的路径 */ };
namespace
结构体由进程描述符中mmt_namespace
域指向,2.4内核版本后,单进程命名空间被加入内核中,它使得每个进程在系统中都能看到唯一的安装文件系统——不仅是唯一的根目录,而是唯一的文件系统层次结构。
书中记录的是<linux/mmt_namespace.h>
中,在kernel3.10版本中没有找到,是另外一种形式,这里笔者不确定是不是对应的如下:
kernel/fs/mount.h
struct mnt_namespace {
atomic_t count; /* 结构的使用计数 */
unsigned int proc_inum;
struct mount * root; /* 根目录的安装点对象 */
struct list_head list; /* 安装点链表:连接已安装文件系统的双向链表,包含的元素组成了全体命名空间 */
struct user_namespace *user_ns;
u64 seq; /* Sequence number to prevent loops */
wait_queue_head_t poll; /* 轮询的等待队列 */
int event; /* 事件计数 */
};
- 上述这些数据结构都是通过进程描述符连接起来的。
- 对多数进程来说,它们的描述符都指向唯一的
files_struct
和fs_struct
结构体。但是,对于那些使用克隆标志CLONE_FILES
或CLONE_FS
创建的进程,会共享这两个结构体。所以多个进程描述符可能指向同一个files_struct
或fs_struct
结构体。 - 每个结构体都维护一个
count
域作为引用计数,它防止进程正使用该结构时,该结构体被撤销。 namespace
结构体的使用方法和前两种结构体完全不同。默认情况下,所有的进程共享同样的命名空间(它们都从相同的挂载表中看到同一个文件系统层次结构)。- 只有在进行
clone()
操作时使用CLONE_NEWS
标志,才会给进程一个唯一的命名空间结构体的拷贝。 - 因为大多数进程都不提供这个标志,所有进程都继承其父进程的命名空间。
因此在大多数系统上只有一个命名空间,不过,CLONE_NEWS
标志可以使这一功能失效。