Linux VFS数据结构

Posted AlanTu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux VFS数据结构相关的知识,希望对你有一定的参考价值。

先说明一下,linux内核中各种数据结构也不停的在变,所以不同版本的内核各个数据结构的定义可能会差别很大,这一组关于linux 文件系统的文章中的代码都摘自linux-2.6.34.1。

 

VFS依赖于数据结构来保存其对于一个文件系统的一般表示。

超级块结构:存放已安装的文件系统的相关信息

索引节点结构:对于文件信息的完全的描述

文件结构:存放一个被进程打开的文件的相关信息

目录项结构:存放有关路径名及路径名所指向的文件的信息

组成VFS的结构与一些操作相关联,这些操作可应用于有这些结构所表示的对象。这些操作在每个对象的操作表中定义。操作表示函数指针的集合。

 

2.1超级块结构

当文件系统被挂载时,所有有关它的信息均被存放在super_block结构体中。每个安装好的文件系统都有一个超级块结构。这个结构体定义如下:

---------------------------------------------------------------------

技术分享图片
include/linux/fs.h

1319 struct super_block {

1320   struct list_head        s_list;         /* Keep this first */

1321   dev_t             s_dev;     /* search index; _not_ kdev_t */

1322   unsigned char           s_dirt;

1323   unsigned char           s_blocksize_bits;

1324   unsigned long           s_blocksize;

1325   loff_t                  s_maxbytes;     /* Max file size */

1326   struct file_system_type *s_type;

1327   const struct super_operations   *s_op;

1328   const struct dquot_operations   *dq_op;

1329   const struct quotactl_ops       *s_qcop;

1330   const struct export_operations *s_export_op;

1331   unsigned long           s_flags;

1332   unsigned long           s_magic;

1333   struct dentry           *s_root;

1334   struct rw_semaphore     s_umount;

1335   struct mutex            s_lock;

1336   int                     s_count;

1337   int                     s_need_sync;

1338   atomic_t                s_active;

1339 #ifdef CONFIG_SECURITY

1340   void                    *s_security;

1341 #endif

1342   struct xattr_handler    **s_xattr;

1343

1344   struct list_head        s_inodes;       /* all inodes */

1345   struct hlist_head  s_anon;/* anonymous dentries for (nfs) exporting */

1346   struct list_head        s_files;

1347   /* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */

1348   struct list_head        s_dentry_lru;   /* unused dentry lru */

1349   int          s_nr_dentry_unused;     /* # of dentry on lru */

1350

1351   struct block_device     *s_bdev;

1352   struct backing_dev_info *s_bdi;

1353   struct mtd_info         *s_mtd;

1354   struct list_head        s_instances;

1355   struct quota_info  s_dquot;  /* Diskquota specific options */

1356

1357   int                     s_frozen;

1358   wait_queue_head_t       s_wait_unfrozen;

1359

1360   char s_id[32];                   /* Informational name */

1361

1362   void            *s_fs_info;     /* Filesystem private info */

1363   fmode_t                 s_mode;

1364

1365   /* Granularity of c/m/atime in ns.

1366      Cannot be worse than a second */

1367   u32                s_time_gran;

1368

1369 /*

1370  * The next field is for VFS *only*. No filesystems have any business

1371  * even looking at it. You had been warned.

1372  */

1373   struct mutex s_vfs_rename_mutex;        /* Kludge */

1374

1375 /*

1376  * Filesystem subtype.  If non-empty the filesystem type field

1377  * in /proc/mounts will be "type.subtype"

1378  */

1379   char *s_subtype;

1380

1381   /*

1382    * Saved mount options for lazy filesystems using

1383    * generic_show_options()

1384    */

1385   char *s_options;

1386 };
技术分享图片

 

---------------------------------------------------------------------

一些重要成员的说明:

s_list:指向双向循环链表中前一个和下一个元素的指针。如同linux内核中的许多其他结构一样,super_block也用双向循环链表维护,而正是这个成员将系统中安装的文件系统的super_block相互链接起来。全局变量super_blocks(fs/super.c,LIST_HEAD(super_blocks);)指向链表中的第一个元素。

 

s_dirt:在基于磁盘的文件系统中,用最初存放于磁盘上特定扇区中的数据会被用来填充超级块结构的一些字段。VFS允许对super_block进行修改,而这个成员正是用于说明超级块结构是否被修改,也即是否与磁盘上的数据一致。以便于在适当的时候将超级块数据写回磁盘。

 

s_maxbytes:这个成员用于说明文件系统支持的文件的最大长度。

 

s_type:超级块结构包含文件系统通用的信息,而这个成员则使超级块与特定的文件系统类型(如ext2,NFS等)关联起来。file_system_type结构体保存具体文件系统的信息。这个成员指向适当具体文件系统的结构体,VFS利用它可管理从一般请求到具体文件系统操作的交互。

下图显示了file_system_type和超级块之间的关系:

 

 

s_op:super_operations结构体类型的指针,这个结构体保存超级块的操作函数表。super_operations结构体由一些函数指针组成,这些函数指针用特定文件系统的超级块操作函数来初始化。

 

s_root:这个成员是一个dentry结构体指针,dentry结构体用于保存目录项。这个成员指向该文件系统安装目录对应的目录项。

 

s_inodes:是一个list_head结构体,指向属于该文件系统的索引节点的双向链表。

 

s_files:这个成员文件结构体链表,这个文件结构体不但正在使用,而且已分配给超级块。

 

s_instances:指向相同的文件系统类型的超级块链表中相邻的超级块元素。

 

s_fs_info:指向特定文件系统的超级块信息的指针。

 

2.2超级块操作结构

超级块的s_op成员指向文件系统的超级块操作结构,他描述了VFS能够如何管理特定的文件系统的超级块。每个具体的文件系统都可以定义自己的超级快操作,因为它直接针对文件系统的实现进行操作。

---------------------------------------------------------------------
技术分享图片
include/linux/fs.h
1558 struct super_operations {

1559   struct inode *(*alloc_inode)(struct super_block *sb);

1560   void (*destroy_inode)(struct inode *);

1561

1562   void (*dirty_inode) (struct inode *);

1563 int (*write_inode) (struct inode *, struct writeback_control *wbc);

1564   void (*drop_inode) (struct inode *);

1565   void (*delete_inode) (struct inode *);

1566   void (*put_super) (struct super_block *);

1567   void (*write_super) (struct super_block *);

1568   int (*sync_fs)(struct super_block *sb, int wait);

1569   int (*freeze_fs) (struct super_block *);

1570   int (*unfreeze_fs) (struct super_block *);

1571   int (*statfs) (struct dentry *, struct kstatfs *);

1572   int (*remount_fs) (struct super_block *, int *, char *);

1573   void (*clear_inode) (struct inode *);

1574   void (*umount_begin) (struct super_block *);

1575

1576   int (*show_options)(struct seq_file *, struct vfsmount *);

1577   int (*show_stats)(struct seq_file *, struct vfsmount *);

1578 #ifdef CONFIG_QUOTA

1579   ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);

1580   ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);

1581 #endif

1582   int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);

1583 };
技术分享图片

 

---------------------------------------------------------------------

超级快操作的说明:

  alloc_inode: 这个方法由alloc_inode()函数调用来为struct inode 分配内存并初始化它。如果这个函数没有定义,则会简单的从inode slab缓存中分配一个struct inode。通常alloc_inode 被用来分配一个更大的但内嵌有struct inode的结构体,即特定文件系统的索引节点,会包含文件指针等信息。

 

  destroy_inode: 这个方法由destroy_inode()函数调用,以释放分配的struct inode。只有当->alloc_inode 有定义的时候才需要它,它只是简单的撤销->alloc_inode 所做的一切。

 

  dirty_inode: 这个方法被VFS调用,以标记一个inode为dirty,即标记文件的管理元数据被修改过了,在适当的时候要将inode写回磁盘。

 

  write_inode: 当VFS需要将一个inode 写回磁盘的时候调用这个方法。第二个参数用以说明写回是否为同步的,并不是所有的文件系统都会检查这个标志。

 

  drop_inode: 在即将撤销索引节点时调用,即,当最后一个用户释放该索引节点时。实现该方法的文件系统通常使用generic_delete_inode函数。该函数从VFS数据结构中移走对索引节点的每一个引用,如果索引节点不再出现在任何目录中,则调用超级快方法delete_inode将它从文件系统中删除。调用时要持有inode_lock自旋锁。

 

  delete_inode: 当VFS想要删除(delete)一个inode 时调用,删除内存中的VFS索引节点和磁盘上的文件数据及元数据。

 

  put_super: 当VFS想要释放superblock 时调用(比如unmount)。在持有superblock 锁时调用。

 

  write_super: 当VFS 要向磁盘写回superblock时调用。

 

  sync_fs: 当VFS写完所有的与superblock 相关的“脏”的数据之后调用。第二个参数用以说明这个方法是否需要等待直到写回操作完成。(由日志文件系统使用)。

 

  freeze_fs: 当VFS锁定一个文件系统并强制它进入一致性状态时调用。这个方法现在由Logical Volume Manager (LVM)使用。

 

  unfreeze_fs: 当VFS解除锁定一个文件系统并再次使它可写是调用。

 

  statfs: 当VFS需要获得文件系统统计量时调用。

 

  remount_fs: 当文件系统需要被重新挂载时调用。持有内核锁时调用。

 

  clear_inode: 当VFS清除(clear)inode 时调用。可选。

 

  umount_begin: 当VFS卸载一个文件系统时调用。

 

  show_options:被VFS调用来为/proc/<pid>/mounts显示挂载选项 。 (参考 "挂载选项" 部分)

 

  quota_read: 限额系统使用该方法从文件中读取数据,该文件详细说明了所在文件系统的限制。

 

  quota_write: 限额系统使用该方法将数据写入文件中,该文件详细说明了所在文件系统的限制。

 

这些方法对所有的文件系统类型均是可用的。但是,对于特定的文件系统而言,则只使用其中的一个子集。未实现的方法对应的字段置为NULL。

 

2.3索引节点对象

文件系统处理文件所需要的信息都放在一个名为索引节点的数据结构中。具体文件系统的索引节点则根据文件系统类型的不同不同,inode节点有在磁盘上存在的静态节点,为UNIX-like系统开发的文件系统大多使用的是静态inode节点,比如ext2,ext3。还有仅仅存在于内存中的动态节点的文件系统,比如为windows系统开发的文件系统,FAT32,MSDOS,NTFS等。linux下文件系统的实现根据相关的文件系统协议,利用磁盘上存储的文件管理元数据来实现inode节点对象。内核中使用的众多的伪文件系统自然也是用的动态inode节点,因为整个文件系统就仅仅存在于内存而已。内存中的索引节点对象由一个inode结构体来表示,其定义如下

---------------------------------------------------------------------
技术分享图片
include/linux/fs.h
struct inode {

   struct hlist_node  i_hash; /* 用于散列链表的指针 */

    /* 用于描述索引节点当前状态的链表的指针 */

   struct list_head   i_list;     /* backing dev IO list */

    struct list_head  i_sb_list; /* 用于超级块的索引节点链表的指针 */

   struct list_head   i_dentry; /* 引用索引节点的目录项对象链表的头 */

   unsigned long      i_ino;    /* 索引节点号 */

   atomic_t    i_count;      /* 索引节点引用计数 */

   unsigned int    i_nlink;   /* 硬链接数目*/

   uid_t       i_uid;

   gid_t       i_gid;

   dev_t       i_rdev;

   unsigned int    i_blkbits; /* 块的位数 */

   u64         i_version;

   loff_t      i_size; /* 文件的字节数 */

#ifdef __NEED_I_SIZE_ORDERED

   seqcount_t      i_size_seqcount;

#endif

   struct timespec    i_atime;

   struct timespec    i_mtime;

   struct timespec    i_ctime;

   blkcnt_t    i_blocks;  /* 文件的块数*/

   unsigned short          i_bytes; /* 文件中最后一个块的字节数*/

   umode_t         i_mode;

   spinlock_t      i_lock;  /* i_blocks, i_bytes, maybe i_size */

   struct mutex    i_mutex;

   struct rw_semaphore   i_alloc_sem;

   const struct inode_operations  *i_op;

/* former ->i_op->default_file_ops */

   const struct file_operations *i_fop;

   struct super_block *i_sb;

   struct file_lock   *i_flock; /* 指向文件锁链表的指针 */

   struct address_space  *i_mapping;

   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_mark_entries; /* fsnotify mark entries */

#endif

 

#ifdef CONFIG_INOTIFY

   struct list_head   inotify_watches; /* watches on this inode */

   struct mutex    inotify_mutex;  /* protects the watches list */

#endif

 

   unsigned long      i_state;  /* 索引节点的状态标志,如dirty等 */

   unsigned long      dirtied_when;   /* jiffies of first dirtying */

 

   unsigned int    i_flags; /* 文件系统的安装标志*/

 

   atomic_t    i_writecount; /* 用于写进程的引用计数 */

#ifdef CONFIG_SECURITY

   void        *i_security;

#endif

#ifdef CONFIG_FS_POSIX_ACL

   struct posix_acl   *i_acl;

   struct posix_acl   *i_default_acl;

#endif

   void        *i_private; /* fs or device private pointer */

};
技术分享图片
---------------------------------------------------------------------

这是通用的inode结构,它主要是将索引节点的通用部分组织起来,这个结构会被嵌入到特定的文件系统的索引节点结构中,而后者则会包含一些具体文件系统数据组织的信息,比如文件的块指针等。

 

每个索引节点对象都会复制磁盘索引节点中包含的一些数据,比如分配给文件的磁盘块数,访问时间、修改时间,文件拥有者的UID、GID等。如果i_state字段的值等于I_DIRTY_SYNC、 I_DIRTY_DATASYNC 或 I_DIRTY_PAGES,则该索引节点是“脏”的,也就是说,索引节点被修改,对应的磁盘索引节点必须被更新。I_DIRTY宏可以用来检查这三个标志的值。i_state字段的其他值有I_NEW(索引节点对象已经分配但还没有用从磁盘索引节点读取来的数据进行填充)、I_WILL_FREE、I_FREEING(索引节点对象正在被释放)、I_CLEAR(索引节点对象的内容不再有意义)、I_SYNC

每个索引节点对象总是出现在两个不同的索引节点表中。首先,是inode的哈希表,这主要是为了快速查询,前提是系统内核要知道索引节点号及文件系统对应的超级块对象的地址。inode.c文件中的hlist_head类型指针静态变量inode_hashtable为哈希表的表头,哈希表的元素也同样为hlist_head类型, inode结构的hlist_node类型的i_hash字段用来将inode和哈希表链接起来。

 

它还会出现在另外两个“类型”双向循环链表中的某个链表里(使用i_list与相应的链表链接起来):

有效但未使用的索引节点链表,该链表中的索引节点未被任何进程使用。它们的i_count字段值为0。链表中的首元素和尾元素是由全局变量inode_unused(fs/inode.c中, LIST_HEAD(inode_unused);)的next字段和prev字段分别指向的。这个链表用作磁盘高速缓存。

 

正在使用的索引节点链表,该链表里的索引节点当前被某些进程使用。它们的i_count字段大于零,i_nlink字段大于零。链表中的首元素和尾元素是由全局变量inode_in_use(fs/inode.c, LIST_HEAD(inode_in_use);)引用的。

最后,它还会出现在特定文件系统,也就是特定super_block的双向循环索引节点表中,由super_block的s_inodes字段和inode结构的i_sb_list字段连接。

 

索引节点结构与超级块之间的关系如下图:

 

2.4索引节点操作

与索引节点对象相关联的方法也叫索引节点操作。由inode_operations来描述,该结构的地址存放在索引节点的i_op字段中。定义如下:

---------------------------------------------------------------------

技术分享图片
include/linux/fs.h

struct inode_operations {

   int (*create) (struct inode *,struct dentry *,int, struct nameidata *);

   struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);

   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 *,int);

   int (*rmdir) (struct inode *,struct dentry *);

   int (*mknod) (struct inode *,struct dentry *,int,dev_t);

   int (*rename) (struct inode *, struct dentry *,

         struct inode *, struct dentry *);

   int (*readlink) (struct dentry *, char __user *,int);

   void * (*follow_link) (struct dentry *, struct nameidata *);

   void (*put_link) (struct dentry *, struct nameidata *, void *);

   void (*truncate) (struct inode *);

   int (*permission) (struct inode *, int);

   int (*check_acl)(struct inode *, int);

   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 *);

   void (*truncate_range)(struct inode *, loff_t, loff_t);

   long (*fallocate)(struct inode *inode, int mode, loff_t offset,

           loff_t len);

   int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,

            u64 len);

};
技术分享图片

---------------------------------------------------------------------

  create(dir, dentry, mode, nameidata): 被open(2) and creat(2)系统调用所调用。在某一个目录下,为与目录项对象相关的普通文件创建一个新的磁盘索引节点。

 

  lookup(dir, dentry, nameidata): 为包含在一个目录项对象中的文件名对应的索引节点查找目录项。

 

  link(old_dentry, dir, new_dentry): 创建一个新的名为new_dentry的硬链接,它指向dir目录下名为old_dentry的文件

 

  unlink(dir, dentry): unlink(2)系统调用调用。从一个目录中删除目录项对象所指定的文件的硬链接。

 

  symlink(dir, dentry, symname): symlink(2)系统调用调用。在某个目录下,为与目录项对象相关的文件创建一个新的索引节点及目录项。

 

  mkdir(dir, dentry, mode):系统调用 mkdir(2)调用。在某个目录下,为与目录项对象相关的目录创建一个新的索引节点。

 

  rmdir(dir, dentry): 从一个目录中删除子目录,子目录的名称包含在目录项对象中。

 

  mknod(dir, dentry, mode, rdev): 在某个目录中,为与目录项对象相关的特定设备文件创建一个新的磁盘索引节点。其中参数mode和rdev分别表示文件的类型和设备号。

 

  rename(old_dir, old_dentry, new_dir, new_dentry): 系统调用rename(2)调用来重命名对象,将old_dir目录下由old_dentry标识的文件移到new_dir目录下。新文件名包含在new_dentry指向的目录项对象中。

 

  readlink(dentry, buffer, buflen): 系统调用readlink(2)调用。将目录项所指定的符号链接指向的文件的路径名拷贝到buffer所指定的用户态内存缓冲区中。

 

  follow_link(inode, nameidata): 解析索引节点对象所指定的符号链接;如果该符号链接是一个相对路径名,则从第二个参数所指定的目录开始进行查找。

 

  put_link(dentry, nameidata): 释放由follow_link方法分配的用于解析符号链接的所有临时数据结构。

 

  truncate(inode): 修改与索引节点相关的文件的文件大小。在调用该方法之前,必须将inode对象的i_size字段设置为需要的新长度值

 

  permission(inode, mask, nameidata): 在一个POSIX-like 文件系统上由VFS调用来检查访问权限。

 

  setattr(dentry, iattr): 由VFS调用来为一个文件设置属性。这个方法由chmod(2)及相关的系统调用来调用。

 

  getattr(mnt, dentry, kstat): 由VFS调用来获得一个文件的属性。这个方法由stat(2)及相关的系统调用来调用。

 

  setxattr(dentry, name, value, size, flags): 由VFS调用来设置一个文件的扩展属性。扩展属性是一个与一个inode关联的name:value对,存放在任何索引节点之外的磁盘块中。这个方法由系统调用setxattr(2)调用。

 

  getxattr(dentry, name,buffer, size): 由VFS调用来检索一个扩展属性名的值。这个方法由getxattr(2)函数调用。

 

  listxattr(dentry, buffer, size): 由VFS调用来列出给定文件的所有的扩展属性。这个方法由系统调用listxattr(2)调用。

 

  removexattr(dentry, name): 由VFS调用来从一个文件移除(remove)一个扩展属性。这个方法由系统调用removexattr(2)调用。

 

  truncate_range: 一个由底层文件系统提供来截断块的范围的方法,比如在一个文件的某些地方打洞。

 

上面的方法对所有可能的索引节点和文件系统类型都是可用的。不过,只有其中的一个子集应用到某一个特定单位文件系统的索引节点和文件系统;为实现的方法对应的字段应被设置为NULL。

 

inode_operations中对inode操作的方法和super_operations中队inode操作的方法相比则要更高级一些,而后者则更靠近底层,即更接近对磁盘硬件设备的操作。

 

2.5 目录项对象

对于进程查找的路径名中的每个分量,内核都为其创建一个目录项对象;目录项对象将每个分量与其对应的索引节点相联系。目录项对象在磁盘上没有对应的映像,它们存放在目录项缓存中。它们是一些根据目录文件的内容,填充的一些内存中的结构。其定义如下:

---------------------------------------------------------------------
技术分享图片
include/linux/dcache.h

struct dentry {

   atomic_t d_count;

    /* 目录项高速缓存标志 */

   unsigned int d_flags;    /* protected by d_lock */

   spinlock_t d_lock;    /* per dentry lock */

/* 对于目录而言,用于记录安装该目录项的文件系统的计数器*/

   int d_mounted;

   struct inode *d_inode;      /* Where the name belongs to - NULL is

                 * negative */

   /*

    * The next three fields are touched by __d_lookup.  Place them here

    * so they all fit in a cache line.

    */

   struct hlist_node d_hash;   /* lookup hash list */

   struct dentry *d_parent; /* parent directory */

   struct qstr d_name;  /* 文件名 */

    /* 用于未使用目录项链表的指针 */

   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 */

        /* 回收目录项对象时,有RCU描述符使用 */

      struct rcu_head d_rcu;

   } d_u;

    /* 对于目录而言,子目录项链表的头 */

   struct list_head d_subdirs; /* our children */

    /* 用于与同一索引节点相关的目录项链表指针 */

   struct list_head d_alias;   /* inode alias list */

   unsigned long d_time;    /* used by d_revalidate */

   const struct dentry_operations *d_op;

   struct super_block *d_sb;   /* The root of the dentry tree */

   void *d_fsdata;       /* fs-specific data */

 

   unsigned char d_iname[DNAME_INLINE_LEN_MIN];   /* small names */

};
技术分享图片

 

---------------------------------------------------------------------

每个目录项对象可以处于以下四种状态之一:

空闲状态(free)

    处于该状态的目录项对象不包括有效的信息,且还没有被VFS使用。对应的内存区由slab分配器进行处理。

未使用状态(unused)

    处于该状态的目录项对象当前还没有被内核使用。该对象的引用计数器d_count的值为0,但其d_inode字段仍然指向关联的索引节点。该目录项对象包含有效的信息,但为了在必要时回收内存,它的内容可能被丢弃。

正在使用状态(in use)

    处于该状态的目录项对象当前正在被内核使用。该对象的引用计数器d_count的值为正数,其d_inode字段指向关联的索引节点对象。该目录项对象包含有效的信息,并且不能丢弃。

负状态(negative)

    与目录项关联的索引节点不存在,那是因为相应的磁盘索引节点已被删除,或者因为目录项对象是通过解析一个不存在的路径名创建的。目录项对象的d_inode字段被置为NULL,但该对象仍然被保存在目录项高速缓存中,以便后续对同一文件目录名的查找操作能够快速完成。

 

与目录项关联的方法称为目录项操作,它描述了一个文件系统是如何重载一个标准的dentry operations。这些方法由dentry_operations结构体描述,该结构的地址存放在目录项对象的d_op字段中。由于他们是可选的或者VFS使用默认的例程,这些方法可能被设为NULL。其定义如下:

---------------------------------------------------------------------
技术分享图片
include/linux/dcache.h

struct dentry_operations {

   int (*d_revalidate)(struct dentry *, struct nameidata *);

   int (*d_hash) (struct dentry *, struct qstr *);

   int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);

   int (*d_delete)(struct dentry *);

   void (*d_release)(struct dentry *);

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

   char *(*d_dname)(struct dentry *, char *, int);

};
技术分享图片
---------------------------------------------------------------------

  d_revalidate:在把目录项对象转换为一个文件路径名之前,判定该目录项对象是否仍然有效。大多数文件系统将它设置为NULL,而网络文件系统可以指定自己的函数。

 

  d_hash: 生成一个散列值,用于目录项散列表的、特定于具体文件系统的散列函数。参数dentry标识包含路径分量的目录。第二个参数包含要查找的路径名分量以及由散列函数生成的散列值。

 

  d_compare: 比较两个文件名。name1应该属于dir所指的目录。

 

  d_delete: 对于一个dentry的最后的引用解除(delete)时调用。这意味着没有人正在使用这个dentry,但依然是有效的,并依然在dcache中。

 

  d_release: 当要释放一个目录项对象时(放入slab分配器),调用该方法。

 

  d_iput: 当一个目录项对象变为“负”状态时调用该方法。缺省的VFS函数调用iput()释放索引节点对象。

 

  d_dname: 当需要产生一个dentry的路径名的时候调用。对于某些想要延迟路径名的产生的伪文件系统(sockfs, pipefs, ...)很有用。(不是在dentry创建的时候,而是在需要路径名的时候才产生)。真实的文件系统可能不会使用它,因为它们的dentries 出现在全局的dcache哈希表中,它们的哈希应该是不变量。除非使用适当的SMP安全措施,否则由于没有持有锁,则d_dname()不应该试着自己去修改dentry 。注意:d_path()逻辑是相当复杂的。正确的返回,比如"Hello"的方法是将其放在缓冲区的结尾处,然后返回指向第一个字符的指针。dynamic_dname()辅助函数可被用来处理这一点。

2.6 文件对象

文件对象描述进程怎样与一个打开的文件进行交互。文件对象是在文件被打开的时候创建的,由一个file结构来描述,文件结构也仅仅存在于内存中。其定义如下:

---------------------------------------------------------------------

 

技术分享图片
include/linux/fs.h
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;

   } f_u;

   struct path     f_path;

#define f_dentry   f_path.dentry /* 与文件相关的目录项 */

#define f_vfsmnt   f_path.mnt /* 含有该文件的已安装文件系统 */

   const struct file_operations   *f_op; /* 指向文件操作表的指针 */

   spinlock_t      f_lock;  /* f_ep_links, f_flags, no IRQ */

   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;

 

#ifdef CONFIG_EPOLL

   /* Used by fs/eventpoll.c to link all the hooks to this file */

   struct list_head   f_ep_links;

#endif /* #ifdef CONFIG_EPOLL */

   struct address_space  *f_mapping; /* 指向文件地址空间对象的指针 */

#ifdef CONFIG_DEBUG_WRITECOUNT

   unsigned long f_mnt_write_state;

#endif

};
技术分享图片

 

从Linux kernel 2.6.29开始,task_struct新增cred结构体描述进程环境,这些信息都是特定于进程的,而不是特定于打开的文件的,所以不把这些信息嵌入在file对象中自是也合情合理。file结构的cred结构体指针类型的f_cred成员指向cred结构。cred结构体其定义如下:

---------------------------------------------------------------------
技术分享图片
include/linux/cred.h
struct cred {

   atomic_t usage;

#ifdef CONFIG_DEBUG_CREDENTIALS

   atomic_t subscribers; /* number of processes subscribed */

   void     *put_addr;

   unsigned magic;

#define CRED_MAGIC 0x43736564

#define CRED_MAGIC_DEAD  0x44656144

#endif

   uid_t    uid;     /* real UID of the task */

   gid_t    gid;     /* real GID of the task */

   uid_t    suid;    /* saved UID of the task */

   gid_t    sgid;    /* saved GID of the task */

   uid_t    euid;    /* effective UID of the task */

   gid_t    egid;    /* effective GID of the task */

   uid_t    fsuid;   /* UID for VFS ops */

   gid_t    fsgid;   /* GID for VFS ops */

   unsigned securebits; /* SUID-less security management */

   kernel_cap_t cap_inheritable; /* caps our children can inherit */

   kernel_cap_t cap_permitted;  /* caps we‘re permitted */

   kernel_cap_t cap_effective;  /* caps we can actually use */

   kernel_cap_t cap_bset; /* capability bounding set */

#ifdef CONFIG_KEYS

   unsigned char   jit_keyring; /* default keyring to attach requested

                 * keys to */

   struct key  *thread_keyring; /* keyring private to this thread */

   struct key  *request_key_auth; /* assumed request_key authority */

   struct thread_group_cred *tgcred; /* thread-group shared credentials */

#endif

#ifdef CONFIG_SECURITY

   void     *security;  /* subjective LSM security */

#endif

   struct user_struct *user;   /* real user ID subscription */

   struct group_info *group_info; /* supplementary groups for euid/fsgid */

   struct rcu_head rcu;     /* RCU deletion hook */

};
技术分享图片
---------------------------------------------------------------------

文件指针说明当前的文件偏移位置,即下一个操作(读或写等)将在该位置发生。由于几个文件可能同时访问同一个文件,因此文件指针必须存放在file对象而不是索引节点对象中。

 

文件对象包含在具体文件系统的超级块的几个链表中。每个超级快对象把文件对象链表的表头存放在s_files字段中。链表中分别指向前一个元素和后一个元素的指针都存放在文件对象的fu_list字段中。

 

文件对象的f_count字段是一个引用计数器:它记录使用文件对象的进程数(以CLONE_FILES标志创建的轻量级进程共享文件描述符表,因此他们可以使用相同的文件对象)。当内核本身要使用该文件对象时也要增加计数器的值。

 

每个文件系统都有自己的文件操作集合,执行诸如读写文件这样的操作。当内核将一个索引节点从磁盘装入内核时,就会把指向这些文件操作的指针存放在file_operations结构中,而该结构的地址存放在该索引节点的i_fop字段中。当进程打开文件时,VFS就用存放在索引节点中的这个地址初始化新文件对象的f_op字段,使得对文件操作的后续调用能够使用这些函数。如果需要,VFS随后也可以通过在f_op字段存放一个新值而修改文件操作的集合。file_operations定义如下:

---------------------------------------------------------------------
技术分享图片
include/linux/fs.h
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 *);

   int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

   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 *, struct dentry *, 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 **);

};
技术分享图片
---------------------------------------------------------------------

该结构的各成员,与同名的系统调用有着相同的语义,包括参数和返回值,这里也就不再做过多的解释了。



以上是关于Linux VFS数据结构的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核VFS的主要数据结构

Linux VFS分析

VFS之基本数据结构

VFS,super_block,inode,dentry—结构体图解

linux文件系统体系结构 和 虚拟文件系统(VFS)

linux内核源码分析之虚拟文件系统VFS