Linux 虚拟文件系统四大对象:超级块、inode、dentry、file之间关系

一:文件系统

1. 什么是文件系统?

操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。

通常文件系统是用于存储和组织文件的一种机制,便于对文件进行方便的查找与访问。

文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。

它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。

随着文件种类的增多,扩增了更多的文件系统,为了对各种文件系统进行统一的管理与组织。

2. Linux文件系统

Linux将文件系统分为了两层:VFS(虚拟文件系统)、具体文件系统,如下图所示:

VFS

VFS(Virtual Filesystem Switch)称为虚拟文件系统或虚拟文件系统转换,是一个内核软件层,在具体的文件系统之上抽象的一层,用来处理与Posix文件系统相关的所有调用,表现为能够给各种文件系统提供一个通用的接口,使上层的应用程序能够使用通用的接口访问不同文件系统,同时也为不同文件系统的通信提供了媒介。

VFS并不是一种实际的文件系统,它只存在于内存中,不存在任何外存空间,VFS在系统启动时建立,在系统关闭时消亡。

VFS由超级块、inode、dentry、vfsmount等结构来组成。

Linux系统中存在很多的文件系统,例如常见的**ext2,ext3,ext4,sysfs,rootfs,proc...**等等。

二 、VFS

1. VFS在linux架构中的位置

从用户的使用角度,Linux下的文件系统中宏观上主要分为三层:

1.上层的文件系统的系统调用(System-call );2.虚拟文件系统VFS(Virtual File System)层,3.挂载到VFS中的各种实际文件系统。

VFS在整个Linux系统中的架构视图如下:

VFS

Linux系统的User使用GLIBC(POSIX标准、GUN C运行时库)作为应用程序的运行时库,然后通过操作系统,将其转换为系统调用SCI(system-call interface),SCI是操作系统内核定义的系统调用接口,这层抽象允许用户程序的I/O操作转换为内核的接口调用。

2. 用户如何透明的去处理文件?

我们知道每个文件系统是独立的,有自己的组织方法,操作方法。那么对于用户来说,不可能所有的文件系统都了解,那么怎么做到让用户透明的去处理文件呢?

例如:我想写文件,那就直接read就OK,不管你是什么文件系统,具体怎么去读!这里就需要引入虚拟文件系统。

所以虚拟文件系统就是:对于一个system,可以存在多个“实际的文件系统”,例如:ext2,ext3,fat32,ntfs...例如我现在有多个分区,对于每一个分区我们知道可以是不同的“实际文件系统”。

例如现在三个磁盘分区分别是:ext2,ext3,fat32,那么每个“实际的文件系统”的操作和数据结构肯定不一样,那么,用户怎么能透明使用它们呢?

这个时候就需要VFS作为中间一层!用户直接和VFS打交道。

VFS是一种软件机制,只存在于内存中,每次系统初始化期间Linux都会先在内存中构造一棵VFS的目录树(也就是源码中的namespace)。

VFS主要的作用是对上层应用屏蔽底层不同的调用方法,提供一套统一的调用接口,二是便于对不同的文件系统进行组织管理。

VFS提供了一个抽象层,将POSIX API接口与不同存储设备的具体接口实现进行了分离,使得底层的文件系统类型、设备类型对上层应用程序透明。

例如read,write,那么映射到VFS中就是sys_read,sys_write,那么VFS可以根据你操作的是哪个“实际文件系统”(哪个分区)来进行不同的实际的操作!这个技术也是很熟悉的“钩子结构”技术来处理的。

其实就是VFS中提供一个抽象的struct结构体,然后对于每一个具体的文件系统要把自己的字段和函数填充进去,这样就解决了异构问题(内核很多子系统都大量使用了这种机制)。

三、Linux虚拟文件系统四大对象

为了对文件系统进行统一的管理与组织,Linux创建了一个公共根目录和全局文件系统树。要访问一个文件系统中的文件,必须先将这个文件系统挂载在全局文件系统树的某个根目录下,这一挂载过程被称作文件系统的挂载,所挂载的目录称为挂载点。

传统的文件系统在磁盘上的布局如下:

由上图可知,文件系统的开头通常是由一个磁盘扇区所组成的引导块,该部分的主要目的是用于对操作系统的引导。一般只在启动操作系统时使用。

随后是超级块,超级块主要存放了该物理磁盘中文件系统结构的相关信息,并且对各个部分的大小进行说明。

最后由i节点位图,逻辑块位图、i节点、逻辑块这几部分分布在物理磁盘上。

Linux为了对超级块,i节点,逻辑块这三部分进行高效的管理,Linux创建了几种不同的数据结构,分别是文件系统类型、inode、dentry等几种。

其中,文件系统类型规定了某种文件系统的行为,利用该数据结构可以构造某种文件系统类型的实例,另外,该实例也被称为超级块实例。

超级块则是反映了文件系统整体的控制信息。超级块能够以多种的方式存在,对于基于磁盘的文件系统,它以特定的格式存在于磁盘的固定区域(取决于文件系统类型)上。在挂载文件系统时,该超级块中的内容被读入磁盘中,从而构建出位于内存中的新的超级块。

inode则反映了文件系统对象中的一般元数据信息。dentry则是反映出某个文件系统对象在全局文件系统树中的位置。

Linux对这四种数据结构进行了相关的关联。如下图:

结构体关系1. 超级块(super block)

超级块:一个超级块对应一个文件系统(已经安装的文件系统类型如ext2,此处是实际的文件系统,不是VFS)。

之前我们已经说了文件系统用于管理这些文件的数据格式和操作之类的,系统文件有系统文件自己的文件系统,同时对于不同的磁盘分区也有可以是不同的文件系统。那么一个超级块对于一个独立的文件系统。保存文件系统的类型、大小、状态等等。

(“文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block)

既然我们知道对于不同的文件系统有不同的super_block,那么对于不同的super_block的操作肯定也是不同的,所以我们在下面的super_block结构中可以看到上面说的抽象的struct结构(例如下面的:struct super_operations):

(linux内核3.14)

1246 struct super_block {
1247     struct list_head    s_list;      Keep this first
1248     dev_t           s_dev;       search index; _not_ kdev_t                                                                                                                                                                                                                                                                                                                                                                                      
1249     unsigned char       s_blocksize_bits;
1250     unsigned long       s_blocksize;
1251     loff_t          s_maxbytes;  Max file size
1252     struct file_system_type *s_type;
1253     const struct super_operations   *s_op;
1254     const struct dquot_operations   *dq_op;
1255     const struct quotactl_ops   *s_qcop;
1256     const struct export_operations *s_export_op;
1257     unsigned long       s_flags;
1258     unsigned long       s_magic;
1259     struct dentry       *s_root;
1260     struct rw_semaphore s_umount;
1261     int         s_count;
1262     atomic_t        s_active;
1263 #ifdef CONFIG_SECURITY
1264     void                    *s_security;
1265 #endif
1266     const struct xattr_handler **s_xattr;
1267
1268     struct list_head    s_inodes;    all inodes
1269     struct hlist_bl_head    s_anon;      anonymous dentries for (nfs) exporting
1270     struct list_head    s_mounts;    list of mounts; _not_ for fs use

1271     struct block_device *s_bdev;
1272     struct backing_dev_info *s_bdi;
1273     struct mtd_info     *s_mtd;
1274     struct hlist_node   s_instances;
1275     struct quota_info   s_dquot;     Diskquota specific options
1276
1277     struct sb_writers   s_writers;
1278
1279     char s_id[32];               Informational name
1280     u8 s_uuid[16];               UUID
1281
1282     void            *s_fs_info;  Filesystem private info
1283     unsigned int        s_max_links;
1284     fmode_t         s_mode;
1285
1286      Granularity of c/m/atime in ns.
1287        Cannot be worse than a second
1288     u32        s_time_gran;
1289
1290    
1291      * The next field is for VFS *only*. No filesystems have any business
1292      * even looking at it. You had been warned.
1293      
1294     struct mutex s_vfs_rename_mutex;     Kludge
1295
1296    
1297      * Filesystem subtype.  If non-empty the filesystem type field
1298      * in /proc/mounts will be "type.subtype"
1299      
1300     char *s_subtype;
1301
1302    
1303      * Saved mount options for lazy filesystems using
1304      * generic_show_options()
1305      
1306     char __rcu *s_options;
1307     const struct dentry_operations *s_d_op;  default d_op for dentries
1308
1309    
1310      * Saved pool identifier for cleancache (-1 means none)
1311      
1312     int cleancache_poolid;
1313
1314     struct shrinker s_shrink;    per-sb shrinker handle
1315
1316      Number of inodes with nlink == 0 but still referenced
1317     atomic_long_t s_remove_count;
1318
1319      Being remounted read-only
1320     int s_readonly_remount;
1321
1322      AIO completions deferred from interrupt context
1323     struct workqueue_struct *s_dio_done_wq;
1324
1325    
1326      * Keep the lru lists last in the structure so they always sit on their
1327      * own individual cachelines.
1328      
1329     struct list_lru     s_dentry_lru ____cacheline_aligned_in_smp;
1330     struct list_lru     s_inode_lru ____cacheline_aligned_in_smp;
1331     struct rcu_head     rcu;
1332 };

解释字段:

字段描述s_list指向超级块链表的指针,这个struct list_head是很熟悉的结构了,里面其实就是用于连接关系的prev和next字段。内核单独使用一个简单的结构体将所有的super_block都链接起来。s_dev包含该具体文件系统的块设备标识符。例如,对于 /dev/hda1,其设备标识符为 0x301s_blocksize_bits上面的size大小占用位数,例如512字节就是9 bitss_blocksize文件系统中数据块大小,以字节单位s_maxbytes允许的最大的文件大小(字节数)struct file_system_type *s_type文件系统类型(也就是当前这个文件系统属于哪个类型?ext2还是fat32),要区分“文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block,后面会说!struct super_operations *s_op指向某个特定的具体文件系统的用于超级块操作的函数集合struct dquot_operations *dq_op指向某个特定的具体文件系统用于限额操作的函数集合struct quotactl_ops     *s_qcop用于配置磁盘限额的的方法,处理来自用户空间的请求s_flags安装标识s_magic区别于其他文件系统的标识s_root指向该具体文件系统安装目录的目录项s_umount对超级块读写时进行同步s_count对超级块的使用计数s_active引用计数

超级块方法

struct super_operations {
       //该函数在给定的超级块下创建并初始化一个新的索引节点对象
   struct inode *(*alloc_inode)(struct super_block *sb);
       //释放指定的索引结点 。
void (*destroy_inode)(struct inode *);
       //VFS在索引节点被修改时会调用此函数。
   void (*dirty_inode) (struct inode *, int flags);
       // 将指定的inode写回磁盘。
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 *);
       //使文件系统的数据元素与磁盘上的文件系统同步,wait参数指定操作是否同步。
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_fs) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
       //获取文件系统状态。把文件系统相关的统计信息放在statfs中
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);

int (*show_options)(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);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *, int);
long (*free_cached_objects)(struct super_block *, long, int);
};
2. 索引节点(inode)

索引节点inode:保存的其实是实际的数据的一些信息,这些信息称为“元数据”(也就是对文件属性的描述)。

例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取或修改的时间戳,链接数量,指向存储该内容的磁盘区块的指针,文件分类等等。

( 注意数据分成:元数据+数据本身 )

同时注意:inode有两种,一种是VFS的inode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode,这样才是算使用了磁盘文件inode。

inode怎样生成的?

每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定(现代OS可以动态变化),一般每2KB就设置一个inode。

一般文件系统中很少有文件小于2KB的,所以预定按照2KB分,一般inode是用不完的。所以inode在文件系统安装的时候会有一个默认数量,后期会根据实际的需要发生变化。

注意inode号:inode号是唯一的,表示不同的文件。其实在Linux内部的时候,访问文件都是通过inode号来进行的,所谓文件名仅仅是给用户容易使用的。

当我们打开一个文件的时候,首先,系统找到这个文件名对应的inode号;然后,通过inode号,得到inode信息,最后,由inode找到文件数据所在的block,现在可以处理文件数据了。

inode和文件的关系?

当创建一个文件的时候,就给文件分配了一个inode。一个inode只对应一个实际文件,一个文件也会只有一个inode。inodes最大数量就是文件的最大数量。

527 struct inode {
528     umode_t         i_mode;   访问权限控制  
529     unsigned short      i_opflags;
530     kuid_t          i_uid;   使用者的id
531     kgid_t          i_gid;    使用组id  
532     unsigned int        i_flags;  文件系统标志
533
534 #ifdef CONFIG_FS_POSIX_ACL
535     struct posix_acl    *i_acl;
536     struct posix_acl    *i_default_acl;
537 #endif
538
539     const struct inode_operations   *i_op; 索引节点操作表
540     struct super_block  *i_sb;              相关的超级块  
541     struct address_space    *i_mapping;    相关的地址映射
542
543 #ifdef CONFIG_SECURITY
544     void            *i_security;
545 #endif
546
547      Stat data, not accessed from path walking
548     unsigned long       i_ino;    索引节点号
549    
550      * Filesystems may only read i_nlink directly.  They shall use the
551      * following functions for modification:
552      *
553      *    (set|clear|inc|drop)_nlink
554      *    inode_(inc|dec)_link_count
555      
556     union {
557         const unsigned int i_nlink;
558         unsigned int __i_nlink;    硬连接数
559     };
560     dev_t           i_rdev;   实际设备标识符号
561     loff_t          i_size;
562     struct timespec     i_atime;  最后访问时间
563     struct timespec     i_mtime;  最后修改时间
564     struct timespec     i_ctime;  最后改变时间  
565     spinlock_t      i_lock;  i_blocks, i_bytes, maybe i_size
566     unsigned short          i_bytes;   使用的字节数
567     unsigned int        i_blkbits;
568     blkcnt_t        i_blocks;   文件的块数
569
570 #ifdef __NEED_I_SIZE_ORDERED
571     seqcount_t      i_size_seqcount;

572 #endif
573
574      Misc
575     unsigned long       i_state;
576     struct mutex        i_mutex;
577
578     unsigned long       dirtied_when;    jiffies of first dirtying 首次修改时间
579
580     struct hlist_node   i_hash;     hash值,提高查找效率
581     struct list_head    i_wb_list;   backing dev IO list
582     struct list_head    i_lru;       inode LRU list 未使用的inode
583     struct list_head    i_sb_list;  链接一个文件系统中所有inode的链表
584     union {
585         struct hlist_head   i_dentry;   目录项链表  
586         struct rcu_head     i_rcu;
587     };
588     u64         i_version;
589     atomic_t        i_count;     引用计数
590     atomic_t        i_dio_count;
591     atomic_t        i_writecount;    写者计数
592     const struct file_operations    *i_fop;  former ->i_op->default_file_ops 文件操作
593     struct file_lock    *i_flock;    文件锁链表
594     struct address_space    i_data;  表示被inode读写的页面
595 #ifdef CONFIG_QUOTA
596     struct dquot        *i_dquot[MAXQUOTAS]; 节点的磁盘限额
597 #endif
598     struct list_head    i_devices;   设备链表(共用同一个驱动程序的设备形成的链表。)
599     union {
600         struct pipe_inode_info  *i_pipe;  管道信息
601         struct block_device *i_bdev;  块设备驱动节点
602         struct cdev     *i_cdev;    字符设备驱动节点
603     };
604
605     __u32           i_generation;     索引节点版本号
606
607 #ifdef CONFIG_FSNOTIFY
608     __u32           i_fsnotify_mask;  all events this inode cares about
609     struct hlist_head   i_fsnotify_marks;
610 #endif
611
612 #ifdef CONFIG_IMA
613     atomic_t        i_readcount;  struct files open RO
614 #endif
615     void            *i_private;  fs or device private pointer 用户私有数据
616 };

注意管理inode的四个链表:

static struct hlist_head *inode_hashtable __read_mostly;
节点方法struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
void * (*follow_link) (struct dentry *, struct nameidata *);
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);

int (*readlink) (struct dentry *, char __user *,int);
void (*put_link) (struct dentry *, struct nameidata *, 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 (*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;

对其中一些重要的结果进行分析:

方法含义create()如果该inode描述一个目录文件,那么当在该目录下创建或打开一个文件时,内核必须为这个文件创建一个inode。VFS通过调用该inode的i_op->create()函数来完成上述新inode的创建。该函数的第一个参数为该目录的 inode,第二个参数为要打开新文件的dentry,第三个参数是对该文件的访问权限。如果该inode描述的是一个普通文件,那么该inode永远都不会调用这个create函数;lookup()查找指定文件的dentry;link()用于在指定目录下创建一个硬链接。这个link函数最终会被系统调用link()调用。该函数的第一个参数是原始文件的dentry,第二个参数即为上述指定目录的inode,第三个参数是链接文件的dentry。unlink ()在某个目录下删除指定的硬链接。这个unlink函数最终会被系统调用unlink()调用。第一个参数即为上述硬链接所在目录的inode,第二个参数为要删除文件的dentry。symlink ()在某个目录下新建mkdir()在指定的目录下创建一个子目录,当前目录的inode会调用i_op->mkdir()。该函数会被系统调用mkdir()调用。第一个参数即为指定目录的inode,第二个参数为子目录的dentry,第三个参数为子目录权限;rmdir ()从inode所描述的目录中删除一个指定的子目录时,该函数会被系统调用rmdir()最终调用;mknod()在指定的目录下创建一个特殊文件,比如管道、设备文件或套接字等。3)目录项(dentry)

目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。

注意不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。

例如:open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。

注意:目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。

108 struct dentry {
109      RCU lookup touched fields
110     unsigned int d_flags;        protected by d_lock
111     seqcount_t d_seq;        per dentry seqlock
112     struct hlist_bl_node d_hash;     lookup hash list
113     struct dentry *d_parent;     parent directory 父目录
114     struct qstr d_name;
115     struct inode *d_inode;       Where the name belongs to - NULL is
116                      * negative 与该目录项关联的inode
117     unsigned char d_iname[DNAME_INLINE_LEN];     small names 短文件名
118
119      Ref lookup also touches following
120     struct lockref d_lockref;    per-dentry lock and refcount
121     const struct dentry_operations *d_op;   目录项操作
122     struct super_block *d_sb;    The root of the dentry tree 这个目录项所属的文件系统的超级块(目录项树的根)
123     unsigned long d_time;        used by d_revalidate 重新生效时间
124     void *d_fsdata;          fs-specific data 具体文件系统的数据
125
126     struct list_head d_lru;      LRU list 未使用目录以LRU 算法链接的链表
127    
128      * d_child and d_rcu can share memory
129      
130     union {
131         struct list_head d_child;    child of parent list 目录项通过这个加入到父目录的d_subdirs中
132         struct rcu_head d_rcu;
133     } d_u;
134     struct list_head d_subdirs;  our children 本目录的所有孩子目录链表头
135     struct hlist_node d_alias;   inode alias list 索引节点别名链表
136 };

一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。但是inode却可以对应多个。

整个结构其实就是一棵树,如果看过我的设备模型kobject就能知道,目录其实就是文件(kobject、inode)再加上一层封装,这里所谓的封装主要就是增加两个指针,一个是指向父目录,一个是指向该目录所包含的所有文件(普通文件和目录)的链表头。

这样才能有我们的目录操作(比如回到上次目录,只需要一个指针步骤【..】,而进入子目录需要链表索引需要多个步骤)

dentry相关的操作(inode里面已经包含了mkdir,rmdir,mknod之类的操作了)

struct dentry_operations {
        该函数判断目录对象是否有效。VFS准备从dcache中使用一个目录项时,会调用该函数.
int (*d_revalidate)(struct dentry *, unsigned int);      
int (*d_weak_revalidate)(struct dentry *, unsigned int);
        该目录生成散列值,当目录项要加入到散列表时,VFS要调用此函数。
int (*d_hash)(const struct dentry *, struct qstr *);    
        该函数来比较name1和name2这两个文件名。使用该函数要加dcache_lock锁。
int (*d_compare)(const struct dentry *, const struct dentry *,
  unsigned int, const char *, const struct qstr *);
        当d_count=0时,VFS调用次函数。使用该函数要叫 dcache_lock锁。
int (*d_delete)(const struct dentry *);
        当该目录对象将要被释放时,VFS调用该函数。
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
        当一个目录项丢失了其索引节点时,VFS就掉用该函数。
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);
} ____cacheline_aligned;

4)文件对象(file)

文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode就是唯一的,目录项也是定的!

进程其实是通过文件描述符来操作文件的,每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。

一般情况下打开文件后,打开位置都是从0开始,除非一些特殊情况。Linux用file结构体来保存打开的文件的位置,所以file称为打开的文件描述。file结构形成一个双链表,称为系统打开文件表。

file775 struct file {
776     union {
777         struct llist_node   fu_llist;  每个文件系统中被打开的文件都会形成一个双链表
778         struct rcu_head     fu_rcuhead;
779     } f_u;
780     struct path     f_path;
781 #define f_dentry    f_path.dentry
782     struct inode        *f_inode;    cached value
783     const struct file_operations    *f_op;  指向文件操作表的指针
784
785    
786      * Protects f_ep_links, f_flags.
787      * Must not be taken from IRQ context.
788      
789     spinlock_t      f_lock;
790     atomic_long_t       f_count;   文件对象的使用计数
791     unsigned int        f_flags;   打开文件时所指定的标志
792     fmode_t         f_mode;        文件的访问模式(权限等)
793     struct mutex        f_pos_lock;
794     loff_t          f_pos;        文件当前的位移量
795     struct fown_struct  f_owner;
796     const struct cred   *f_cred;
797     struct file_ra_state    f_ra;  预读状态
798
799     u64         f_version;    版本号
800 #ifdef CONFIG_SECURITY
801     void            *f_security;   安全模块
802 #endif
803      needed for tty driver, and maybe others
804     void            *private_data;  私有数据
805
806 #ifdef CONFIG_EPOLL
807      Used by fs/eventpoll.c to link all the hooks to this file
808     struct list_head    f_ep_links;
809     struct list_head    f_tfile_llink;
810 #endif  #ifdef CONFIG_EPOLL
811     struct address_space    *f_mapping; 页缓存映射
812 #ifdef CONFIG_DEBUG_WRITECOUNT
813     unsigned long f_mnt_write_state;
814 #endif
815 } __attribute__((aligned(4)));   lest something weird decides that 2 is OK

重点解释一些重要字段:

首先,f_flags、f_mode和f_pos代表的是这个进程当前操作这个文件的控制信息。这个非常重要,因为对于一个文件,可以被多个进程同时打开,那么对于每个进程来说,操作这个文件是异步的,所以这个三个字段就很重要了。对于引用计数f_count,当我们关闭一个进程的某一个文件描述符时候,其实并不是真正的关闭文件,仅仅是将f_count减一,当f_count=0时候,才会真的去关闭它。对于dup,fork这些操作来说,都会使得f_count增加,具体的细节,以后再说。f_op也是很重要的!是涉及到所有的文件的操作结构体。例如:用户使用read,最终都会调用file_operations中的读操作,而file_operations结构体是对于不同的文件系统不一定相同。里面一个重要的操作函数式release函数,当用户执行close时候,其实在内核中是执行release函数,这个函数仅仅将f_count减一,这也就解释了上面说的,用户close一个文件其实是将f_count减一。只有引用计数减到0才关闭文件。

注意:对于“正在使用”和“未使用”的文件对象分别使用一个双向链表进行管理。

files_struct

上面的file只是对一个文件而言,对于一个进程(用户)来说,可以同时处理多个文件,所以需要另一个结构来管理所有的files!

即:用户打开文件表--->files_struct

172 struct files_struct {
173         atomic_t count;
174         rwlock_t file_lock;      Protects all the below members.  Nests inside tsk->alloc_lock
175         int max_fds;
176         int max_fdset;
177         int next_fd;
178         struct file ** fd;       current fd array
179         fd_set *close_on_exec;
180         fd_set *open_fds;
181         fd_set close_on_exec_init;
182         fd_set open_fds_init;
183         struct file * fd_array[NR_OPEN_DEFAULT];
184 };

解释一些字段:

字段描述count引用计数file_lock锁,保护下面的字段max_fds当前文件对象的最大的数量max_fdset文件描述符最大数next_fd已分配的最大的文件描述符+1fd指向文件对象指针数组的指针,一般就是指向最后一个字段fd_arrray,当文件数超过NR_OPEN_DEFAULT时候,就会重新分配一个数组,然后指向这个新的数组指针!close_on_exec执行exec()时候需要关闭的文件描述符open_fds指向打开的文件描述符的指针close_on_exec_init执行exec()时候需要关闭的文件描述符初始化值open_fds_init文件描述符初值集合fd_array文件对象指针的初始化数组fs_struct

上面的file和files_struct记录的是与进程相关的文件的信息,但是对于进程本身来说,自身的一些信息用什么表示,这里就涉及到fs_struct结构体。

 5 struct fs_struct {
 6         atomic_t count;
 7         rwlock_t lock;
 8         int umask;
 9         struct dentry * root, * pwd, * altroot;
10         struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
11 };

解释一些字段:

字段描述count引用计数lock保护锁umask打开文件时候默认的文件访问权限root进程的根目录pwd进程当前的执行目录altroot用户设置的替换根目录

注意:实际运行时,这三个目录不一定都在同一个文件系统中。例如,进程的根目录通常是安装于“/”节点上的ext文件系统,而当前工作目录可能是安装于/etc的一个文件系统,替换根目录也可以不同文件系统中。rootmnt,pwdmnt,altrootmnt:对应于上面三个的安装点。

文件方法(操作)file_operationsstruct 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);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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 *, loff_t, loff_t, 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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
    loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

上面这个对我们驱动开发人员应该是最熟悉的,也是必须掌握的了。

字段描述owner用于指定拥有这个文件操作结构体的模块,通常取THIS_MODULE;llseek用于设置文件的偏移量。第一个参数指明要操作的文件,第二个参数为偏移量,第三个参数为开始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)。read从文件中读数据。第一个参数为源文件,第二个参数为目的字符串,第三个参数指明欲读数据的总字节数,第四个参数指明从源文件的某个偏移量处开始读数据。由系统调用read()调用;write往文件里写数据。第一个参数为目的文件,第二个参数源字符串,第三个参数指明欲写数据的总字节数,第四个参数指明从目的文件的某个偏移量出开始写数据。由系统调用write()调用;mmap将指定文件映射到指定的地址空间上。由系统调用mmap()调用;open打开指定文件,并且将这个文件和指定的索引结点关联起来。由系统调用open()调用;release释放以打开的文件,当打开文件的引用计数(f_count)为0时,该函数被调用;fsync文件在缓冲的数据写回磁盘;

四、进程与这四者之间的关系

内核中用于管理进程的结构体是task_struct。进程打开文件就涉及到上述4个重要的数据结构:

file
fs_struct
files_struct
namespace

每个进程都有自己的namespace。

fs_struct用于表示进程与文件系统之间的结构关系,比如当前的工作目录,进程的根目录等等。

files_struct 用于表示当前进程打开的文件。

而对于每一个打开的文件,由file对象来表示。

Linux中,常常用文件描述符(file descriptor)来表示一个打开的文件,这个描述符的值往往是一个大于或等于0的整数。而这个整数,其实就是在files_struct中file数组fd的下标。对于所有打开的文件, 这些文件描述符会存储在open_fds的位图中。

进程与超级块、文件、索引结点、目录项的关系

从图中可知:

进程通过task_struct中的一个域files->files_struct 来了解它当前所打开的文件对象;而我们通常所说的文件描述符其实是进程打开的文件对象数组的索引值。文件对象通过域f_dentry找到它对应的dentry对象,再由dentry对象的域d_inode找到它对应的索引节点(通过索引节点又可以得到超级块的信息,也就可以得到最终操作文件的方法,在open文件的时候就是使用这样一个过程),这样就建立了文件对象与实际的物理文件的关联。文件对象所对应的文件操作函数列表是通过索引节点的域i_fop得到的,而i_fop最终又是通过struct super_operations  *s_op来初始化的。

VFS文件系统中的inode和dentry与实际文件系统的inode和dentry有一定的关系,但不能等同。

真实磁盘文件的inode和dentry是存在于物理外存上的,但VFS中的inode和dentry是存在于内存中的,系统读取外存中的inode和dentry信息进行一定加工后,生成内存中的inode和dentry。

虚拟的文件系统也具有inode和dentry结构,只是这是系统根据相应的规则生成的,不存在于实际外存中。

五、磁盘与文件系统

假设一块磁盘被分为好几个分区,每个分区都是不同的文件系统。

磁盘与文件系统





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

    VFS只存在于内存中,它在系统启动时被创建,系统关闭时注销。

    VFS的作用就是屏蔽各类文件系统的差异,给用户、应用程序、甚至Linux其他管理模块提供统一的接口集合。

    管理VFS数据结构的组成部分主要包括超级块和inode。

    VFS是物理文件系统与服务之间的一个接口层,它对Linux的每个文件系统的所有细节进行抽象,使得不同的文件系统在Linux核心以及系统中运行的进程看来都是相同的。

    严格的说,VFS并不是一种实际的文件系统。它只存在于内存中,不存在于任何外存空间。VFS在系统启动时建立,在系统关闭时消亡。

    VFS使Linux同时安装、支持许多不同类型的文件系统成为可能。VFS拥有关于各种特殊文件系统的公共界面,当某个进程发布了一个面向文件的系统调用时,内核将调用VFS中对应的函数,这个函数处理一些与物理结构无关的操作,并且把它重定向为真实文件系统中相应的函数调用,后者用来处理那些与物理结构相关的操作。

    下图就是逻辑上对VFS及其下层实际文件系统的组织图,可以看到用户层只能于VFS打交道,而不能直接访问实际的文件系统,比如EXT2、EXT3、PROC,换句话说,

    就是用户层不用也不能区别对待这些真正的文件系统,不过,SOCKET虽然也属于VFS的管辖范围,但是有其特殊性,

    就是不能像打开大部分文件系统下的“文件”一样打开socket,它只能被创建,而且内核中对其有特殊性处理。

        VFS描述文件系统使用超级块和inode 的方式,所谓超级块就是对所有文件系统的管理机构,每种文件系统都要把自己的信息挂到super_blocks这么一个全局链表上。

    内核中是分成2个步骤完成:首先每个文件系统必须通过register_filesystem函数将自己的file_system_type挂接到file_systems这个全局变量上,

    然后调用kern_mount函数把自己的文件相关操作函数集合表挂到super_blocks上。每种文件系统类型的读超级块的例程(get_sb)必须由自己实现。

        文件系统由子目录和文件构成。每个子目录和文件只能由唯一的inode 描述。inode 是Linux管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。

    VFS inode的内容取自物理设备上的文件系统,由文件系统指定的操作函数(i_op 属性指定)填写。VFS inode只存在于内存中,可通过inode缓存访问。

    1、super_block

    • 相关的数据结构为:
    1. struct super_block
    2. {
    3. struct list_head s_list;/* Keep this first */// 连接super_block的链表
    4. dev_t s_dev;/* search index; _not_ kdev_t */
    5. unsignedlong s_blocksize;
    6. unsignedlong s_old_blocksize;
    7. unsignedchar s_blocksize_bits;
    8. unsignedchar s_dirt;
    9. unsignedlonglong s_maxbytes;/* Max file size */
    10. struct file_system_type  *s_type;// 所表示的文件系统的类型
    11. struct super_operations  *s_op;// 文件相关操作函数集合表 
    12. struct dquot_operations  *dq_op;// 
    13. struct quotactl_ops  *s_qcop;// 
    14. struct export_operations *s_export_op;// 
    15. unsignedlong s_flags;// 
    16. unsignedlong s_magic;// 
    17. struct dentry  *s_root;// Linux文件系统中某个索引节点(inode)的链接
    18. struct rw_semaphore s_umount;// 
    19. struct semaphore s_lock;// 
    20. int s_count;// 
    21. int s_syncing;// 
    22. int s_need_sync_fs;// 
    23. atomic_t s_active;// 
    24. void*s_security;// 
    25. struct xattr_handler  **s_xattr;// 
    26. struct list_head s_inodes;/* all inodes */// 链接文件系统的inode
    27. struct list_head s_dirty;/* dirty inodes */
    28. struct list_head s_io;/* parked for writeback */
    29. struct hlist_head s_anon;/* anonymous dentries for (nfs) exporting */
    30. struct list_head s_files;// 对于每一个打开的文件,由file对象来表示。链接文件系统中file
    31. struct block_device  *s_bdev;// 
    32. struct list_head s_instances;// 
    33. struct quota_info s_dquot;/* Diskquota specific options */
    34. int s_frozen;// 
    35. wait_queue_head_t s_wait_unfrozen;// 
    36. char s_id[32];/* Informational name */
    37. void*s_fs_info;/* Filesystem private info */
    38. /**
    39. * The next field is for VFS *only*. No filesystems have any business
    40. * even looking at it. You had been warned.
    41. */
    42. struct semaphore s_vfs_rename_sem;/* Kludge */
    43. /* Granuality of c/m/atime in ns.
    44. Cannot be worse than a second */
    45. u32 s_time_gran;
    46. };

    • super_block存在于两个链表中,一个是系统所有super_block的链表, 一个是对于特定的文件系统的super_block链表.

          所有的super_block都存在于 super_blocks(VFS管理层) 链表中:

    • 对于特定的文件系统(文件系统层的具体文件系统), 该文件系统的所有的super_block 都存在于file_sytem_type中的fs_supers链表中.
         而所有的文件系统,都存在于file_systems链表中.这是通过调用register_filesystem接口来注册文件系统的.
         int register_filesystem(struct file_system_type * fs) 

    2、inode

    inode(发音:eye-node)译成中文就是索引节点,它用来存放档案及目录的基本信息,包含时间、档名、使用者及群组等。

    inode 是 UNIX 操作系统中的一种数据结构,其本质是结构体,它包含了与文件系统中各个文件相关的一些重要信息。在 UNIX 中创建文件系统时,同时将会创建大量的 inode 。

    通常,文件系统磁盘空间中大约百分之一空间分配给了 inode 表。

    dentry的中文名称是目录项,是Linux文件系统中某个索引节点(inode)的链接。这个索引节点可以是文件的,也可以是目录的。

    inode对应于物理磁盘上的具体对象,dentry是一个内存实体,其中的d_inode成员指向对应的inode。

    相关的数据结构为:

    1. /*
    2. * Keep mostly read-only and often accessed (especially for
    3. * the RCU path lookup and 'stat' data) fields at the beginning
    4. * of the 'struct inode'
    5. */
    6. struct inode
    7. {
    8. umode_t i_mode;
    9. unsignedshort i_opflags;
    10. kuid_t i_uid;
    11. kgid_t i_gid;
    12. unsignedint i_flags;
    13. #ifdef CONFIG_FS_POSIX_ACL
    14. struct posix_acl *i_acl;
    15. struct posix_acl *i_default_acl;
    16. #endif
    17. conststruct inode_operations *i_op;
    18. struct super_block *i_sb;
    19. struct address_space *i_mapping;
    20. #ifdef CONFIG_SECURITY
    21. void*i_security;
    22. #endif
    23. /* Stat data, not accessed from path walking */
    24. unsignedlong i_ino;
    25. /*
    26. * Filesystems may only read i_nlink directly. They shall use the
    27. * following functions for modification:
    28. *
    29. * (set|clear|inc|drop)_nlink
    30. * inode_(inc|dec)_link_count
    31. */
    32. union
    33. {
    34. constunsignedint i_nlink;
    35. unsignedint __i_nlink;
    36. };
    37. dev_t i_rdev;
    38. loff_t i_size;
    39. struct timespec i_atime;
    40. struct timespec i_mtime;
    41. struct timespec i_ctime;
    42. spinlock_t i_lock;/* i_blocks, i_bytes, maybe i_size */
    43. unsignedshort i_bytes;
    44. unsignedint i_blkbits;
    45. blkcnt_t i_blocks;
    46. #ifdef __NEED_I_SIZE_ORDERED
    47. seqcount_t i_size_seqcount;
    48. #endif
    49. /* Misc */
    50. unsignedlong i_state;
    51. struct mutex i_mutex;
    52. unsignedlong dirtied_when;/* jiffies of first dirtying */
    53. unsignedlong dirtied_time_when;
    54. struct hlist_node i_hash;
    55. struct list_head i_wb_list;/* backing dev IO list */
    56. struct list_head i_lru;/* inode LRU list */
    57. struct list_head i_sb_list;
    58. union
    59. {
    60. struct hlist_head i_dentry;
    61. struct rcu_head i_rcu;
    62. };
    63. u64 i_version;
    64. atomic_t i_count;
    65. atomic_t i_dio_count;
    66. atomic_t i_writecount;
    67. #ifdef CONFIG_IMA
    68. atomic_t i_readcount;/* struct files open RO */
    69. #endif
    70. conststruct file_operations *i_fop;/* former ->i_op->default_file_ops */
    71. struct file_lock_context *i_flctx;
    72. struct address_space i_data;
    73. struct list_head i_devices;
    74. union
    75. {
    76. struct pipe_inode_info *i_pipe;
    77. struct block_device *i_bdev;
    78. struct cdev *i_cdev;
    79. };
    80. __u32 i_generation;
    81. #ifdef CONFIG_FSNOTIFY
    82. __u32 i_fsnotify_mask;/* all events this inode cares about */
    83. struct hlist_head i_fsnotify_marks;
    84. #endif
    85. void*i_private;/* fs or device private pointer */
    86. };
    inode存在于两个双向链表中:
    一个是inode所在文件系统的super_block的 s_inodes 链表中
    一个是根据inode的使用状态存在于以下三个链表中的某个链表中:
    • 未用的: inode_unused 链表
    • 正在使用的: inode_in_use 链表
    • 脏的: super block中的s_dirty 链表
    另外,还有一个重要的链表: inode_hashtable(这个暂不介绍).

    3、dentry

    1. struct dentry
    2. {
    3. /* RCU lookup touched fields */
    4. unsignedint d_flags;/* protected by d_lock */
    5. seqcount_t d_seq;/* per dentry seqlock */
    6. struct hlist_bl_node d_hash;/* lookup hash list */
    7. struct dentry *d_parent;/* parent directory */
    8. struct qstr d_name;
    9. struct inode *d_inode;/* Where the name belongs to - NULL is
    10. * negative */
    11. unsignedchar d_iname[DNAME_INLINE_LEN];/* small names */
    12. /* Ref lookup also touches following */
    13. struct lockref d_lockref;/* per-dentry lock and refcount */
    14. conststruct dentry_operations *d_op;
    15. struct super_block *d_sb;/* The root of the dentry tree */
    16. unsignedlong d_time;/* used by d_revalidate */
    17. void*d_fsdata;/* fs-specific data */
    18. struct list_head d_lru;/* LRU list */
    19. struct list_head d_child;/* child of parent list */
    20. struct list_head d_subdirs;/* our children */
    21. /*
    22. * d_alias and d_rcu can share memory
    23. */
    24. union
    25. {
    26. struct hlist_node d_alias;/* inode alias list */
    27. struct rcu_head d_rcu;
    28. } d_u;
    29. };
    dentry对象存在于三个双向链表中:
    • 所有未用的目录项: dentry_unused 链表
    • 正在使用的目录项: 对应inode的 i_dentry 链表
    • 表示父子目录结构的链表
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux虚拟文件系统(Virtual File System,VFS)是Linux操作系统中的一个重要组成部分,它提供了一个统一的接口,使得用户和应用程序可以通过相同的方式访问不同类型的文件系统。 VFS的设计目标是将不同类型的文件系统抽象为一个统一的接口,使得用户和应用程序无需关心底层文件系统的具体实现细节。通过VFS,用户可以使用相同的系统调用(如open、read、write等)来访问不同类型的文件系统,包括本地文件系统(如ext4、XFS等)、网络文件系统(如NFS、CIFS等)以及虚拟文件系统(如procfs、sysfs等)。 VFS的核心数据结构是inodedentry。inode(index node)表示文件或目录在文件系统中的索引节点,它包含了文件或目录的元数据信息,如权限、大小、时间戳等。dentry(directory entry)表示目录中的一个条目,它包含了文件或目录的名称和对应的inode指针。 VFS通过层次化的文件系统结构来管理不同类型的文件系统。在最顶层是一个虚拟文件系统层(VFS层),它提供了与用户和应用程序交互的接口。下面是一些常见的虚拟文件系统: 1. procfs:提供了对内核运行时信息的访问,以文件的形式呈现。用户可以通过读取procfs中的文件获取系统信息,如进程信息、内存信息等。 2. sysfs:提供了对设备和驱动程序的访问,以文件的形式呈现。用户可以通过sysfs获取和配置系统中的设备信息。 3. tmpfs:是一种基于内存的文件系统,它将文件存储在内存中而不是硬盘上,读写速度较快,适用于临时文件存储。 4. nfs:是一种网络文件系统,允许用户通过网络访问远程主机上的文件。 5. ext4、XFS等:是常见的本地文件系统,用于在硬盘上存储文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值