Linux内核设计与实现(14)--虚拟文件系统
  TEZNKK3IfmPf 2023年11月13日 16 0

 

虚拟文件系统,简称VFS。系统中所有文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。程序可以利用标准的Unix系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。

VFS使得用户可以直接使用open(),read()和write()这样的系统调用而无需考虑具体文件系统和实际物理介质,这点DOS是不具备的,DOS中任何对非本地文件系统的访问都要依靠特殊工具才能完成。

VFS与块I/O相结合,提供抽象、接口以及交融,使得用户空间的程序调用统一的系统调用访问各种文件,不管文件系统是什么,也不管文件系统位于何种介质,采用命名策略是统一的。

2.文件系统抽象层

 

VFS可以为应用提供通用接口操作各类文件系统,是因为内核在底层文件系统接口上建立了一个抽象层。这个抽象系统模型,包括了任何文件系统的常用功能集和行为。当然,该模型偏重于Unix风格的文件系统。

内核通过抽象层能够方便、简单地支持各种类型的文件系统,实际文件系统通过编程提供VFS所期望的抽象接口和数据结构。比如用户空间的一个写操作:

ret = write(fd, buf, len);

write系统调用将buf指针指向的长度为len字节的数据写入文件描述符fd对应的文件的当前位置。这个系统调用首先调用sys_write()处理,sys_write()函数要找到fd所在的文件系统实际给出的是哪个写操作,然后再执行该操作。具体文件系统的写方法是文件系统实现的一部分,数据最终写入介质。

3.Unix文件系统

 

Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点(mount point)。

从本质上讲文件系统是特殊的数据分层存储结构,包含文件、目录和相关控制信息。

文件系统通用操作包含创建、删除和安装等。在Unix中,文件系统采用全局命名空间,挂载到系统根目录下的一个子节点。

Unix文件概念和面向记录的文件系统形(比如OpenVMS的File-11)成鲜明对比,面向记录的文件系统提供更丰富、更结构化的表示,而简单的面向字节流抽象的Unix文件则以简单性和相当的灵活性为代价。

Unix中,目录属于普通文件,它列出包含在其中的所有文件,由于VFS把目录当作文件对待,所以可以对目录执行和文件相同的操作。

Unix系统将文件的信息和文件本身这两个概念加以区分,例如访问控制权限、大小、拥有者、创建时间等。文件相关信息称作文件的元数据,被存储在一个单独的数据结构中,叫做索引节点(inode)。索引节点存储在超级块中。超级块是一种包含文件系统信息的数据结构。

Linux的VFS设计目标就是要保证能与支持或实现了这些概念的文件系统协同工作。

非Unixf风格的文件系统(比如FAT或NTFS),要在Linux上工作,必须经过封装,提供一个符合这些概念的界面。这个封装转换需要在使用现场引入一些特殊处理,这样文件系统仍然能工作,但其带来的开销是很大的。

 

4.VFS对象及其数据结构

 

VFS其实采用的是面向对象的设计,内核是纯C开发,用结构体来实现对象,结构体包含数据和操作这些数据的函数指针。

VFS有四个主要对象类型,分别是:

超级块对象:代表一个具体已安装文件系统

索引节点对象:代表一个具体文件

目录项对象:代表一个目录项,是路径的一个组成部分

文件对象:它代表由进程打开的文件

注意,这里的目录项代表的是路径的一个组成部分,可能包含一个普通文件。这不同于目录。Unix中,目录仅是一个普通文件。

每个主要对象中,都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:

super_operations对象,包含针对特定文件系统调用方法,比如write_inode(),sync_fs等

inode_operations对象,包含create(),link()等;

dentry_operations对象,包含d_compare(),d_delete()等

file_operations对象,包含进程针对已打开文件所能调用的方法,比如read()和write()等

操作对象作为一个结构体指针来实现,此结构体中包含指向操作其父对象的函数指针。通常来说,可以继承使用VFS通用函数,如果通用函数提供的基本功能无法满足需要,那就必须使用实际文件系统独有方法填充这些函数指针,使其指向文件系统实例。

VFS使用了大量结构体对象,还包括file_system_type描述文件系统及其性能,vfsmount包含安装点相关信息等。

5.超级块对象

 

各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或者文件系统控制块(超级块对象)。对于非基于磁盘的文件系统(比如基于内存的文件系统sysfs),它们会在使用现场创建超级块并将其保存到内存中。

超级块对象由super_block结构体表示,定义在 中:

 

此处)折叠或打开

 

1. struct super_block {
2. struct list_head s_list; /* Keep this first *///指向所有超级快的链表
3. dev_t s_dev; /* search index; _not_ kdev_t *///设备标识符
4. unsigned char s_dirt; //修改(脏)标志
5. unsigned char s_blocksize_bits; //块大小,单位bit
6. unsigned long s_blocksize; //块大小,单位字节
7. loff_t s_maxbytes; /* Max file size *///文件大小上限
8. struct file_system_type *s_type; //文件系统类型
9. const struct super_operations *s_op; //超级块方法
10. const struct dquot_operations *dq_op; //磁盘限额方法
11. const struct quotactl_ops *s_qcop; //限额控制方法
12. const struct export_operations *s_export_op;//导出方法
13. unsigned long s_flags; //挂载标志
14. unsigned long s_magic; //文件系统幻数
15. struct dentry *s_root; //目录挂载点
16. struct rw_semaphore s_umount; //卸载信号量
17. struct mutex s_lock; //超级块信号量
18. int s_count; //超级块引用计数
19. int s_need_sync; //尚未同步标志
20. atomic_t s_active; //活动引用计数
21. #ifdef CONFIG_SECURITY
22. void *s_security; //活动模块
23. #endif
24. struct xattr_handler **s_xattr; //扩展属性操作
25.
26. struct list_head s_inodes; /* all inodes *///inodes链表
27. struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting *///匿名目录项
28. struct list_head s_files; //被分配文件链表
29. /* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
30. struct list_head s_dentry_lru; /* unused dentry lru *///未被使用目录项链表
31. int s_nr_dentry_unused; /* # of dentry on lru *///链表中目录项的数目
32.
33. struct block_device *s_bdev; //相关块设备
34. struct backing_dev_info *s_bdi;
35. struct mtd_info *s_mtd; //存储磁盘信息
36. struct list_head s_instances; //该类型文件系统
37. struct quota_info s_dquot; /* Diskquota specific options *///限额相关选项
38.
39. int s_frozen; //frozen标志位
40. wait_queue_head_t s_wait_unfrozen; //冻结的等待队列
41.
42. char s_id[32]; /* Informational name *///文本名字
43.
44. void *s_fs_info; /* Filesystem private info *///文件系统特殊信息
45. fmode_t s_mode; //安装权限
46.
47. /* Granularity of c/m/atime in ns.
48. Cannot be worse than a second */
49. u32 s_time_gran; //时间戳粒度
50.
51. /*
52. * The next field is for VFS *only*. No filesystems have any business
53. * even looking at it. You had been warned.
54. */
55. struct mutex s_vfs_rename_mutex; /* Kludge *///重命名信号量
56.
57. /*
58. * Filesystem subtype. If non-empty the filesystem type field
59. * in /proc/mounts will be "type.subtype"
60. */
61. char *s_subtype;//子类型名称
62.
63. /*
64. * Saved mount options for lazy filesystems using
65. * generic_show_options()
66. */
67. char *s_options; //已存安装选项
68. };

 

创建、管理和撤销超级块对象的代码位于文件fs/super.c中,超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。

6.超级块操作

 

超级块对象中最重要的一个域是s_op, 它指向超级块的操作函数表,操作函数表在 定义

该结构体每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的底层操作。

当文件系统需要对其超级块执行操作时,首先要在超级块对象中找到合适的方法,比如一个文件系要写自己的超级块,需要调用

sb->s_op->write_super(sb);

由于C语言无法直接得到操作函数的父对象,所以必须将父对象以参数显示传给操作函数。

 

此处)折叠或打开

 

 

1. struct super_operations {
2. //在给定的超级块下创建和初始化一个新的索引节点对象
3. struct inode *(*alloc_inode)(struct super_block *sb);
4. //释放给定的索引节点
5. void (*destroy_inode)(struct inode *);
6. //VFS在索引节点脏(被修改)时会调用此函数
7. //日志文件系统,(比如ext3/etx4)执行该函数进行日志更新
8. void (*dirty_inode) (struct inode *);
9. //用于将给定的索引节点写入磁盘,wait参数指明写操作是否需同步
10. int (*write_inode) (struct inode *, struct writeback_control *wbc);
11. //在最后一个指向索引节点的引用被释放后,VFS会调用该函数,VFS只需要简单地删除这个索引节点后,Unix文件系统就不会定义这个函数了
12. void (*drop_inode) (struct inode *);
13. void (*delete_inode) (struct inode *);//从磁盘删除给定的索引节点
14. void (*put_super) (struct super_block *);//卸载文件系统时被VFS调用,用来释放超级块,调用者必须持有s_lock锁
15. //用给定的超级块更新磁盘上的超级块,VFS通过该函数对内存中的超级块和磁盘中的超级块进行同步,调用者必须持有s_lock锁
16. void (*write_super) (struct super_block *);
17. //使文件系统的数据元与磁盘上的文件系统同步,wait参数指定操作是否同步
18. int (*sync_fs)(struct super_block *sb, int wait);
19. int (*freeze_fs) (struct super_block *);
20. int (*unfreeze_fs) (struct super_block *);
21. int (*statfs) (struct dentry *, struct kstatfs *);//获取文件系统状态
22. //当执行新的安装选项重新安装文件系统时,VFS会调用,必须持有s_lock锁
23. int (*remount_fs) (struct super_block *, int *, char *);
24. //VFS调用,释放索引节点,并清空包含相关数据所有页面
25. void (*clear_inode) (struct inode *);
26. //VFS调用中断安装操作,该函数被网络文件系统使用,如NFS。
27. void (*umount_begin) (struct super_block *);
28.
29. int (*show_options)(struct seq_file *, struct vfsmount *);
30. int (*show_stats)(struct seq_file *, struct vfsmount *);
31. #ifdef CONFIG_QUOTA
32. ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
33. ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
34. #endif
35. int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
36. };

 

以上函数都是由VFS在进程上下文调用,除了dirty_inode(),其他函数可以阻塞。

其中一些函数是可选的,不需要的函数设为NULL,如果VFS发现操作函数指针是NULL,它调用通用函数指向相应操作,或者什么都不做。

7.索引节点对象

 

索引节点对象,包含了内核在操作文件或目录时需要的全部信息。

对于Unix风格的文件系统来说,这些信息可以从磁盘索引节点直接读入。有的文件系统没有索引节点,没有将数据和控制信息分开存放。不管哪种情况、采用哪种方式,索引节点对象必须在内存中创建,以便于文件系统调用。

索引节点对象inode结构,在 中定义

此处)折叠或打开

1. struct inode {
2. struct hlist_node i_hash; //散列表
3. /* backing dev IO list */
4. struct list_head i_list; //索引节点链表
5. struct list_head i_sb_list; //超级块链表
6. struct list_head i_dentry; //目录项链表
7. unsigned long i_ino; //节点号
8. atomic_t i_count; //引用计数
9. unsigned int i_nlink; //硬链接数
10. uid_t i_uid; //使用者id
11. gid_t i_gid; //使用组id
12. dev_t i_rdev; //实际设备标志符
13. unsigned int i_blkbits; //以位单位文件大小
14. u64 i_version; //版本号
15. loff_t i_size; //以字节单位文件大小
16. #ifdef __NEED_I_SIZE_ORDERED
17. seqcount_t i_size_seqcount; //以i_size进行串行计数
18. #endif
19. struct timespec i_atime; //最后访问时间
20. struct timespec i_mtime; //最后修改时间
21. struct timespec i_ctime; //最后改变时间
22. blkcnt_t i_blocks; //文件的块数
23. unsigned short i_bytes; //使用的字节数
24. umode_t i_mode; //访问权限
25. /* i_blocks, i_bytes, maybe i_size */
26. spinlock_t i_lock; //自旋锁
27. struct mutex i_mutex; //互斥锁
28. struct rw_semaphore i_alloc_sem; //嵌入i_sem内部
29. const struct inode_operations *i_op;//索引节点操作表
30. /* former ->i_op->default_file_ops */
31. const struct file_operations *i_fop;//缺省的索引节点操作表
32. struct super_block *i_sb; //相关的超级块
33. struct file_lock *i_flock;
34. struct address_space *i_mapping;//相关的地址映射
35. struct address_space i_data; //设备地址映射
36. #ifdef CONFIG_QUOTA
37. struct dquot *i_dquot[MAXQUOTAS];
38. #endif
39. struct list_head i_devices;
40. union {
41. struct pipe_inode_info *i_pipe; //管道信息
42. struct block_device *i_bdev; //块设备驱动
43. struct cdev *i_cdev; //字符设备驱动
44. };
45.
46. __u32 i_generation;
47.
48. #ifdef CONFIG_FSNOTIFY
49. __u32 i_fsnotify_mask; /* all events this inode cares about */
50. struct hlist_head i_fsnotify_mark_entries; /* fsnotify mark entries */
51. #endif
52.
53. #ifdef CONFIG_INOTIFY
54. struct list_head inotify_watches; /* watches on this inode */
55. struct mutex inotify_mutex; /* protects the watches list */
56. #endif
57.
58. unsigned long i_state;
59. unsigned long dirtied_when; /* jiffies of first dirtying */
60.
61. unsigned int i_flags; //文件系统标志
62.
63. atomic_t i_writecount; //写者技数
64. #ifdef CONFIG_SECURITY
65. void *i_security; //安全模块
66. #endif
67. #ifdef CONFIG_FS_POSIX_ACL
68. struct posix_acl *i_acl;
69. struct posix_acl *i_default_acl;
70. #endif
71. /* fs or device private pointer */
72.
73. void *i_private;//fs私有指针
74. };

 

一个索引节点代表文件系统中的一个文件(索引节点仅当文件被访问时,才在内存中创建),也可以是设备文件或管道文件这样的特殊文件。

8.索引节点操作

 

索引节点对象的inode_operations项非常重要,因为它描述了操作索引节点的所有方法,这些方法由文件系统实现。在 定义

 

此处)折叠或打开

 

 

1. struct inode_operations {
2. //VFS通过系统调用create()和open()来调用该函数,从而为dentry对象创建
3. //一个新的索引节点
4. int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
5. //在特定的目录中寻找索引节点,该索引节点要对应于denrty中给定的文件名
6. struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
7. //被系统调用link()调用,用来创建硬链接
8. //硬链接名称由dentry参数指定,链接对象是dir目录中old_dentry目录项所代表的文件
9. int (*link) (struct dentry *,struct inode *,struct dentry *);
10. //被系统调用ulink()调用,从目录dir中删除由目录项dentry指定的索引节点对象
11. int (*unlink) (struct inode *,struct dentry *);
12. //被symlik()调用,创建符号链接
13. int (*symlink) (struct inode *,struct dentry *,const char *);
14. //被mknod()调用,创建新目录
15. int (*mkdir) (struct inode *,struct dentry *,int);
16. //被rmdir()调用,删除dir目录中的dentry目录项代表的文件
17. int (*rmdir) (struct inode *,struct dentry *);
18. //被mknod()调用,船舰特殊文件(设备文件,管道,套接字等)
19. int (*mknod) (struct inode *,struct dentry *,int,dev_t);
20. //移动文件
21. int (*rename) (struct inode *, struct dentry *,
22. struct inode *, struct dentry *);
23. //readlink()调用,拷贝数据到特定的缓冲buffer中?
24. //拷贝的数据来自dentry指定的符号链接,拷贝大小最大可达buflen字节
25. int (*readlink) (struct dentry *, char __user *,int);
26. void * (*follow_link) (struct dentry *, struct nameidata *);
27. void (*put_link) (struct dentry *, struct nameidata *, void *);
28. void (*truncate) (struct inode *);//修改文件大小
29. int (*permission) (struct inode *, int);//检查访问权限
30. int (*check_acl)(struct inode *, int);
31. //被notify_change()调用,在修改索引节点后,通知发送了"改变事件"
32. int (*setattr) (struct dentry *, struct iattr *);
33. //在通知索引节点需要从磁盘中更新时,VFS会调用
34. int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
35. //VFS调用,给dentry指定的文件设置扩展属性
36. int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
37. //VFS调用,向value中拷贝给定文件的扩展属性name对应的数值
38. ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
39. //将特定文件的所有属性列表拷贝到一个缓冲列表中
40. ssize_t (*listxattr) (struct dentry *, char *, size_t);
41. //从文件中删除指定的属性
42. int (*removexattr) (struct dentry *, const char *);
43. void (*truncate_range)(struct inode *, loff_t, loff_t);
44. long (*fallocate)(struct inode *inode, int mode, loff_t offset,
45. loff_t len);
46. int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
47. u64 len);
48. };

 

9.目录项对象

 

VFS把目录当作文件对待,一个完整路径中的每个组成部分都由一个索引节点对象标志。虽然可以用索引节点表示,但为了实际操作方便,引入目录项对象。完整路径的每一部分都是一个目录项对象,比如/bin/vi中/,bin,vi是三个目录项对象。

VFS在执行目录操作时(需要的话)会现场创建目录项对象。

目录项对象dentry结构体在 定义

 

此处)折叠或打开

 

 

1. struct dentry {
2. atomic_t d_count; //使用计数
3. /* protected by d_lock */
4. unsigned int d_flags; //目录项标识
5. /* per dentry lock */
6. spinlock_t d_lock; //但目录项锁
7. int d_mounted;
8. /* Where the name belongs to - NULL is
9. * negative */
10. struct inode *d_inode; //相关联的索引节点
11. /*
12. * The next three fields are touched by __d_lookup. Place them here
13. * so they all fit in a cache line.
14. */
15. struct hlist_node d_hash; /* lookup hash list */
16. struct dentry *d_parent; /* parent directory */
17. struct qstr d_name; //目录项名字
18.
19. struct list_head d_lru; /* LRU list *///未使用的链表
20. /*
21. * d_child and d_rcu can share memory
22. */
23. union {
24. /* child of parent list */
25. struct list_head d_child;//目录项内部形成的链表
26. struct rcu_head d_rcu; //RCU加锁
27. } d_u;
28. struct list_head d_subdirs; /* our children *///子目录链表
29. struct list_head d_alias; /* inode alias list *///索引节点别名链表
30. unsigned long d_time; /* used by d_revalidate *///重置时间
31. const struct dentry_operations *d_op;//目录项操作时间
32. struct super_block *d_sb; /* The root of the dentry tree *///文件的超级块
33. void *d_fsdata; /* fs-specific data *///文件系统特有数据
34.
35. unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
36. };

 

与超级块和索引节点不同,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它,而且由于目录项对象并非真正保存在磁盘上,所以目录项结构体没有是否被修改的标志。

9.1 目录项状态

 

目录项对象有三种有效状态:被使用、未被使用和负状态。

(1)一个被使用的目录项对应一个有效的索引节点(d_inode指针),并且表明该对象存在一个或多个使用者(d_count为正值),一个目录项处于被使用状态,意味着它正被VFS使用并且指向有效数据,因此不能丢弃。

(2)一个未被使用的目录项对应一个有效的索引节点(d_inode指向索引节点),但VFS当前并未使用(d_count为0),该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时使用。这样当需要它时,不必重新创建,这样路径查找更快。

(3)负状态的目录项没有对应的有效索引节点,节点以及被删除或路径已经不正确了。但目录项仍然保留,以便快速解析以后的路径查询。

目录项对象释放后也可以保存到slab对象缓存中。

9.2 目录项缓存

 

如果VFS层遍历路径名中的所有元素并将它们逐个解析成目录项对象,还要到达最深层目录,是一件非常费力的工作,会浪费大量时间。所以内核将目录项对象缓存在目录项缓存中

dcache主要包括三个部分:

(1)“被使用的”目录项链表,i_dentry项链接相关索引节点。因为一个索引节点可能有多个链接,所以就可能有多个目录项对象,因此用链表链接他们。

(2)“最近被使用的”双向链表。该链表含有未被使用的或负状态的目录项对象。

由于该链表总是在头部插入目录项,所以头节点目录项是最新的,内核通过删除节点回收内存时,会从链尾删除。

(3)散列表和相应的散列函数,用来快速地将给定路径解析为相关目录项对象。

散列表由数组dentry_hashtable表示,其中每一个元素都是一个指向相同键值的目录项对象链表的指针。

实际的散列值由d_hash()函数计算,它是内核提供给文件系统的唯一的散列函数。

dcache在一定意义上也提供了对索引节点的缓存,即icache。和目录项对象相关的索引节点不会被释放。因为目录项让相关索引节点的使用计数为正,这样确保相应索引节点保留在内存中。所以,只要路径名在缓存中找到,那么相应的索引节点肯定也在内存中缓存着。

 

因为文件访问呈现空间和时间的局部性,所以对目录项和索引节点进程缓存非常有益。

时间的局部性,是指程序可能会一次又一次地访问相同的文件,当同一个文件被访问时,所缓存的相关目录项和索引节点命中率高。

空间局部性,是指程序可能在同一个目录下访问多个文件,因此一个文件对应的目录项缓存后命中率高,因为相关的文件可能在下次又被使用。

10.目录项操作

 

dentry_operation结构体指向VFS操作目录项的所有方法,在 定义

 

此处)折叠或打开

1. struct dentry_operations {
2. int (*d_revalidate)(struct dentry *, struct nameidata *);
3. int (*d_hash) (struct dentry *, struct qstr *);
4. int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
5. int (*d_delete)(struct dentry *);
6. void (*d_release)(struct dentry *);
7. void (*d_iput)(struct dentry *, struct inode *);
8. char *(*d_dname)(struct dentry *, char *, int);
9. };

int d_ revalidate(struct dentry *dentry, struct nameidata*name);

判断目录对象是否有效,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默认的操作,仅比较字符串。但有些系统,比如FAT,不区分大小写的,所以需要实现不区分大小写的字符串比较函数。

int (*d_delete)(struct dentry *dentry);

当目录项对象的d_count计数值等于0,VFS调用该函数

void (*d_release)(struct dentry *);

当目录项对象将要被释放时,VFS调用该函数

void (*d_iput)(struct dentry *, struct inode *);

当目录项丢失了索引节点时(索引节点被删除了),VFS调用,默认调用input()函数释放所有节点。

11.文件对象

 

文件对象,表示进程已打开的文件,进程直接处理的是文件,而不是超级块,索引节点或目录项。文件对象与我们熟悉的系统调用read()和write()对应。

多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅是进程观点上已打开的实际文件,只有索引节点和目录项对象才是唯一的。

文件对象在 定义

此处)折叠或打开

1. struct file {
2. /*
3. * fu_list becomes invalid after file_free is called and queued via
4. * fu_rcuhead for RCU freeing
5. */
6. union {
7. struct list_head fu_list; //文件对象链表
8. struct rcu_head fu_rcuhead; //释放之后的RCU链表
9. } f_u;
10. struct path f_path; //包含目录项
11. #define f_dentry f_path.dentry
12. #define f_vfsmnt f_path.mnt
13. const struct file_operations *f_op;//文件操作表
14. spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ *///单个文件结构锁
15. atomic_long_t f_count; //文件对象的使用计数
16. unsigned int f_flags; //打开文件时所指定的标志
17. fmode_t f_mode; //文件的访问模式
18. loff_t f_pos; //文件当前的位移量(文件指针)
19. struct fown_struct f_owner; //拥有者通过信号进行异步I/O数据传送
20. const struct cred *f_cred; //文件的信任状
21. struct file_ra_state f_ra; //预读状态
22.
23. u64 f_version; //版本号
24. #ifdef CONFIG_SECURITY
25. void *f_security; //安全模块
26. #endif
27. /* needed for tty driver, and maybe others */
28. void *private_data; //tty设备驱动的钩子
29.
30. #ifdef CONFIG_EPOLL
31. /* Used by fs/eventpoll.c to link all the hooks to this file */
32. struct list_head f_ep_links; //事件池链表
33. #endif /* #ifdef CONFIG_EPOLL */
34. struct address_space *f_mapping; //页缓存映射
35. #ifdef CONFIG_DEBUG_WRITECOUNT
36. unsigned long f_mnt_write_state; //调试状态
37. #endif
38. };

文件对象实际上没有对应的磁盘数据,通过f_dentry指针指向相关的目录项对象,目录项会指向相关的索引节点,索引节点会记录文件是否为脏的。

12.文件操作

此处)折叠或打开

1. /*
2. * NOTE:
3. * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
4. * can be called without the big kernel lock held in all filesystems.
5. */
6. struct file_operations {
7. struct module *owner;
8. //该函数用于更新偏移量指针,
9. //由系统调用lleek()调用
10. loff_t (*llseek) (struct file *, loff_t, int);
11. //从给定文件的offset偏移处读取count字节到buf中
12. //由系统调用read()调用
13. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
14. //从给定buf中取出count字节数据,写入给定文件的offset偏移处,更新文件指针
15. //由系统调用write()调用
16. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
17. //从给定文件,以同步方式读取count字节的数据到buf中,
18. //由系统调用aio_read()调用
19. ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
20. //以同步方式从给定buf中取出count字节数据,写入由iocb描述的文件中
21. //由系统调用aio_write()调用
22. ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
23. //返回目录列表的下一个目录
24. //由系统调用readdir()调用
25. int (*readdir) (struct file *, void *, filldir_t);
26. //该函数睡眠等待给定的文件活动
27. //由系统调用poll()调用
28. unsigned int (*poll) (struct file *, struct poll_table_struct *);
29. //给设备发送命令参数对,进行设置操作
30. //由系统调用ioctl调用,调用者必须持有BKL
31. int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
32. //功能与ioctl()类似,但不需要调用者持有BKL,如果用户空间调用ioctl()系统调用,
33. //VFS可以调用unlocked_ioctl(),它与ioctl()实现一个即可,一般有限实现unlocked_ioctl()
34. long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
35. //是ioctl()函数的可移植变种,被32位应用程序用在64位系统上
36. long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
37. //将给定的文件映射到指定的地址空间上,
38. //由系统调用mmap()调用
39. int (*mmap) (struct file *, struct vm_area_struct *);
40. //创建一个新的文件对象,并将它与相应索引节点对象关联
41. //由系统调用open()调用
42. int (*open) (struct inode *, struct file *);
43. //当已打开文件的引用计数减少时,该函数被VFS调用,作用根据具体文件系统而定
44. int (*flush) (struct file *, fl_owner_t id);
45. //当文件最后一个引用被注销时,该函数被VFS调用
46. int (*release) (struct inode *, struct file *);
47. //将给定文件的所有缓存数据写回磁盘
48. //由系统调用fsync()调用
49. int (*fsync) (struct file *, struct dentry *, int datasync);
50. //将iocb描述的文件所有缓存数据写回磁盘,
51. //由系统调用aio_fsync()调用
52. int (*aio_fsync) (struct kiocb *, int datasync);
53. //用于打开或关闭异步I/O的通告信号
54. int (*fasync) (int, struct file *, int);
55. //给指定文件上锁
56. int (*lock) (struct file *, int, struct file_lock *);
57. ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
58. unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
59. int (*check_flags)(int);
60. int (*flock) (struct file *, int, struct file_lock *);
61. ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
62. ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
63. int (*setlease)(struct file *, long, struct file_lock **);
64. };

 

13.和文件系统相关的数据结构

除了上面几种VFS基础对象外,内核还使用了另外一些标注数据结构来管理文件系统的其他相关数据。

第一个是file_system_type,用来描述各种特定文件系统类型,比如ext3/4,UDF。

在 定义

此处)折叠或打开

1. struct file_system_type {
2. const char *name; //文件系统名字
3. int fs_flags; //文件系统类型标志
4. //从磁盘中读取超级块
5. int (*get_sb) (struct file_system_type *, int,
6. const char *, void *, struct vfsmount *);
7. void (*kill_sb) (struct super_block *);//终止访问超级块
8. struct module *owner; //文件系统模块
9. struct file_system_type * next; //链表中下一个文件系统类型
10. struct list_head fs_supers; //超级块对象链表
11. //以下字段运行时使锁生效
12. struct lock_class_key s_lock_key;
13. struct lock_class_key s_umount_key;
14.
15. struct lock_class_key i_lock_key;
16. struct lock_class_key i_mutex_key;
17. struct lock_class_key i_mutex_dir_key;
18. struct lock_class_key i_alloc_sem_key;
19. };

get_sb()函数从磁盘读取超级块,并且在文件系统被安装时,在内存中组装超级块对象,剩余的函数描述文件系统属性。

每种文件系统不管被安装多少个实例到系统中,都只有一个file_system_type结构。当文件系统被实际安装时,有一个vfsmount结构体在安装点被创建。该结构体用来代表文件系统的实例,即一个安装点

第二个结构体vfsmount,在 定义

此处)折叠或打开

1. struct vfsmount {
2. struct list_head mnt_hash;
3. struct vfsmount *mnt_parent; /* fs we are mounted on */
4. struct dentry *mnt_mountpoint; /* dentry of mountpoint */
5. struct dentry *mnt_root; /* root of the mounted tree */
6. struct super_block *mnt_sb; /* pointer to superblock */
7. struct list_head mnt_mounts; /* list of children, anchored here */
8. struct list_head mnt_child; /* and going through their mnt_child */
9. int mnt_flags;
10. /* 4 bytes hole on 64bits arches */
11. const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
12. struct list_head mnt_list;
13. struct list_head mnt_expire; /* link in fs-specific expiry list */
14. struct list_head mnt_share; /* circular list of shared mounts */
15. struct list_head mnt_slave_list;/* list of slave mounts */
16. struct list_head mnt_slave; /* slave list entry */
17. struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
18. struct mnt_namespace *mnt_ns; /* containing namespace */
19. int mnt_id; /* mount identifier */
20. int mnt_group_id; /* peer group identifier */
21. /*
22. * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
23. * to let these frequently modified fields in a separate cache line
24. * (so that reads of mnt_flags wont ping-pong on SMP machines)
25. */
26. atomic_t mnt_count;
27. int mnt_expiry_mark; /* true if marked for expiry */
28. int mnt_pinned;
29. int mnt_ghosts;
30. #ifdef CONFIG_SMP
31. int __percpu *mnt_writers;
32. #else
33. int mnt_writers;
34. #endif
35. };

vfsmount结构还保存了在安装时指定的标志信息,该信息存储在mnt_flags域中

14.和进程相关的数据结构

每个进程多有自己的一组打开的文件,像跟文件系统,当前工作目录,安装点等。有三个将VFS层和系统的进程紧密联系在一起,它们分别是file_struct, fs_struct, namespace结构体。

file_struct 结构体定义在 中,该结构体由进程描述符的files目录项指向,所有与单个进程相关的信息(如打开的文件及文件描述符)都包含在其中

此处)折叠或打开

1. /*
2. * Open file table structure
3. */
4. struct files_struct {
5. /*
6. * read mostly part
7. */
8. atomic_t count; //结构的使用计数
9. struct fdtable *fdt; //指向其他fd表的指针
10. struct fdtable fdtab; //基fd表
11. /*
12. * written part on a separate cache line in SMP
13. */
14. spinlock_t file_lock ____cacheline_aligned_in_smp;
15. int next_fd; //缓存下一个可用的fd
16. struct embedded_fd_set close_on_exec_init;//exec()时关闭的文件描述符表
17. struct embedded_fd_set open_fds_init;//打开的文件描述符链表
18. struct file * fd_array[NR_OPEN_DEFAULT];//缺省的文件对象数组
19. };

 

第二个和进程相关的结构体是fs_struct,该结构由进程描述符的fs域指向,它包含文件系统和进程的相关信息,定义在 中

此处)折叠或打开

1. struct fs_struct {
2. int users; //用户数目
3. rwlock_t lock; //保护该结构体的锁
4. int umask; //掩码
5. int in_exec; //当前正执行的文件
6. struct path root, pwd;//根目录路径和当前工作目录路径
7. };

第三个相关进程相关结构体是namespace,由进程描述符的mmt_namespace域指向,在 定义,大多数系统上只有一个命名空间,但CLONE_NEWS标志可以使这一功能失效。

上述这些数据结构都是通过进程描述符连接起来的,对多数进程来说,它们的描述符都指向唯一的files_struct和fs_struct结构体。但对于那些使用CLONE_FILES或CLONE_FS创建的进程,会共享这两个结构体。所以多进程描述符可能指向同一个files_struct或fs_struct结构体,每个结构体都维护一个count域作为引用计数,防止在进程正使用时,该结构被撤销。

小结:

Linux支持了相当多种类的文件系统,从本地文件系统(ext3和ext4)到网络文件系统(NFS和Coda),Linux在标准内核中已支持的文件系统超过60种。VFS层提供给这些不同文件系统一个统一的实现框架,而且也提供了能和标准系统调用交互工作的统一接口。由于VFS层的存在,使得在Linux上实现新文件系统的工作变得简单起来,它可以轻松地使这些文件系统通过标准Unix系统调用而协同工作。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月13日 0

暂无评论

推荐阅读
  TEZNKK3IfmPf   2024年05月31日   51   0   0 linux服务器
  TEZNKK3IfmPf   2024年05月31日   30   0   0 linux服务器centos
  TEZNKK3IfmPf   2024年05月31日   29   0   0 linuxbind
  TEZNKK3IfmPf   2024年05月31日   40   0   0 linuxshell
TEZNKK3IfmPf