linux虚拟文件系统四大对象:
1)超级块(super block)
2)索引节点(inode)
3)目录项(dentry)
4)文件对象(file)
super_block
/*超级块代表了整个文件系统,超级块是文件系统的控制块,有整个文件系统信息,一个文件系统所有的inode都要连接到超级块上, 可以说,一个超级块就代表了一个文件系统 */ struct super_block { struct list_head s_list; /* Keep this first 一个双向循环链表,把所有的super_block连接起来,一个super_block代表一个在linux上的文件系统, 这个list上边的就是所有的在linux上记录的文件系统。*/ dev_t s_dev; /* search index; _not_ kdev_t 包含该具体文件系统的块设备标识符。例如,对于 /dev/hda1,其设备标识符为 0x301*/ unsigned char s_blocksize_bits;//上面的size大小占用位数,例如512字节就是9 bits unsigned long s_blocksize;//文件系统中数据块大小,以字节单位 loff_t s_maxbytes; /* Max file size允许的最大的文件大小(字节数) */ struct file_system_type *s_type;//文件系统类型 ext2还是fat32 ? “文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block 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;//指向该具体文件系统安装目录的目录项 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 hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */ struct list_head s_mounts; /* list of mounts; _not_ for fs use */ struct block_device *s_bdev;//指向文件系统被安装的块设备 struct backing_dev_info *s_bdi; struct mtd_info *s_mtd; struct hlist_node s_instances; 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; /* 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; /* AIO completions deferred from interrupt context */ struct workqueue_struct *s_dio_done_wq; struct hlist_head s_pins; /* * Context in which to interpret filesystem uids, gids, * quotas, device nodes, extended attributes 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; struct list_lru s_inode_lru ____cacheline_aligned_in_smp; 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 */ };
Inode:
/* * Keep mostly read-only and often accessed (especially for * the RCU path lookup and 'stat' data) fields at the beginning * of the 'struct inode' inode有两种,一种是VFS的inode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode, 这样才是算使用了磁盘文件inode。 每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定(现代OS可以动态变化),一般每2KB就设置一个inode。 一般文件系统中很少有文件小于2KB的,所以预定按照2KB分,一般inode是用不完的。所以inode在文件系统安装的时候会有一个默认数量, 后期会根据实际的需要发生变化 inode号是唯一的,表示不同的文件。其实在Linux内部的时候,访问文件都是通过inode号来进行的,所谓文件名仅仅是给用户容易使用的。 当我们打开一个文件的时候,首先,系统找到这个文件名对应的inode号;然后,通过inode号,得到inode信息,最后,由inode找到文件数据所在的block, 现在可以处理文件数据了。 inode和文件的关系:当创建一个文件的时候,就给文件分配了一个inode。一个inode只对应一个实际文件,一个文件也会只有一个inode。 inodes最大数量就是文件的最大数量。 */ struct inode { umode_t i_mode;//文件的类型和访问权限 unsigned short i_opflags; kuid_t i_uid;//文件拥有者标号 kgid_t i_gid;//文件所在组标号 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;//inode所属文件系统的超级块指针 struct address_space *i_mapping;//表示向谁请求页面 于描述页高速缓存中的页面的 #ifdef CONFIG_SECURITY void *i_security; #endif /* Stat data, not accessed from path walking */ unsigned long i_ino;//索引节点号,每个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;//inode所代表的的文件的大小,以字节为单位 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 mutex i_mutex; unsigned long dirtied_when; /* jiffies of first dirtying */ unsigned long dirtied_time_when; struct hlist_node i_hash;//指向hash链表指针,用于inode的hash表 struct list_head i_io_list; /* backing dev IO list */ #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; /* inode LRU list */ struct list_head i_sb_list; union { struct hlist_head i_dentry;//指向目录项链表指针,注意一个inodes可以对应多个dentry,因为一个实际的文件可能被链接到其他的文件, //那么就会有另一个dentry,这个链表就是将所有的与本inode有关的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; /* former ->i_op->default_file_ops 文件操作*/ struct file_lock_context *i_flctx; struct address_space i_data; struct list_head i_devices;//设备链表。共用同一个驱动程序的设备形成的链表 union { struct pipe_inode_info *i_pipe;//向管道文件(如果文件是管道文件时使用) struct block_device *i_bdev;//指向块设备文件指针(如果文件是块设备文件时使用) struct cdev *i_cdev;//指向字符设备文件指针(如果文件是字符设备时使用) char *i_link;//链接 }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct hlist_head i_fsnotify_marks; #endif void *i_private; /* fs or device private pointer */ };
和索引节点关联的方法有:
create loopup link unlink symlink mkdir rmdir mknod rename readlink follow_link put_link truncate permsission setattr setxattr 等函数
索引节点操作
truct inode_operations *i_op; struct inode_operations { //create():创建一个新的磁盘索引结点。 int (*create) (struct inode *,struct dentry *,int, struct nameidata *); //lookup():查诈一个索引结点所在的目录。 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *); ...... //mkdir():为目录项创建一个新的索引结点。 int (*mkdir) (struct inode *,struct dentry *,int); ...... };
dentry:
/* dentry则表示了不同层级之间的关系,也是链接所使用的结构体。dentry通过d_parent来和上级目录构成链接关系, 通过d_op来存储对应的实际文件系统的文件操作,如创建、删除、打开、读写等。d_sb指向实际文件系统的超级块, 该结构在上文已详细介绍。d_inode指向对应的inode,d_name表示该文件的文件名。 一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。 所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。但是inode却可以对应多个 */ struct dentry { /* RCU lookup touched fields */ unsigned int d_flags; /* protected by d_lock目录项缓存标识,可取 DCACHE_UNUSED DCACHE_REFERENCED 等 */ seqcount_t d_seq; /* per dentry seqlock */ struct hlist_bl_node d_hash; /* lookup hash list 内核使用dentry_hashtable对dentry进行管理,dentry_hashtable是由list_head组成的链表, 一个dentry创建之后,就通过d_hash链接进入对应的hash值的链表中。*/ struct dentry *d_parent; /*父目录的目录项 parent directory */ struct qstr d_name;//目录项名称 struct inode *d_inode; /* 与该目录项关联的inode Where the name belongs to - NULL is * negative */ unsigned char d_iname[DNAME_INLINE_LEN]; /* 存放短的文件名small names */ /* Ref lookup also touches following */ struct lockref d_lockref; /* per-dentry lock and refcount */ 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 重新变为有效的时间!注意只要操作成功这个dentry就是有效的,否则无效*/ void *d_fsdata; /* fs-specific data 文件系统私有数据 */ struct list_head d_lru; /*最近未使用的目录项的链表 LRU list */ struct list_head d_child; /* child of parent list 目录项通过这个加入到父目录的d_subdirs中*/ struct list_head d_subdirs; /* our children本目录的所有孩子目录链表头 */ /* * d_alias and d_rcu can share memory 一个有效的dentry必然与一个inode关联,但是一个inode可以对应多个dentry,因为一个文件可以被链接到其他文件, 所以,这个dentry就是通过这个字段链接到属于自己的inode结构中的i_dentry链表中的 */ union { struct hlist_node d_alias; /* inode alias list这个dentry就是通过这个字段链接到属于自己的inode结构中的i_dentry链表中的 */ struct rcu_head d_rcu; } d_u; };
每个目录项对象可以处于以下四种状态之一:
- 空闲状态(free):处于该状态的目录项对象不包括有效的信息,且还没有被VFS使用。对应的内存区由slab分配器进行处理。
- 未使用状态(unused):处于该状态的目录项对象当前还没有被内核使用。该对象的引用计数器d_count的值为0,但其d_inode字段仍然指向关联的索引节点。该目录项对象包含有效的信息,但为了在必要时回收内存,它的内容可能被丢弃。
- 正在使用状态(in use):处于该状态的目录项对象当前正在被内核使用。该对象的引用计数器d_count的值为正数,其d_inode字段指向关联的索引节点对象。该目录项对象包含有效的信息,并且不能被丢弃。
- 负状态(negative):与目录项关联的索引节点不复存在,那是因为相应的磁盘索引节点已被删除,或者因为目录项对象是通过解析一个不存在文件的路径名创建的。目录项对象的d_inode字段被置为NULL,但该对象仍然被保存在目录项高速缓存中,以便后续对同一文件目录名的查找操作能够快速完成。术语“负状态”容易使人误解,因为根本不涉及任何负值。
truct dentry_operations { /* * 在把目录项对象转换为一个文件路径名之前,判定该目录项对象是否仍然有效。 * 缺省的VFS函数什么也不做,而网络文件系统可以指定自己的函数。 * */ int (*d_revalidate)(struct dentry *, struct nameidata *); /* * 生成一个散列值;这是用于目录项散列表的、特定干具体文件系统的散列函数。 * 参数dentry标识包含路径分量的目录。参数name指向一个结构, * 该结构包含要查找的路径名分量以及由散列函数生成的散列值。 * */ int (*d_hash) (struct dentry *, struct qstr *); /* 比较两个文件名。name1应该属于dir所指的目录。 * 缺省的VFS函数是常用的字符串匹配函数。 * 不过,每个文件系统可用自己的方式实现这一方法。 * 例如,MS.DOS文件系统不区分大写和小写字母。 **/ int (*d_compare) (struct dentry *, struct qstr *, struct qstr *); /* * 当对目录项对象的最后一个引用被删除(d_count变为“0”)时, * 调用该方法。缺省的VFS函数什么也不做。 * */ int (*d_delete)(struct dentry *); /* 当要释放一个目录项对象时(放入slab分配器),调用该方法。 * 缺省的VFS函数什么也不做。 * */ void (*d_release)(struct dentry *); /* * 当一个目录项对象变为“负”状态(即丢弃它的索引节点)时,调用该方法。 * 缺省的VFS函数调用iput()释放索引节点对象。 * */ void (*d_iput)(struct dentry *, struct inode *); };
目录项高级缓存
为了最大限度地提高处理同一个文件需要被反复访问的这些目录项对象的效率,Linux使用目录项高速缓存,它由两种类型的数据结构组成:
- 一个处于正在使用、未使用或负状态的目录项对象的集合。
- 一个散列表,从中能够快速获取与给定的文件名和目录名对应的目录项对象。同样,如果访问的对象不在目录项高速缓存中,则散列表函数返回一个空值。
目录项高速缓存的作用还相当于索引节点高速缓存(inode cache)的控制器。在内核内存中,并不丢弃与未用目录项相关的索引节点,这是由于目录项高速缓存仍在使用它们。因此,这些索引节点对象保存在RAM中,并能够借助相应的目录项快速引用它们。
struct list_head d_lru; /* LRU list */
所有“未使用”目录项对象都存放在一个“最近最少使用(Least Recently used,LRU)”的双向链表中,该链表按照插入的时间顺序。换句话说,最后释放的目录项对象放在链表的首部,所以最近最少使用的目录项对象总是靠近链表的尾部。一旦目录项高速缓存的空间开始变小,内核就从链表的尾部删除元素,使得最近最常使用的对象得以保留。
files
存储一个进程与一个被该进程打开的文件之间交互的信息。该信息只存在内核内存中且当该进程拥有这个文件时。
同一个进程可以多次打开同一个文件而得到多个不同的file结构,file结构描述被打开文件的属性,如文件的当前偏移量等信息。
struct path { struct vfsmount *mnt; struct dentry *dentry; };
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u;//用于通用文件对象链表的指针 struct path f_path;//指出该文件的已安装的文件系统vfsmount以及文件相关的目录项对象dentry struct inode *f_inode; /* cached value */ const struct file_operations *f_op;///*指向文件操作表的指针 --函数指针*/ /* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; atomic_long_t f_count;//表示打开文件的引用计数 unsigned int f_flags;//表示打开文件的权限 fmode_t f_mode;//设置对文件的访问模式,例如:只读,只写 struct mutex f_pos_lock; loff_t f_pos;//表示当前读写文件的位置 struct fown_struct f_owner;//该结构的作用是通过信号进行I/O时间通知的数据 异步操作;记录一个进程ID,当某些事发送的时候发送给该ID进程的信号 const struct cred *f_cred; struct file_ra_state f_ra;//文件预读状态,文件预读算法使用的主要数据结构,当打开一个文件时,f_ra中出了perv_page(默认为-1)和ra_apges(对该文件允许的最大预读量)这两个字段外,其他的所有西端都置为0 u64 f_version;//记录文件的版本号,每次使用后都自动递增 #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data; #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; } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
存放在文件对象中的主要信息是文件指针f_pos,即文件中当前的位置,下一个操作将在该位置发生。由于几个进程可能同时访问同一文件,因此文件指针必须存放在文件对象而不是索引节点对象中。
文件对象的f_count字段是一个引用计数器:它记录使用文件对象的进程数(记住,以CLONE_FILES标志创建的轻量级进程共享打开文件表,因此它们可以使用相同的文件对象)。当内核本身使用该文件对象时也要增加计数器的值——例如,把对象插入链表中或发出dup()系统调用时。file操作
内核将一个索引节点从磁盘装入内存时,就会把指向这些文件操作的指针存放在file_operations结构中,而该结构的地址存放在该索引节点对象的i_fop字段中。当进程打开这个文件时,VFS就用存放在索引节点中的这个地址初始化新文件对象的fop字段,使得对文件操作的后续调用能够使用这些函数。
与进程相关的文件
每个进程都有它自己当前的工作目录和它自己的根目录。这仅仅是内核用来表示进程与文件系统相互作用所必须维护的数据中的两个列子。类型为fs_struct的数据结构就用于此目的,且每个进程描述的fs字段就指向进程的fs_struct结构。
fs_struct
struct fs_struct { atomic_t count; //共享这个表的进程个数 rwlock_t lock; //用于表中字段的读/写自旋锁 //当打开文件设置文件权限时所使用的位掩码 int umask; //根目录,当前目录,模拟根目录的目录项 struct dentry * root, * pwd, * altroot; //根目录锁安装的文件系统对象 //当前目录所安装的文件系统对象 //模拟目录所安装的文件系统对象(在80x86结构上始终为NULL) struct vfsmount * rootmnt, * pwdmnt, * altrootmnt; };
files_struct:
struct fdtable { unsigned int max_fds; struct file __rcu **fd; /* current fd array */ unsigned long *close_on_exec; unsigned long *open_fds; unsigned long *full_fds_bits; struct rcu_head rcu; }; /* * Open file table structure */ struct files_struct { /* * read mostly part */ atomic_t count;/* 共享该表的进程数 */ bool resize_in_progress; wait_queue_head_t resize_wait; /*包括一个struct fdtable变量实例和一个struct fdtable类型指针; 而struct fdtable中的成员变量close_on_exec,open_fds,fd又分别指向struct files_struct中 的成员close_on_exec_init,open_fds_init和fd_array*/ struct fdtable __rcu *fdt; struct fdtable fdtab; /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/ int next_fd;/*已分配的文件描述符加1*/ unsigned long close_on_exec_init[1];/*指向执行exec( )时需要关闭的文件描述符*/ unsigned long open_fds_init[1];/*指向打开文件描述符的指针*/ unsigned long full_fds_bits_init[1];/*文件描述符的初值集合*/ struct file __rcu * fd_array[NR_OPEN_DEFAULT];;/* 文件对象指针的初始化数组 默认使用 不够时动态分配*/ /*通常,fd字段指向files_struct结构的fd_array字段,该字段包括32个文件对象指针。 如果进程打开的文件数目多于32,内核就分配一个新的、更大的文件指针数组, 并将其地址存放在fd字段中,内核同时也更新max_fds字段的值。*/ };
linux系统中,一个进程打开的文件数是有初步限制的,即其文件描述符数初始时有最大化定量,即一个进程一般只能打开NR_OPEN_DEFAULT个文件,该值在32位机上为32个,在64位机上为64个。上面的files_struct这种初始化正是体现了进程初步所能打开文件的内核结构描述。这里需要说明的是,这不仅仅限于类似上面的静态初始化,当init进程fork一个子进程时,也是如此(此时是files_struct是动态分配的)。
do_fork------>copy_process |------>dup_task_struct------>alloc_task_struct |------>copy_files------>dup_fd
即先调用alloc_task_struct分配一个task_struct结构实例,然后使用alloc_files来分配并初始化files_struct变量实例。
上面的 files_struct初始化和上面例子中的静态初始化并无本质差别:
语句fdt = &newf->fdtab取出newf的struct fdtable实例变量fdtab的指针,然后通过rcu_assign_pointer函数将其赋值给newf->fdt,那么newf->fdt还是指向其自身中的struct fdtable实例变量,fdt的成员close_on_exec、open_fds和fd也是如此。
当进行struct files_struct扩充时,会分配一个新的struct fdtable,为了叙述方便,下面该变量用指针用nfdt来表示。另外还分配了满足扩充要求的fd数组(即struct file数组),以及与fd相对的bitmap描述close_on_exec,open_fds的存储空间。
然后将新分配的close_on_exec,open_fds,fd空间指针赋值给nfdt->close_on_exec,nfdt->open_fds和nfdt->fd。注意,这里的close_on_exec,open_fds和上面初始化时close_on_exec_init,open_fds_init的差别:
close_on_exec,open_fds的最大值按bit来说为__FDSET_LONGS,实际值为1024位,即文件描述符的最大数为1024个。但它们也是按需分配,并和file数组的大小一致,分配的实际值会同时赋值给nfdt->max_fds。
分配并初始化新的struct fdtable变量后,原先指向fdtab的struct files_struct指针成员fdt,会调整为指向新分配的struct fdtable变量。这时,struct files_struct实例变量中就包含两个struct fdtable存储区:一个是其自身的,一个新分配的,用fdt指向。
执行完上面的操作后,其关系如下图:
上图中同颜色的存储区,代表的是两者内容是一致的。即执行完上述的操作后,还要将旧的结构存储区的内容拷贝到新的存储区,这包括files_struct自身所包含的close_on_exec,open_fds,fd到新分配的close_on_exec,open_fds,fd的拷贝。
执行完上述拷贝之后,就要释放旧的struct fdtable
Vfsmount数据结构是vfs mount最为重要的数据结构,其维护了一个mount点的所有信息。该数据结构描述如下:
同一个文件系统被安装多次是可能的,如果同一个文件系统被安装n次,那么他的根目录可以通过n个安装点来访问,尽管同一个文件系统通过不同安装点来访问,但是文件系统确实是唯一的,这个文件系统也只有这一个super block;
安装点和安装标志以及各个安装系统之间的关系,都保存在vfdmount 中;
struct vfsmount { struct list_head mnt_hash; /* 连接到VFSMOUNT Hash Table */ struct vfsmount *mnt_parent; /* 指向mount树中的父节点 */ struct dentry *mnt_mountpoint; /* 指向mount点的目录项 */ struct dentry *mnt_root; /* 被mount的文件系统根目录项 */ struct super_block *mnt_sb; /* 指向被mount的文件系统superblock */ #ifdef CONFIG_SMP struct mnt_pcp __percpu *mnt_pcp; atomic_t mnt_longterm; /* how many of the refs are longterm */ #else int mnt_count; int mnt_writers; #endif struct list_head mnt_mounts; /* 包含所有文件系统描述符链表头,相对于这个文件系统;下级(child)vfsmount对象链表 */ struct list_head mnt_child; /* 链入上级vfsmount对象的链表点 */ int mnt_flags; /* 4 bytes hole on 64bits arches without fsnotify */ #ifdef CONFIG_FSNOTIFY __u32 mnt_fsnotify_mask; struct hlist_head mnt_fsnotify_marks; #endif const char *mnt_devname; /* 文件系统所在的设备名字,例如/dev/sdb */ struct list_head mnt_list; struct list_head mnt_expire; /* link in fs-specific expiry list */ struct list_head mnt_share; /* circular list of shared mounts */ struct list_head mnt_slave_list;/* list of slave mounts */ struct list_head mnt_slave; /* slave list entry */ struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */ struct mnt_namespace *mnt_ns; /* containing namespace */ int mnt_id; /* mount identifier */ int mnt_group_id; /* peer group identifier */ int mnt_expiry_mark; /* true if marked for expiry */ int mnt_pinned; int mnt_ghosts; };
超级快(super block)对象:
一个超级块对应一个具体的文件系统(已经安装的文件系统类型如 ext2,此处是实际的文件系统,不是 VFS)。
iNode 对象:
inode是内核文件对象的元数据,inode 仅仅只是保存了文件对象的属性信息,包括:权限、属组、数据块的位置、时间戳等信息。
file 对象:
注意 file 对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么 inode 就是唯一的,目录项也是定的!
dentry 对象:
dentry 是一个纯粹的内存结构,由文件系统在提供文件访问的过程中在内存中直接建立。dentry 中包含了文件名,文件的 inode 号等信息。
关系:
1. 超级块对象和inode对象分别对应有物理数据,在磁盘上有静态信息。
2. 目录项对象和文件对象描述的是一种关系,前者描述的文件与文件名的关系;后者描述的是进程与文件的关系,所以没有对应物理数据。例如有三个不同的进程打开同一个文件,其中有两个进程使用了相同的硬链接。三个进程拥有各自的file object,而只有两个dentry(同一个硬链接对应一个dentry,dentry不随进程打开文件而增加或改变)。两个dentry都指向同一个inode。
3. 进程每打开一个文件,就会有一个file结构与之对应。同一个进程可以多次打开同一个文件而得到多个不同的file结构,file结构描述被打开文件的属性,如文件的当前偏移量等信息。
4. 两个不同的file结构可以对应同一个dentry结构。进程多次打开同一个文件时,对应的只有一个dentry结构。
5. 在存储介质中,每个文件对应唯一的inode结点,但是每个文件又可以有多个文件名。即可以通过不同的文件名访问同一个文件。这里多个文件名对应一个文件的关系在数据结构中表示就是dentry和inode的关系。
6. Inode中不存储文件的名字,它只存储节点号;而dentry则保存有名字和与其对应的节点号,所以就可以通过不同的dentry访问同一个inode。
7. 不同的dentry则是同个文件链接(ln命令)来实现的