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 */ };
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; };
files
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 */
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,