Linux内核设计与实现——读书笔记(10)虚拟文件系统VFS

VFS

  • 1、Unix文件系统
  • 2、VFS对象及数据结构
  • 3、超级块
    • 3.1、超级块对象
    • 3.2、超级块操作
  • 4、索引节点
    • 4.1、索引节点对象
    • 4.2、索引节点操作
  • 5、目录项
    • 5.1、目录项对象
    • 5.2、目录项状态
    • 5.3、目录项缓存
    • 5.4、目录项操作
  • 6、文件
    • 6.1、文件对象
    • 6.2、文件操作
  • 7、和文件系统相关的数据结构
  • 8、和进程相关的数据结构
    • 8.1、files_struct
    • 8.2、fs_struct
    • 8.3、namespace

  虚拟文件系统为用户空间提供了文件和文件系统的相关接口。通过虚拟文件系统和块IO层,程序可以利用标准的UNIX系统调用对不同的文件进行读写操作,无需考虑文件存在于什么样的文件系统和存储介质中。

1、Unix文件系统

  Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点(mount)。
  Unix系统将文件的相关信息和文件本身这两个概念加以区分。文件的相关信息有时被称为元数据,被存储在一个单独的数据结构中,索引节点(inode)该结构成为,index node 的缩写。
  文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。

2、VFS对象及数据结构

  VFS有四个主要的数据类型:

  • 超级块对象,代表一个具体的已经安装的文件系统;
  • 索引节点对象,代表一个文件;
  • 目录项对象,代表一个目录项,路径的组成部分;
  • 文件对象,代表进程打开的文件;

  每个主要对象都包含一个操作对象,这些操作对象都包含了内核针对主要对象可以使用的方法:

  • super_operations对象,包含内核针对特定文件系统所能调用的方法,例如,write_inode()sync_fs() 等;
  • inode_operations对象,包含内核针对特定文件所能调用的方法,例如,create()link() 等;
  • dentry_operations对象,包含内核针对特定目录所能调用的方法,例如,d_compare()d_delete() 等;
  • file_operations对象,包含进程针对已打开文件所能调用的方法,例如,read()write()

  VFS所包含的对象远远多于上面这几种对象。例如,file_system_type结构体表示每个注册的文件系统,描述了文件系统及其性能;vfsmount结构体表示每一个安装点,描述了位置和安装标志等。

3、超级块

3.1、超级块对象

  所有文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,对应存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。除了基于磁盘的文件系统外,还有基于内存的文件系统,比如sysfs,它们会在现场创建超级块并将其存放在内存中。
  超级块对象由super_block结构体表示,定义在文件 中:

struct super_block {
    struct list_head    s_list;     				/* Keep this first,指向所有超级块的链表*/
    dev_t         		s_dev;      				/* search index; _not_ kdev_t ,设备标识符*/
    unsigned long       s_blocksize;				/* 以字节为单位的块大小 */
    unsigned char       s_blocksize_bits;			/* 以位为单位的块大小 */
    unsigned char       s_dirt;						/* 修改(脏)标志 */
    unsigned long long  s_maxbytes; 				/* Max file size */
    struct file_system_type *s_type;				/* 文件系统类型 */
    const struct super_operations   *s_op;			/* 超级块方法 */
    struct dquot_operations *dq_op;					/* 磁盘限额方法 */
    struct quotactl_ops *s_qcop;					/* 限额控制方法 */
    const struct export_operations *s_export_op;	/* 导出方法 */
    unsigned long       s_flags;					/* 挂载标志 */
    unsigned long       s_magic;					/* 文件系统的幻数 */
    struct dentry       *s_root;					/* 目录挂载点 */
    struct rw_semaphore s_umount;					/* 卸载信号量 */
    struct mutex        s_lock;						/* 超级块信号量 */
    int         s_count;							/* 超级块引用计数 */
    int         s_syncing;							/*  */
    int         s_need_sync_fs;						/* 尚未同步标志 */
    atomic_t        s_active;						/* 活动引用计数 */
#ifdef CONFIG_SECURITY	
    void                    *s_security;			/* 安全模块 */
#endif
    struct xattr_handler    **s_xattr;				/* 扩展的属性操作 */

    struct list_head    s_inodes;   				/* all inodes,inode链表 */
    struct list_head    s_dirty;    				/* dirty inodes */
    struct list_head    s_io;       				/* parked for writeback,回写链表 */
    struct list_head    s_more_io;  				/* parked for more writeback,更多回写链表 */
    struct hlist_head   s_anon;     				/* anonymous dentries for (nfs) exporting,匿名目录项 */
    struct list_head    s_files;					/* 被分配文件链表 */

    struct block_device *s_bdev;					/* 相关块设备 */
    struct mtd_info     *s_mtd;						/* 存储磁盘信息 */
    struct list_head    s_instances;				/* 该类型文件系统 */
    struct quota_info   s_dquot;   					/* Diskquota specific options,磁盘配额选项 */

    int         s_frozen;							/* 冻结标志位 */
    wait_queue_head_t   s_wait_unfrozen;			/* 冻结的等待队列 */

    char s_id[32];              					/* Informational name,文本名字 */

    void            *s_fs_info; 					/* Filesystem private info,文件系统特殊信息 */

    /*
     * 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,重命名信号量 */

    /* Granularity of c/m/atime in ns.
       Cannot be worse than a second */
    u32        s_time_gran;							/* 时间戳粒度 */

    /*
     * Filesystem subtype.  If non-empty the filesystem type field
     * in /proc/mounts will be "type.subtype"
     */
    char *s_subtype;								/* 子类型名称 */
};

  创建、管理和撤销超级块对象的代码位于fs/super.c中。超级块对象通过alloc_super() 函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并将其信息填充到内存中的超级块对象中。

3.2、超级块操作

  超级块对象中s_op 域用于指向超级块的操作函数。操作函数游super_operations结构体表示,定义在文件 中:

struct super_operations {
    struct inode *(*alloc_inode)(struct super_block *sb);	//在给定超级块中创建和初始化一个新的索引节点对象
    void (*destroy_inode)(struct inode *);					//释放给定的索引节点

    void (*dirty_inode) (struct inode *, int flags);		//VFS在索引节点脏时调用此函数;日志文件系统(ext3和ext4)在日志更新时执行该函数
    int (*write_inode) (struct inode *, struct writeback_control *wbc);	//将给定索引节点写入磁盘
    int (*drop_inode) (struct inode *);						//最后一个指向索引节点的引用被释放后,VFS会调用该函数
    void (*evict_inode) (struct inode *);
    void (*put_super) (struct super_block *);				//卸载文件系统时由VFS调用,用来释放超级块,调用者需要一直持有s_lock锁
    int (*sync_fs)(struct super_block *sb, int wait);		//使文件系统的数据元和磁盘上的文件系统同步。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 *);		//VFS通过此函数获取文件系统状态。指定文件系统相关的统计信息将放置在statfs中
    int (*remount_fs) (struct super_block *, int *, char *);//当指定新的安装选项重新安装文件系统时,VFS会调用此函数。调用者必须一直持有s_lock锁
    void (*umount_begin) (struct super_block *);			//VFS调用此函数中断安装操作。网络文件系统中使用(NFS)

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

  除了dirty_inode() ,其他函数在必要时都可以阻塞。
  不需要的函数可以设置为NULL,当VFS发现操作函数指针是NULL时,会调用通用函数执行操作,或者什么都不做。

4、索引节点

4.1、索引节点对象

  索引节点对象包含了内核在操作文件或目录时需要的全部信息。对于Unix风格的文件系统,这些信息可以从磁盘索引节点直接读入。如果一个文件系统没有索引节点,那么通常将文件的描述信息作为文件的一部分来存放。
  索引节点对象由inode表示,定义在 中:

struct inode {                                                                                                                                                                                                                                                                
    struct hlist_node   i_hash;				/* 散列表 */
    struct list_head    i_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;					/* 使用者id */
    gid_t           i_gid;					/* 使用组id */
    dev_t           i_rdev;					/* 实际设备标识符 */
    unsigned long       i_version;			/* 版本号 */
    loff_t          i_size;					/* 以字节为单位的文件大小 */	
#ifdef __NEED_I_SIZE_ORDERED
    seqcount_t      i_size_seqcount;		/* 对i_size进行串行计数 */
#endif
    struct timespec     i_atime;			/* 文件最后访问时间 */
    struct timespec     i_mtime;			/* 文件最后修改时间 */
    struct timespec     i_ctime;			/* 节点最后修改时间 */
    unsigned int        i_blkbits;			/* 以位为单位的块大小 */
    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;	/* 索引节点操作表 */
    const struct file_operations    *i_fop; /* former ->i_op->default_file_ops,缺省索引节点操作 */
    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;			/* 字符设备驱动 */
    };   
    int         i_cindex;					/*  */

    __u32           i_generation;

#ifdef CONFIG_DNOTIFY
    unsigned long       i_dnotify_mask; 	/* Directory notify events */
    struct dnotify_struct   *i_dnotify; 	/* for directory notifications */
#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;			/* 状态标志 */
    unsigned long       dirtied_when;   	/* jiffies of first dirtying,第一次脏数据的时间 */

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

    atomic_t        i_writecount;			/* 写者计数 */
#ifdef CONFIG_SECURITY
    void            *i_security;			/* 安全模块 */
#endif
    void            *i_private; 			/* fs or device private pointer,fs私有指针 */
};

4.2、索引节点操作

  索引节点对象中inode_operations域描述了VFS操作索引节点对象的方法。该结构体定义在 中:

struct inode_operations {
    struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);//在特定目录中搜索节点
    const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
    int (*permission) (struct inode *, int);								//用来检查给定的inode所代表的文件是否允许特定的访问模式
    struct posix_acl * (*get_acl)(struct inode *, int);

    int (*readlink) (struct dentry *, char __user *,int);					//由系统调用readlink()调用,拷贝数据到特定缓冲区中

    int (*create) (struct inode *,struct dentry *, umode_t, bool);			//VFS通过系统调用create()和open()来调用该函数,为denty对象创建一个新的索引节点,创建时使用 mode指定初始模式
    int (*link) (struct dentry *,struct inode *,struct dentry *);			//由系统调用link()调用,用来创建硬链接
    int (*unlink) (struct inode *,struct dentry *);							//由系统调用unlink()调用,从目录中删除指定的索引节点对象
    int (*symlink) (struct inode *,struct dentry *,const char *);			//由系统调用symlik()调用,创建符号链接
    int (*mkdir) (struct inode *,struct dentry *,umode_t);					//由系统调用mkdir()调用,创建一个新的目录
    int (*rmdir) (struct inode *,struct dentry *);							//由系统调用rmdir()调用,删除一个目录
    int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);			//由系统调用mknod()调用,创建特殊文件(设备、管道、套接字)
    int (*rename) (struct inode *, struct dentry *,							//VFS调用该函数来移动文件
            struct inode *, struct dentry *, unsigned int);
    int (*setattr) (struct dentry *, struct iattr *);						//由系统调用notify_change()调用,在修改索引节点后,同时发生了“改变事件”
    int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);//VFS会调用该函数,用于通知索引节点需要从磁盘中更新
    ssize_t (*listxattr) (struct dentry *, char *, size_t);					//将特定文件的所有属性列表拷贝到一个缓冲列表中
    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;

5、目录项

5.1、目录项对象

  VFS引入目录项概念,目录项对象由结构dentry表示,每个dentry代表路径中的一个部分,包括目录、文件和安装点。VFS在执行目录操作时,如果需要会现场创建目录项对象。
  dentry结构定义在 中:

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,文件系统特有数据 */

    union {
        struct list_head d_lru;     		/* LRU list */
        wait_queue_head_t *d_wait;  		/* in-lookup ones only */
    };		
    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 hlist_bl_node d_in_lookup_hash;  /* only for in-lookup ones */
        struct rcu_head d_rcu;				/* RCU锁 */
    } d_u;
};

  与索引节点和超级块不同,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。目录项对象没有脏标识,因为它不会保存在磁盘上。

5.2、目录项状态

  目录项对象有三种有效状态:被使用、未被使用和负状态

  • 一个被VFS使用的目录项对应一个有效的索引节点(d_inode指向响应的索引节点)并且在比较旧的内核中使用d_count 来表示存在一个或多个使用者。一个被使用的目录项不能被释放。
  • 一个未被使用的目录项也对应一个有效的索引节点并且d_count为0,目录项对象被保存在内存中,以便在有需要时使用,可以使查找路径更加迅速。不需要的话也可以回收内存。
  • 一个负状态(无效) 的目录项没有对应的索引节点(d_inode为NULL),由于索引节点已被删除或者原先的路径已经不正确,尽管如此,目录项对象仍被保留,以便以后快速解析路径查询。
      目录项对象释放后也可以保存到slab对象缓存中,事实上大部分内核的目录项缓存也是使用高速缓存来进行分配

5.3、目录项缓存

  如果VFS层遍历路径名中所有的元素并将它们猪哥地解析成目录项对象,还要到达最深层目录,这将是一件非常费力耗时的工作。所以内核将目录项对象缓存在目录项缓存(简称dcache,这只是个简称,4.9.1内核中使用dentry_cache高速缓存来实现,缓存中的对象即为dentry 结构)中:
  目录项缓存包括三个主要部分

  • 被使用的”目录项链表。该链表通过索引节点对象中的i_dentry项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以可能有多个目录项对象,因此用链表来表示;
  • 最近被使用的”双向链表。该链表含有未被使用的和负状态的目录项对象。由于该链表总是在头部插入目录项,所以链头节点的数据总比链尾的数据要新。当内核需要通过删除节点项回收内存时,会从链尾删除节点项,因为尾部的节点最旧;
  • 散列表和相应的散列函数,用来快速地将给定路径解析为相关目录项对象。
      散列表由数组dentry_hashtable表示,其中每一个元素都是指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。
      实际的散列值d_hash() 函数计算,他是内核提供给文件系统的唯一的一个散列函数。
      查找散列表通过d_lookup() 函数,如果该函数在dcache中发现了与其相匹配的目录项对象,则匹配的对象被返回;否则,返回NULL。

  为了避免每次访问某个路径名都要沿着目录依次解析全部路径,VFS会现在目录项缓存中搜索路径名,如果找到了就无须进行路径解析。相反,如果该目录项在目录项缓存中不存在,VFS就必须遍历文件系统为每个路径分量解析路径,解析完毕后,将目录项对象加入dcache中,以便以后可以快速查找到。
  只要目录项被缓存,相应的索引节点也会被缓存。如果目录项对象在目录项缓存中,那么和目录项对象相关的索引节点对象不会被释放,因为目录项会让相关索引节点对象的引用计数为正,确保索引节点能留在内存中。因此,只要路径名在缓存中被找到了,那么相应的索引节点肯定也在内存中缓存着。

5.4、目录项操作

  dentry_operation结构指明了VFS操作目录项的所有方法。结构定义在 中:

struct dentry_operations {        
	/* 判断目录项对象是否有效。VFS准备从dcache中使用一个目录项时会调用该函数,
	通常大部分文件系统认为目录项对象总是有效的,所以设置为NULL */                                                                                                                                                                                                                                            
    int (*d_revalidate)(struct dentry *, unsigned int);				
    int (*d_weak_revalidate)(struct dentry *, unsigned int);
    /* 为目录项生成散列值,当目录项需要加入散列表时,VFS调用此函数 */	
    int (*d_hash)(const struct dentry *, struct qstr *); 
    /* 比较两个文件名是否相同。大部分文件系统区分大小写,因此设置为NULL使用默认操作。
    如果使用此函数要加dcache_lock锁 */
    int (*d_compare)(const struct dentry *,
            unsigned int, const char *, const struct qstr *); 
    /* 当目录项对象的d_count计数值等于0时,VFS调用该函数。使用时需要加dcache_lock */
    int (*d_delete)(const struct dentry *); 
    int (*d_init)(struct dentry *); 
    /*  当目录项对象要被释放时,VFS调用此函数。默认情况下,什么都不做 */
    void (*d_release)(struct dentry *); 
    void (*d_prune)(struct dentry *); 
    /* 当一个目录项对象丢失其相关索引节点时(磁盘索引节点被删除了),VFS调用此函数。默认情况下VFS会调用
    iput()函数。如果文件系统重载了此函数,那么除了执行此文件系统特殊的工作之外,还必须调用iput()函数 */
    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 dentry *(*d_real)(struct dentry *, const struct inode *,
                 unsigned int);
} ____cacheline_aligned;

6、文件

6.1、文件对象

  文件对象(不是物理文件)是以打开的文件在内存中的表示。文件对象由相应的open()系统调用创建,由close()系统调用撤销。因为多个进程可以同时打开和操作同一个文件,所以同一个文件可能存在多个对应的文件对象。文件对象仅仅代表进程观点上代表已打开文件,它反过来指向目录项对象(而目录项对象又指向索引节点)。只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象是唯一的。
  文件对象由file结构体表示,定义在 中:

struct file {
    union {
        struct llist_node   fu_llist;				/* 文件对象链表 */
        struct rcu_head     fu_rcuhead;				/* 释放之后的RCU链表 */
    } 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;					/* 拥有者,可以通过信号进行异步IO数据传输 */
    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 */

  类似于目录项对象,文件对象没有实际的磁盘数据。所以在结构上没有“是否为脏”、“是否需要写回”的标志。文件对象可以通过f_dentry指针指向相关的目录项对象,目录项对象会指向相关的索引节点,索引节点会记录文件是否是脏的。

6.2、文件操作

  文件对象的操作由file_operations 结构体表示,定义在文件 中:

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 (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    /* 当用户空间调用ioctl()时,VFS会调用unlocked_ioctl。
    我觉得之所以叫unlock_ioctl是因为以前存在BKL(大内核锁),而这个函数不需要调用者持有BKL */
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    /* 在64位系统上使用32位应用程序时,使用此ioctl函数 */
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    /* 将给定的文件映射到指定的地址空间上,由系统调用mmap()调用 */
    int (*mmap) (struct file *, struct vm_area_struct *);
    /* 创建一个新的文件对象,并将它和相应的索引节点对象关联起来,由系统调用open()调用 */
    int (*open) (struct inode *, struct file *);
    /* 当以打开文件的引用计数减少时,该函数被VFS调用。具体作用看具体文件系统 */
    int (*flush) (struct file *, fl_owner_t id);
    /* 当文件的最后一个引用被注销时(最后一个共享文件描述符的进程条用了close()或退出时),
    该函数会被VFS调用,具体作用看具体文件系统 */
    int (*release) (struct inode *, struct file *);
    /* 将给定文件的所有被缓存数据写回磁盘,由系统调用fsync()调用 */
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    /* 打开或关闭异步I/O的通告信号 */
    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);
    /* 当给出SETFL命令时,用来检查传递给fcntl()系统调用的flags的有效性,目前只有NFS文件系统上实现了。
    在NFS文件系统中,不允许把O_APPEND和O_DIRECT相结合 */
    int (*check_flags)(int);
    /* 用来实现flock()系统调用,提供忠告锁(啥玩意???) */
    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 **, void **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#endif
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
            loff_t, size_t, unsigned int);
    int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
            u64);
    ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
            u64);
};

7、和文件系统相关的数据结构

  除了上面的四种VFS主要对象结构外,还有file_system_type用来描述各种特定文件系统类型,比如ext3、ext4等等;vfsmount 用来描述一个安装文件系统的实例。
  file_system_type结构体定义在 中:

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_RENAME_DOES_D_MOVE   32768   /* FS will handle d_move() during rename() internally. */
    struct dentry *(*mount) (struct file_system_type *, int,	
               const char *, void *);						/* 磁盘中读取目录项对象 */
    void (*kill_sb) (struct super_block *);					/* 终止访问超级块 */
    struct module *owner;									/* 文件系统模块 */
    struct file_system_type * next;							/* 链表中下一个文件系统类型 */
    struct hlist_head fs_supers;							/* 超级块对象链表 */

	/* 以下字段在VFS运行时使锁生效 */
    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;
};


  不管有几个文件系统被安装到系统中,都只有一个file_system_type结构。
  当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。vfsmount 结构定义在 中:

  旧的内核中vfsmount的定义如下:
Linux内核设计与实现——读书笔记(10)虚拟文件系统VFS_第1张图片

  较新的内核的结构变为如下所示:

struct vfsmount {
    struct dentry *mnt_root;    /* root of the mounted tree */
    struct super_block *mnt_sb; /* pointer to superblock */
    int mnt_flags;
};

  vfsmount结构保存了在安装时指定的标志信息,这些信息存储在mnt_flags中:

标志 描述
MNT_NOSUID 禁止该文件系统的可执行文件设置setuid和setgid标志
MNT_MODEV 禁止访问该文件系统上的设备文件
MNT_NOEXEC 禁止执行该文件系统上的可执行文件

  安装那些不充分信任的移动设备时,这些标志就很有用了。

8、和进程相关的数据结构

  系统中有三个数据结构将VFS层和系统的进程紧密联系在一起:files_struct 、 fs_struct 和namespace 结构。

8.1、files_struct

  files_struct 结构定义在文件 中,该结构体由进程描述符task_struct 中的files目录项对象指向。所有和单个进程相关的信息都保存在files_struct 结构中:

#define BITS_PER_LONG 32
#define NR_OPEN_DEFAULT BITS_PER_LONG 
/*
 * 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 __rcu *fdt;								/* 指向其他fd表的指针 */
    struct fdtable fdtab;									/* 基fd表 */
  /*  
   * written part on a separate cache line in SMP
   */
    spinlock_t file_lock ____cacheline_aligned_in_smp;		/* 单个文件的锁 */
    unsigned int next_fd;									/* 缓存下一个可用的fd */
    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_array数组指针指向已打开的文件对象。NR_OPEN_DEFAULT = BITS_PER_LONG = 32,所以在32位机上这个数组最大容纳32个文件对象,如果一个进程打开超过32个文件对象,内核将分配一个新数据,并将fdt指针指向它。所以,如果打开的文件对象不超过32个,那么对这些文件对象的访问会执行很快。

8.2、fs_struct

  fs_struct 结构包含文件系统和进程相关的信息,由进程描述符的fs域指向,定义在 中:

struct fs_struct {                                                                                                                                                                                                                                                            
    int users;						/* 用户数目 */
    spinlock_t lock;				/* fs_struct保护锁 */
    seqcount_t seq;
    int umask;						/* 不知道干什么的掩码 */
    int in_exec;					/* 当前正在执行的文件 */
    struct path root, pwd;			/* 根目录路径和当前工作目录路径 */
};

  fs_struct包含了当前仅存的当前工作目录(pwd)和根目录。

8.3、namespace

  在旧内核中namespace结构体定义在 中,由进程描述符中namespace域指向。新的内核使用nsproxy来管理各种namespace。
   namespace使每一个进程在系统中都看到唯一的安装文件系统:

struct namespace {
    atomic_t        count;					/* 使用计数 */
    struct vfsmount *   root;               /* 根目录的安装点对象 */                                                                                                                                                                                                                                  
    struct list_head    list;				/* 安装点链表 */
    struct rw_semaphore sem;
};

   list域是连接已安装文件系统的双向链表,它包含的元素组成了全体命名空间。
   新内核中的nsproxy结构如下所示:

/*
 * A structure to contain pointers to all per-process
 * namespaces - fs (mount), uts, network, sysvipc, etc.
 *
 * The pid namespace is an exception -- it's accessed using
 * task_active_pid_ns.  The pid namespace here is the
 * namespace that children will use.
 *
 * 'count' is the number of tasks holding a reference.
 * The count for each namespace, then, will be the number
 * of nsproxies pointing to it, not the number of tasks.
 *
 * The nsproxy is shared by tasks which share all namespaces.
 * As soon as a single namespace is cloned or unshared, the
 * nsproxy is copied.
 */
struct nsproxy {
    atomic_t count;
    struct uts_namespace *uts_ns;
    struct ipc_namespace *ipc_ns;                                                                                                                                                                                                                                             
    struct mnt_namespace *mnt_ns;
    struct pid_namespace *pid_ns_for_children;
    struct net       *net_ns;
    struct cgroup_namespace *cgroup_ns;
};

   上述的结构都是通过进程描述符连接起来的。对于多数进程来说,它们的描述符都指向唯一的files_structfs_struct结构体。但是对于那些使用克隆标志CLONE_FILESCLONE_FS创建的进程,会共享这两个结构体。namespace结构体在默认情况下,所有进程共享同样的命名空间,所有进程都继承其父进程的命名空间。只有在进行clone() 操作时使用CLONE_NEWS标志,才会给进程一个唯一的命名空间结构体的拷贝。
  
  
  
  
  

你可能感兴趣的:(Linux内核)