在 Linux 系统中,一切皆文件,除了一般所说的狭义的文件(文本文件和二进制文件) 之外,目录、设备、套接字和管道等都是文件。html
文件系统在不一样的上下文中有不一样的含义:node
Linux文件系统的架构以下图所示,分为用户空间、内核空间和硬件3个层面: linux
虚拟文件系统中各数据结构之间的关系: 算法
应用程序能够直接使用内核提供的系统调用访问文件:编程
应用程序也可使用 glibc 库封装的标准 IO 流函数访问文件,标准流提供了缓冲区, 目的是尽量减小调用 read 和 write 的次数,提升性能 glic 库封装的标准流函数以下所示:数组
外部存储设备分为块设备、闪存和 NVDIMM 设备 3 类。 块设备主要有如下两种。缓存
闪存(Flash Memory)的主要特色以下。服务器
10^4~10^3
, NAND 闪存的擦除块的最大擦除次数是 10^3~10^6
。闪存按存储结构分为 NAND 闪存和 NOR 闪存,二者的区别以下。markdown
为何要针对闪存专门设计文件系统?主要缘由以下。网络
机械硬盘和 NAND 闪存的主要区别以下。
NVDIMM(Nonn-Volatile DIMM,非易失性内存:DIMM 是 Dual-Inline-Memory-Modules 的缩写,表示双列直插式存储模块,是内存的一种规格)设备把 NAND 闪存、内存和超级 电容集成到一块儿,访问速度和内存同样快,而且断电之后数据不会丢失。在断电的瞬间, 超级电容提供电力,把内存中的数据转移到 NAND 闪存。
在内核的目录 fs 下能够看到,内核支持多种文件系统类型。为了对用户程序提供统一的 文件操做接口,为了使不一样的文件系统实现可以共存,内核实现了一个抽象层,称为虚拟文件 系统( Virtual File System,VFS),也称为虚拟文件系统切换( Virtual Filesystem Switch,VFS) 文件系统分为如下 4 种。
内核把闪存称为存储技术设备( Memory Technology Device,MTD),为全部闪存实现 了统一的 MTD 层,每种闪存须要实现本身的驱动程序。
针对 NVDIMM 设备,文件系统须要实现 DAX(Direct Access直接访问:X 表明 eXciting,没有意义,只是为了让名字看起来酷),绕过页缓存和块设备层,把 NVDIMM 设备里面的内存直接映射到进程或内核的虚拟地址空间。
libnvdimm 子系统提供对 3 种 NVDIMM 设备的支持:持久内存(persistent memory,PMEM) 模式的 NVDIMM 设备,块设备(block,BLK)模式的 NVDIMM 设备,以及同时支持PMEM 和 BLK 两种访问模式的 NVDIMM 设备。PMEM 访问模式是把 NVDIMM 设备看成内存,BLK 访问模式是把 NVDIMM 设备看成块设备。每种 NVDIMM 设备须要实现本身的驱动程序。
虽然不一样文件系统类型的物理结构不一样,可是虚拟文件系统定义了一套统一的数据结构。
An individual dentry usually has a pointer to an inode. Inodes are filesystem objects such as regular files, directories, FIFOs and other beasts. They live either on the disc (for block device filesystems) or in the memory (for pseudo filesystems). Inodes that live on the disc are copied into the memory when required and changes to the inode are written back to disc. A single inode can be pointed to by multiple dentries (hard links, for example, do this).
单个 dentry 一般具备指向 inode 的指针。 inode 是文件系统对象,例如常规文件,目录,FIFO 和其余类型。 它们存在于 disc(用于块设备文件系统)或在内存中(用于伪文件系统)中。 存在于 disc 上的 inode 在须要时被复制到内存中,而且对 inode 的更改将被写回到 disc。 单个 inode 能够由多个 dentry 指向(硬连接,例如,这样作)。
注意: 这段话有三个重点:一是 inode 是用来表示文件系统对象,有些地方说是用来表明“文件”,这也没错,但却没有说这个“文件”不只仅是指常规文件,它还包括 目录,FIFO(管道文件),socket,块设备,字符设备,符号连接一共七种类型文件,因此很容易产生误解,理解这点很重要。 二是 inode 的存储位置。三是一个 inode 或者说“文件” 能够被多个 dentry 指向,这里还特地提了是硬连接,其实也就说明硬连接实际上是建立了一个 dentry ,而这个 dentry 的 d_inode 指向了同一个 inode,而对于符号连接它就是一个“文件” 也就是表示它有一个对应的 inode,而这个 inode 里面的内容是指向了另一个 inode 的路径。
To look up an inode requires that the VFS calls the lookup() method of the parent directory inode. This method is installed by the specific filesystem implementation that the inode lives in. Once the VFS has the required dentry (and hence the inode), we can do all those boring things like open(2) the file, or stat(2) it to peek at the inode data. The stat(2) operation is fairly simple: once the VFS has the dentry, it peeks at the inode data and passes some of it back to userspace.
要查找 inode, VFS 须要调用父目录 inode 的 lookup() 方法。此方法由 inode 所在的特定文件系统实现安装。 一旦 VFS 有了目标 dentry(间接可以得到 inode),咱们能够作全部那些无聊的事情,好比 open(2) 文件,或者 stat(2) 查看 inode 数据。stat(2) 操做至关简单:一旦 VFS 具备 dentry, 就能够获取 inode 的数据,并将其中一些传递回用户空间。
注意: 这段话的一个重点是咱们是使用父目录 inode 对应的 lookup()
方法来查找 inode 的,也就是说在查找当前 inode 的时候,已经确保了父目录是没问题,但有时候在查找后也会再次检查父目录有没有发生改变。
文件系统的第一块是超级块,用来描述文件系统的整体信息。当咱们把文件系统挂载 到内存中目录树的一个目录下时,就会读取文件系统的超级块,在内存中建立超级块的副 本:结构体 super_block,主要成员以下: [include/linux/fs.h]
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; ... unsigned long s_flags; unsigned long s_iflags; // internal SB_I_* flags unsigned long s_magic; struct dentry* s_root; ... 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; ... void* s_fs_info; // Filesystem private info ... }; 复制代码
超级块操做集合的数据结构是结构体 super_operations,主要成员以下: [include/linux/fs.h]
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_super) (struct super_block *); int (*freeze_fs) (struct super_block *); int (*thaw_super) (struct super_block *); int (*unfreeze_fs) (struct super_block *); int (*statfs) (struct dentry *, struct kstatfs *); int (*remount_fs) (struct super_block *, int *, char *); int (*remount_fs2) (struct vfsmount *, struct super_block *, int *, char *); void *(*clone_mnt_data) (void *); void (*copy_mnt_data) (void *, void *); void (*umount_begin) (struct super_block *); int (*show_options)(struct seq_file *, struct dentry *); int (*show_options2)(struct vfsmount *,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); 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 *); }; 复制代码
一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件系统 每次挂载文件系统,虚拟文件系统就会建立一个挂载描述符: mount 结构体。挂载描述符 用来描述文件系统的一个挂载实例,同一个存储设备上的文件系统能够屡次挂载,每次挂 载到不一样的目录下。 [include/linux/fs.h]
struct mount { struct hlist_node mnt_hash; struct mount *mnt_parent; struct dentry *mnt_mountpoint; struct vfsmount mnt; union { struct rcu_head mnt_rcu; struct llist_node mnt_llist; }; #ifdef CONFIG_SMP struct mnt_pcp __percpu *mnt_pcp; #else int mnt_count; int mnt_writers; #endif struct list_head mnt_mounts; /* list of children, anchored here */ struct list_head mnt_child; /* and going through their mnt_child */ struct list_head mnt_instance; /* mount instance on sb->s_mounts */ const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */ struct list_head mnt_list; ... struct mnt_namespace *mnt_ns; /* containing namespace */ struct mountpoint *mnt_mp; /* where is it mounted */ struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */ ... }; 复制代码
假设咱们把文件系统 2 挂载到目录 “/a” 下,目录 a 属于文件系统 1。目录 a 称为挂载 点,文件系统 2 的 mount 实例是文件系统 1 的 mount 实例的孩子,文件系统 1 的 mount 实 是文件系统 2 的 mount 实例的父亲。
struct vfsmount{ struct dentry *mnt_root; struct super_block *mnt_sb; int mnt_flags; } 复制代码mnt_root 指向文件系统 2 的根目录,mnt_sb 指向文件系统 2 的超级块。
struct mountpoint{ struct hlist_node m_hash; struct dentry *m_dentry; struct hlist_head m_list; int m_count; } 复制代码m_dentry 指向做为挂载点的目录, m_list 用来把同一个挂载点下的全部挂载描述符链 接起来。为何同一个挂载点下会有多个挂载描述符?这和挂载命名空间有关。
由于每种文件系统的超级块的格式不一样,因此每种文件系统须要向虚拟文件系统注册 文件系统类型 file_system_type,而且实现 mount 方法用来读取和解析超级块。结构体 file_system_type 以下: [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_USERNS_DEV_MOUNT 16 /* A userns mount does not imply MNT_NODEV */ #define FS_USERNS_VISIBLE 32 /* FS must already be visible */ #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 *); struct dentry *(*mount2) (struct vfsmount *, struct file_system_type *, int, const char *, void *); void *(*alloc_mnt_data) (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; }; 复制代码
在文件系统中,每一个文件对应一个索引节点,索引节点描述两类信息。
每一个索引节点有一个惟一的编号。 当内核访问存储设备上的一个文件时,会在内存中建立索引节点的一个副本:结构体 inode,主要 成员以下: [include/linux/fs.h]
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; struct address_space *i_mapping; ... /* 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; enum rw_hint i_write_hint; blkcnt_t i_blocks; ... struct hlist_node i_hash; struct list_head i_io_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; #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; }; ... void *i_private; /* fs or device private pointer */ }; 复制代码
文件分为如下几种类型。
字符设备文件、块设备文件、命名管道和套接字是特殊的文件,这些文件只有索引节点, 没有数据。字符设备文件和块设备文件用来存储设备号,直接把设备号存储在索引节点中。
内核支持两种连接。
struct inode_operations { struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); const char * (*follow_link) (struct dentry *, void **); int (*permission) (struct inode *, int); int (*permission2) (struct vfsmount *, struct inode *, int); struct posix_acl * (*get_acl)(struct inode *, int); int (*readlink) (struct dentry *, char __user *,int); void (*put_link) (struct inode *, 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 (*rename2) (struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*setattr) (struct dentry *, struct iattr *); int (*setattr2) (struct vfsmount *, 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); int (*tmpfile) (struct inode *, struct dentry *, umode_t); int (*set_acl)(struct inode *, struct posix_acl *, int); } ____cacheline_aligned; 复制代码
lookup 方法用来在一个目录下查找文件。 系统调用 open 和 creat 调用 create 方法来建立普通文件,系统调用 link 调用 link 方法 来建立硬连接,系统调用 symlink 调用 symlink 方法来建立符号连接,系统调用 mkdir 调用 mkdir 方法来建立目录,系统调用 mknod 调用 mknod 方法来建立字符设备文件、块设备文件、 命名管道和套接字。 系统调用 unlink 调用 unlink 方法来删除硬连接,系统调用 rmdir 调用 rmdir 方法来删除目录。 系统调用 rename 调用 rename 方法来给文件换一个名字。 系统调用 chmod 调用 setattr 方法来设置文件的属性,系统调用 stat 调用 getattr 方法来建立目录读取文件的属性。 系统调用 listxattr 调用 listxattr 方法来列出文件的全部的扩展属性。
文件系统把目录当作文件,这种文件的数据是有目录项组成的,每一个目录项存储一个子目录或文件的名称以及对应的索引节点号。 当内核访问存储设备上的一个目录项时,会在内存中建立目录项的一个副本:结构体 dentry,主要成员以下: [include/linux/dcache.h]
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 */ 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 */ void *d_fsdata; /* fs-specific data */ struct list_head d_lru; /* LRU list */ struct list_head d_child; /* child of parent list */ struct list_head d_subdirs; /* our children */ /* * d_alias and d_rcu can share memory */ union { struct hlist_node d_alias; /* inode alias list */ struct rcu_head d_rcu; } d_u; }; 复制代码
以文件 “/a/c.txt” 为例,目录项和索引节点的关系以下:
目录项操做集合的数据结构是结构体 dentry_operations,其代码以下:
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 *, const struct dentry *, 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); struct inode *(*d_select_inode)(struct dentry *, unsigned); struct dentry *(*d_real)(struct dentry *, struct inode *); void (*d_canonical_path)(const struct path *, struct path *); } ____cacheline_aligned; 复制代码
参考 vfs.txt 文档:
d_revalidate: called when the VFS needs to revalidate a dentry. This is called whenever a name look-up finds a dentry in the dcache. Most local filesystems leave this as NULL, because all their dentries in the dcache are valid. Network filesystems are different since things can change on the server without the client necessarily being aware of it. This function should return a positive value if the dentry is still valid, and zero or a negative error code if it isn't. d_revalidate may be called in rcu-walk mode (flags & LOOKUP_RCU).If in rcu-walk mode, the filesystem must revalidate the dentry without blocking or storing to the dentry, d_parent and d_inode should not be used without care (because they can change and, in d_inode case, even become NULL under us). If a situation is encountered that rcu-walk cannot handle, return -ECHILD and it will be called again in ref-walk mode.
当 VFS 须要验证一个 dentry 有效性时会被调用。在 dcache 中找到 name 对应的 dentry 的时候就被调用。大多数本地文件系统直接返回 NULL,由于这类文件系统在 dcache 中全部的 dentry 都是有效的。可是网络文件系统不同,由于事情会在服务器上已经发生了,可是客户端殊不知道。这个函数应该返回一个正值来表示该 dentry 依然是有效的,0 或者 负数来表示错误。
d_automount
: called when an automount dentry is to be traversed (optional).This should create a new VFS mount record and return the record to the caller. The caller is supplied with a path parameter giving the automount directory to describe the automount target and the parent VFS mount record to provide inheritable mount parameters. NULL should be returned if someone else managed to make the automount first. If the vfsmount creation failed, then an error code should be returned. If-EISDIR
is returned, then the directory will be treated as an ordinary directory and returned to pathwalk to continue walking.
当碰到一个自动挂载 automount 目录项时,d_automount
会被调用。(可选的)。它应该建立一个新的 VFS 挂载记录,而且把这个记录返回给调用者。为调用者提供了一个 path
参数,该参数给出了automount
目录(用于描述 automount
目标)和父 VFS 挂载记录(以提供可继承的挂载参数)。若是已经有其余调用者 在该 automount
作了处理,那么返回 NULL。若是 vfsmount
建立失败那么返回错误码。若是返回了 -EISDIR
,那么该目录被当成普通目录而且返回到路径查找流程继续下一步查找。
If a vfsmount is returned, the caller will attempt to mount it on the mountpoint and will remove the vfsmount from its expiration list in the case of failure. The vfsmount should be returned with 2 refs on it to prevent automatic expiration - the caller will clean up the additional ref.
若是返回了 vfsmount
,那么调用者将会尝试把它挂载在该挂载点上。而且若是挂载失败,则会将 vfsmount
从它的 expiration 列表中移除。vfsmount
在返回时应该带有两个引用(指向它本身的引用)用来防止 自动卸载(过时)。调用者会清除附加的引用。
This function is only used if
DCACHE_NEED_AUTOMOUNT
is set on the dentry. This is set by__d_instantiate()
ifS_AUTOMOUNT
is set on the inode being added.
只有 dentry 被设置了 DCACHE_NEED_AUTOMOUNT
标志,该函数才会被使用。若是 inode 的 S_AUTOMOUNT
被设置了,那么能够经过 __d_instantiate()
函数来设置 DCACHE_NEED_AUTOMOUNT
标志。
d_manage
: called to allow the filesystem to manage the transition from a dentry (optional). This allows autofs, for example, to hold up clients waiting to explore behind a 'mountpoint' whilst letting the daemon go past and construct the subtree there. 0 should be returned to let the calling process continue. -EISDIR can be returned to tell pathwalk to use this directory as an ordinary directory and to ignore anything mounted on it and not to check the automount flag. Any other error code will abort pathwalk completely.
d_manage
容许文件系统来管理一个 dentry 的转换。(可选的)。它容许使用 autofs
。例如:挂起一个 等待去探索某 mountpoint
下路径的客户端,与此同时让守护进程经过该挂载点而且在其下构建子树。返回 0 表示让挂起的客户端继续执行。返回 -EISDIR
,表示路径查找进程应该把该目录当作普通目录处理,而且忽略在其上挂载的任何东西,以及不会去检查 automou
标志。其它任何错误码表示应该中断本次路径查找。
If the 'rcu_walk' parameter is true, then the caller is doing a pathwalk in RCU-walk mode. Sleeping is not permitted in this mode, and the caller can be asked to leave it and call again by returning
-ECHILD
.-EISDIR
may also be returned to tell pathwalk to ignore d_automount or any mounts.
若是 'rcu_walk' 参数为 true,那么调用者的路径查找处于 RCU 模式。这种模式下是不容许睡眠,因此 调用者被要求离开该函数而且返回 -ECHILD
。一样的,返回 -EISDIR
告诉当前路径查找忽略 d_automount
或者其余任何挂载。
This function is only used if
DCACHE_MANAGE_TRANSIT
is set on the dentry being transited from.
该函数只有当前遍历的 dentry 被设置了 DCACHE_MANAGE_TRANSIT
标志是才能使用。
d_hash: called when the VFS adds a dentry to the hash table. The first dentry passed to d_hash is the parent directory that the name is to be hashed into.
VFS 添加一个 dentry 到散列表的时候被调用。第一个参数是父 dentry。
d_compare 用来比较两个目录项的文件系统。 d_delete 用来在目录项的引用计数减到 0 时判断是否能够释放目录项的内存。 d_release 用来在释放目录项的内存以前调用。 d_iput 用来释放目录项关联的索引节点。
当进程打开一个文件的时候,虚拟文件系统就会建立文件的一个打开实例:file 结构体,主要成员以下:
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u; struct path f_path; 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; 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; #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 */ 复制代码
struct path { struct vfsmount *mnt; struct dentry *dentry; }; 复制代码mnt 指向文件所属文件系统的挂载描述符的成员 mnt,dentry 是文件对应的目录项。
文件的打开实例和索引节点的关系如图:
[include/linux/sched.h]
struct task_struct{ ... struct fs_struct *fs; struct files_struct *files; ... } 复制代码
文件系统信息结构体的主要成员以下:
[include/linux/fs_struct.h]
struct fs_struct{ ... struct path root, pwd; } 复制代码
成员 root 存储进程的根目录,成员 pwd 存储进程的当前工做目录。 假设首先调用系统调用 chroot,把目录“/a” 设置为进程的根目录,而后建立子进程,子进程继承 父进程的文件系统信息,那么把子进程能看到的目录范围限制为以目录 “/a” 为根的子树。但子进程打开文件 “/b.txt”(文件路径是绝对路径,以“/”开头)时,真实的文件路径是 “/a/b.txt”。 假设调用系统调用 chdir,把目录 “/c” 设置为进程的当前工做目录,当子进程打开文件 “d.txt” (文件路径是相对路径,不以“/”开头)时,真实的文件路径是 “/c/d.txt”。
打开文件表也称为文件描述符表,数据结构以下图:
[include/linux/fdtable.h]
struct files_struct { /* * read mostly part */ atomic_t count; bool resize_in_progress; wait_queue_head_t resize_wait; struct fdtable __rcu *fdt; struct fdtable fdtab; /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; int next_fd; unsigned long close_on_exec_init[1]; unsigned long open_fds_init[1]; unsigned long full_fds_bits_init[1]; struct file __rcu * fd_array[NR_OPEN_DEFAULT]; }; 复制代码
成员 count 是结构体 files_struct 的引用计数。 成员 fdt 指向打开文件表。 当进程刚刚建立的时候,成员 fdt 指向成员 fdtab,运行一段时间后,进程打开的文件数量超过了 NR_OPEN_DEFAULT,就会扩大打开文件表,从新分配 fdtable 结构体,成员 fdt 指向新的 fdtable 结构体。 打开文件表的数据以下: [include/linux/fdtable]
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; }; 复制代码
成员 max_fds 是打开文件表的当前大小,即成员 fd 指向的 file 指针数组的大小。随着进程 打开文件的数量增长,打开文件表逐步扩大。 成员 fd 指向 file 指针数组。当进程调用 open 打开文件的时候,返回的文件描述符是 file 指针 数组的索引。 成员 close_on_exec 指向一个位图,指示在指向 execve() 以装载新程序的时候须要关闭哪些文件 描述符。 成员 open_fds 指向文件描述符位图,指示哪些文件描述符被分配。
The VFS implements the open(2), stat(2), chmod(2), and similar system calls. The pathname argument that is passed to them is used by the VFS to search through the directory entry cache (also known as the dentry cache or dcache). This provides a very fast look-up mechanism to translate a pathname (filename) into a specific dentry. Dentries live in RAM and are never saved to disc: they exist only for performance.
VFS实现 open(2)、stat(2)、chmod(2) 和相似的系统调用。传递给这些函数的路径名参数被 VFS 用来在目录项缓存(也称为 dentry 缓存或 dcache)进行搜索目录项。这提供了一个很是快速的查找机制,将路径名(filename)转换为一个特定的 dentry。这些 dentry 存在内存中,并无保存到磁盘:它们的存在于只是为了提升性能。
The dentry cache is meant to be a view into your entire filespace. As most computers cannot fit all dentries in the RAM at the same time, some bits of the cache are missing. In order to resolve your pathname into a dentry, the VFS may have to resort to creating dentries along the way, and then loading the inode. This is done by looking up the inode.
dentry 缓存是指向整个文件空间的视图。因为大多数计算机不能同时在 RAM 中容纳全部的 dentry,缓存中的一些位丢失了。为了将路径名解析为 dentry, VFS 可能不得不在此过程当中建立 dentry,而后加载 inode。这是经过查找 inode 完成的。
有几个文件系统用来维护目录项的函数:
dget:对一个已经存在的 dentry 打开一个新的句柄(handle)(只是增长 dentry 的使用计数)。
dput: 对一个 dentry 关闭一个句柄(减小使用计数)。若是使用计数变为 0,而且这个 dentry 仍是它父目录的 hash 表中,那么 "d_delete" 方法被调用,用来检查它是否应该被缓存。若是 不该该被缓存,或者该 dentry 没有被 hash,那么删除它。不然把该已缓存的 dentry 移动到 LRU 链表中,未来当内存不够时能够回收它。
d_drop:它用来从父目录的 hash 表中 unhashes 一个 dentry。若是此时它的使用计数(usage count) 已经降到 0,随后调用 dput() 来释放这个(deallocate) dentry,;
d_delete:删除一个 dentry。若是它没有其余开放的引用,那么它变成一个 negative dentry, 而后 d_iput() 被调用。若是有其余引用,那么调用 d_drop。
d_add: 增长一个 dentry 到它的父目录的 hash 列表中,而后调用 d_instantiate()。
d_instantiate: 增长一个 dentry 到相应 inode 的 alise hash 链表中,而且更新 d_inode
成员。 inode 的 i_count
成员应该被 “set/incremented”。若是该 dentry 的 inode 指针为空, 那么该 dentry 称为 "negative dentry"。该函数一般在为 "negative dentry" 建立一个 inode 时被调用。
d_lookup: 给定一个父目录和路径名称份量,而后查找相应的 dentry。它从缓存(decahe)的散列表中 经过给定的名称查找相应的子项。若是查找到了,增长引用计数并返回 dentry。当完成使用后, 调用者必须调用 dput() 来释放这个 dentry。
由于每种文件系统的超级块的格式不一样,因此每种文件系统须要向虚拟文件系统注册文件系统类型 file_system_type, 实现 mount 方法用来读取和解析超级块。 函数 register_filesystem 用来注册文件系统类型: int register_filesystem(struct file_system_type *fs);
函数 unregister_filesystem 用来注销文件系统类型: int unregister_filesystem(struct file_system_type *fs);
管理员能够执行命令 “cat/proc/filesystems” 来查看已经注册的文件系统类型。
最后给出两张数据结构图:第一张基于 Linux 4.4,后一张基于 Linux 3.x 版本。图片较大,可先点击放大后再右键“在新标签页中打开图片”,可查看高清图。
参考:
《Linux 内核深度解析》余华兵 著