linux VFS虚拟文件系统


Linux的虚拟文件系统与实际文件系统的关系如下所示:
VFS的原理
各种文件系统之所以有区别,就在于它们的目录文件结构各不相同,随之而来的也就是对文件目录的操作函数也不相同。
对于前者可以在保留原系统目录结构的基础上,再构建一个新的统一的目录文件结构,而这个新目录文件中的信息是通过提取原系统目录文件信息进行重新组织来建立的。
这样,用户面对的就不再是五花八门的目录文件,而是一个统一的目录文件。
为了让不同文件系统操作函数实现一个统一的操作界面,VFS利用函数指针,因为函数指针实质上就是对函数的一次抽象,如下:
从上图可见,用户在使用函数指针f()调用函数时,真正被调用的函数为该指针指向的函数,而指针f只是一个替身, 或者说,它只是实际函数的一个虚拟表示。换句话说,通过同一个f可以调用不同的函数,至于调用的是哪一个函数,则取决于操作系统使哪个函数与指针f相关联。
这也就是说,函数指针可以为多个不同的函数提供一个统一界面。
使用上述方法,Linux为虚拟文件系统构建了如下结构的函数指针集,以统一操作界面:


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

用图形表示的file_operations如下所示:
上图表示的函数指针表,就是VFS提供的用户与实际文件系统之间的接口。
也就是说,不论使用什么文件系统,用户面对的都是指针表中的函数指针,至于具体的实际文件系统则取决于与函数指针相关联的具体函数。
例如,如果VFS与Ext2系统挂接,那么用户对虚拟文件系统的操作就是对Ext2文件系统的操作,如下图所示:
当然,如果实际文件系统不支持虚拟文件系统的某个操作,则该指针应该为NULL。
从上述内容可知,虚拟文件系统既没有文件,也不直接管理文件,它只是用户与实际文件系统之间的接口。
因此,它并不需要保存在永久存储介质中,只是在需要时由内核在内存中创建起来的一个文件系统,所以叫做虚拟文件系统。
出于系统性能的考虑和设计者的偏好,Linux系统VFS的结构与Ext2基本相同。
对于上述虚拟文件系统,《Linux内核源代码情景分析》中有如下描述:如果把内核比喻成计算机中的“母板”,把VFS比喻成“母板”上的一个“插槽”,那么每一个具体的文件系统就好像是一块“接口卡”
。母板面对的是一个固定的插槽,而使用实际系统则是接口卡,插上什么卡就执行什么功能。
VFS的超级块
VFS超级块
所有的实际文件系统,一旦被安装到系统上,就必须由系统在内存为其创建一个虚拟文件系统的超级块。
这个超级块实质上也就是系统把实际文件系统超级块的相关信息提取出来(如Ext2的ext2_sb_info),再与虚拟系统所需要的通用信息拼接起来而形成的一个格式统一的超级块, 它相当于一个实际文件系统在虚拟文件系统中的身份证。这个虚拟超级块包含如下信息:
设备标识符。这是存储文件系统的物理设备的设备标识符;
节点指针,包括安装i节点指针和覆盖i节点指针。其中,安装i节点指针指向被安装的子文件系统的第一个i节点;覆盖i节点指针则指向被安装文件系统的安装点;
文件数据块的大小;
超级块操作集。这是一组指向超级块操作例程的指针,VFS利用他们可以对超级块和i节点进行访问操作;
所安装的文件系统的类型;
被安装文件系统的一些私有信息。
虚拟文件系统的超级块在文件include/linux/fs.h中定义如下:


    struct super_block {
    	struct list_head	s_list;		/* 文件超级块链表头指针 */
    	dev_t			s_dev;		/* 文件系统的块设备标识 */
    	unsigned long		s_blocksize;        //数据块的大小
    	unsigned char		s_blocksize_bits;        //块大小值所占的位数
    	unsigned char		s_dirt;        //该值若为1,标识该超级块已被修改
    	unsigned long long	s_maxbytes;	/* 文件大小的最大值 */
    	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_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 */
    	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;
    	/* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
    	struct list_head	s_dentry_lru;	/* unused dentry lru */
    	int			s_nr_dentry_unused;	/* # of dentry on lru */
     
    	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;	/* 指向实际文件系统超级块内核信息结构的指针 */
    	fmode_t			s_mode;
    	struct mutex s_vfs_rename_mutex;	/* Kludge */
    	u32		   s_time_gran;
    	char *s_subtype;
    	char *s_options;
    	struct list_head s_async_list;
    };

    

虚拟文件系统的超级块,既包含虚拟文件系统的通用管理信息,也包含具体文件系统的私有信息。
其中,结构的指针s_fs_info就指向具体实际文件系统的私有信息,该信息被保存在内存的一个结构中。
例如:当虚拟文件系统在与Ext2文件系统对接时,指针s_fs_info就指向Ext2文件系统的私有信息结构struct ext2_sb_info。
即系统在安装Ext2系统时,会在内存中创建一个struct ext2_sb_info结构实例, 将磁盘文件系统Ext2超级块struct ext2_super_block的部分私有信息提取到该结构,并将结构与VFS超级块指针s_fs_info关联起来,如下图所示:
结构struct ext2_sb_info的定义如下:


    struct ext2_sb_info {
    	unsigned long s_frag_size;	    /* 碎片大小 */
    	unsigned long s_frags_per_block;    /* 每块碎片的数目 */
    	unsigned long s_inodes_per_block;    /* 每块节点数 */
    	unsigned long s_frags_per_group;    /* 块组中的碎片数 */
    	unsigned long s_blocks_per_group;    /* 块组中块的数量 */
    	unsigned long s_inodes_per_group;    /* 块组中的节点数目 */
    	unsigned long s_itb_per_group;	    /* 块组中节点表所占用的块数 */
    	unsigned long s_gdb_count;	    /* 块组描述符表所占用的块数 */
    	unsigned long s_desc_per_block;	    /* 块组描述符数 */
    	unsigned long s_groups_count;	    /* 块组数 */
    	unsigned long s_overhead_last;  /* 块缓冲区指针 */
    	unsigned long s_blocks_last;    /* 指向缓冲区中超级块的指针 */
    	struct buffer_head * s_sbh;	    /* 指向缓冲区中组描述符表的指针 */
    	struct ext2_super_block * s_es;	    /* Pointer to the super block in the buffer */
    	struct buffer_head ** s_group_desc;
    	unsigned long  s_mount_opt;
    	unsigned long s_sb_block;
    	uid_t s_resuid;
    	gid_t s_resgid;
    	unsigned short s_mount_state;
    	unsigned short s_pad;
    	int s_addr_per_block_bits;
    	int s_desc_per_block_bits;
    	int s_inode_size;            //节点大小
    	int s_first_ino;            //第一个节点号
    	spinlock_t s_next_gen_lock;
    	u32 s_next_generation;
    	unsigned long s_dir_count;
    	u8 *s_debts;
    	struct percpu_counter s_freeblocks_counter;
    	struct percpu_counter s_freeinodes_counter;
    	struct percpu_counter s_dirs_counter;
    	struct blockgroup_lock *s_blockgroup_lock;
     
    	spinlock_t s_rsv_window_lock;
    	struct rb_root s_rsv_window_root;
    	struct ext2_reserve_window_node s_rsv_window_head;
    };

    

VFS超级块的操作函数集
依照Linux的设计思想,Linux把有关文件系统超级块的操作函数集中地列写在一个封装了超级块操作函数指针的结构super_operations中,并用VFS超级块结构中的域s_op指向这个结构:

    struct super_operations {
       	struct inode *(*alloc_inode)(struct super_block *sb);
    	void (*destroy_inode)(struct inode *);
     
       	void (*dirty_inode) (struct inode *);
    	int (*write_inode) (struct inode *, int);
    	void (*drop_inode) (struct inode *);
    	void (*delete_inode) (struct inode *);
    	void (*put_super) (struct super_block *);
    	void (*write_super) (struct super_block *);
    	int (*sync_fs)(struct super_block *sb, int wait);
    	int (*freeze_fs) (struct super_block *);
    	int (*unfreeze_fs) (struct super_block *);
    	int (*statfs) (struct dentry *, struct kstatfs *);
    	int (*remount_fs) (struct super_block *, int *, char *);
    	void (*clear_inode) (struct inode *);
    	void (*umount_begin) (struct super_block *);
     
    	int (*show_options)(struct seq_file *, struct vfsmount *);
    	int (*show_stats)(struct seq_file *, struct vfsmount *);
    #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);
    };

        

这个结构与前面介绍的struct file_operations一样,相当于具体文件系统超级块所使用的安装在内核上的“插槽”。
VFS的dentry结构
结构dentry是实际文件系统的目录项在虚拟文件系统VFS中的对应物,实质上就是前面所讲的目录缓冲区。
当系统访问一个具体文件时,会根据这个文件在磁盘上的目录在内存中创建一个dentry结构,它除了要存放文件目录信息之外,还要存放有关文件路径的一些动态信息。
例如:它建立了文件名与节点inode之间的联系,保存了目录项与其父、子目录之间的关系。
之所以建立这样的一个文件目录的对应物,是为了同一个目录被再次访问时,其相关信息就可以直接由dentry获得,而不必再次重复访问磁盘。
VFS的dentry结构 结构dentry在文件include/linux/dcache.h中定义如下:


    struct dentry {
    	atomic_t d_count;
    	unsigned int d_flags;		/* 记录目录项被引用次数的计数器 */
    	spinlock_t d_lock;		/* 目录项的标志 */
    	int d_mounted;
    	struct inode *d_inode;		/* 与文件名相对应的inode */
     
    	struct hlist_node d_hash;	/* 目录项形成的散列表 */
    	struct dentry *d_parent;	/* 指向父目录项的指针 */
    	struct qstr d_name;        //包含文件名的结构
     
    	struct list_head d_lru;		/* 已经没有用户使用的目录项的链表 */
    	union {
    		struct list_head d_child;	/* 父目录的子目录项形成的链表 */
    	 	struct rcu_head d_rcu;
    	} d_u;
    	struct list_head d_subdirs;	/* i节点别名的链表 */
    	struct list_head d_alias;	/* inode alias list */
    	unsigned long d_time;		/* used by d_revalidate */
    	const struct dentry_operations *d_op;        //指向dentry操作函数集的指针
    	struct super_block *d_sb;	/* 目录树的超级块,即本目录项所在目录树的根 */
    	void *d_fsdata;			/* 文件系统的特定数据 */
     
    	unsigned char d_iname[DNAME_INLINE_LEN_MIN];	/* 短文件名 */
    };

        

结构中的域d_name是qstr类型的结构。该结构的定义如下:


    struct qstr {
    	unsigned int hash;            //文件名
    	unsigned int len;            //文件名长度
    	const unsigned char *name;        //散列值
    };

        

内核通过维护一个散列表dentry_hashtable来管理dentry。系统一旦在内存中建立一个dentry,该dentry结构就会被链入散列表的某个队列中。
结构中的域d_count记录了本dentry被访问的次数。 当某个dentry的d_count的值为0时,就意味着这个dentry结构已经没有用户使用了,这时这个dentry会被d_lru链入一个由系统维护的另一个队列dentry_unused中。
由于内核从不删除曾经建立的dentry,于是,同一个目录被再次访问时,其相关信息就可以直接由dentry获得,而不必重复访问存储文件系统的外存。
另外,当本目录项有父目录节点时,其dentry结构通过域d_child挂入父目录项的子目录d_subdirs队列中,同时使指针d_parent指向父目录的dentry结构。
若本目录有子目录,则将它们挂接在域d_subdirs指向的队列中。
dentry的操作函数 为了对dentry结构进行操作,系统定义了一个dentry操作函数集,目录项dentry中的域d_op就指向封装了这些操作函数指针的结构:


    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 *);        //删除count为0的dentry
    	void (*d_release)(struct dentry *);        //释放一个dentry对象
    	void (*d_iput)(struct dentry *, struct inode *);        //丢弃目录项对应的inode
    	char *(*d_dname)(struct dentry *, char *, int);
    };

        

由于在结构中的所有域都是操作函数的指针,因此这个结构相当于一个函数跳转表,系统通过这个函数跳转表来调用所要使用的函数。
因此,这个结构相当于具体文件系统目录项所使用的安装在内核的“插槽”。
VFS的i节点
VFS的i节点简介
前面谈到,i节点是文件的控制块,它存放文件的基本信息。不同文件系统的i节点结构是不同的,甚至在一些一体化目录结构的文件系统(例如FAT系统)中根本就没有i节点的概念, 所以在使用某一个实际文件系统时,内核必须按照虚拟文件系统i节点的格式在内存创建一个VFS的i节点,并根据该实际文件系统的静态数据来填写这个i节点。
VFS的i节点结构在文件include/linux/fs.h中定义如下:


    struct inode {
    	struct hlist_node	i_hash;            //指向散列表的指针
    	struct list_head	i_list;            //指向i节点链表的这孩子很
    	struct list_head	i_sb_list;
    	struct list_head	i_dentry;            //指向dentry链表的指针
    	unsigned long		i_ino;            //i节点号
    	atomic_t		i_count;            //记录使用本节点进程数目的计数器
    	unsigned int		i_nlink;            //节点连接的别名的数目
    	uid_t			i_uid;            //文件拥有者的标识号
    	gid_t			i_gid;            //文件拥有者所在组的标识号
    	dev_t			i_rdev;            //文件设备的标识号
    	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;
    	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;        //指向i节点操作函数集的指针
    	const struct file_operations	*i_fop;	        /* 指向文件操作函数集的指针 */
    	struct super_block	*i_sb;            //指向文件系统超级块的指针
    	struct file_lock	*i_flock;
    	struct address_space	*i_mapping;        //指向下一个域i_data的指针
    	struct address_space	i_data;            //页缓冲区头结构
        void *generic_ip;            //指向实际文件节点信息结构的指针
    #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 */
    };

        

与VFS的超级块一样,VFS的i节点也是既包含虚拟文件系统的通用管理系统,又包含具体文件系统i节点的私有信息。
其中,结构的指针generic_ip就指向了具体文件系统i节点的私有信息,该信息也被保存在内存的一个结构中。
例如:当虚拟文件系统与Ext2文件系统对接时,指针generic_ip就指向Ext2文件系统的i节点信息结构ext2_inode_info。
即系统在安装Ext2系统时,会在内存中创建一个ext2_inode_info结构实例,并将磁盘文件系统Ext2的i节点的部分私有信息提取到该结构。
结构struct ext2_inode_info的代码如下:


    struct ext2_inode_info {
    	__le32	i_data[15];            //数据库块指针数组
    	__u32	i_flags;            //文件打开方式
    	__u32	i_faddr;
    	__u8	i_frag_no;            //第一个碎片号
    	__u8	i_frag_size;            //碎片大小
    	__u16	i_state;
    	__u32	i_file_acl;            //文件访问控制链表
    	__u32	i_dir_acl;
    	__u32	i_dtime;            //文件删除时间
     
    	__u32	i_block_group;            //文件所在块组号
    	struct ext2_block_alloc_info *i_block_alloc_info;
     
    	__u32	i_dir_start_lookup;
    #ifdef CONFIG_EXT2_FS_XATTR
    	struct rw_semaphore xattr_sem;
    #endif
    #ifdef CONFIG_EXT2_FS_POSIX_ACL
    	struct posix_acl	*i_acl;
    	struct posix_acl	*i_default_acl;
    #endif
    	rwlock_t i_meta_lock;
     
    	struct mutex truncate_mutex;
    	struct inode	vfs_inode;
    	struct list_head i_orphan;	/* unlinked but open inodes */
    };

        

当虚拟文件与Ext2文件系统相关联时,VFS的索引节点如下图所示:
VFS的i节点操作函数
对于索引节点的操作函数封装在结构inode_operations中。该结构的定义如下:


    struct inode_operations {
    	int (*create) (struct inode *,struct dentry *,int, struct nameidata *);        //创建一个新i节点
    	struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);        //查找一个i节点的dentry
    	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 (*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);
    };

        

inode的结构的指针i_op指向这个函数集。
需要注意的是,inode结构还是要一个指向文件操作函数集struct file_operations的指针i_fop。
文件缓冲区
与内存相比,处理器读取磁盘数据的速度相当低,只相当于读取内存速度的千分之一,所以为了提高系统效率,Linux要在内存为最近访问的磁盘数据简历文件缓冲区。
所谓文件缓冲区,就是一块内核内存区域。由于该区域是进程与磁盘之间起缓冲作用的中介,因此缓冲区在与磁盘交换信息时必须以块为访问单位。
即从磁盘的角度看,这个缓冲区是磁盘数据块缓冲区,而从进程的角度看,则是页缓冲区。如下图所示:
因此,内核中设置了两套用来管理缓冲区的数据结构。
磁盘数据块缓冲区
在Linux中,每一个磁盘数据块缓冲区用buffer_head结构来描述。buffer_head结构如下:


    struct buffer_head {
    	unsigned long b_state;		/* 缓冲区状态位图 */
    	struct buffer_head *b_this_page;/* 指向同一页面的缓冲块,形成环形链表 */
    	struct page *b_page;		/* 指向页缓冲指针 */
     
    	sector_t b_blocknr;		/* 逻辑块号 */
    	size_t b_size;			/* 块大小 */
    	char *b_data;			/* 指向数据块缓冲区的指针 */
     
    	struct block_device *b_bdev;        //指向块设备结构的指针
    	bh_end_io_t *b_end_io;		/* I/O completion */
     	void *b_private;		/* reserved for b_end_io */
    	struct list_head b_assoc_buffers; /* associated with another mapping */
    	struct address_space *b_assoc_map;	/* mapping this buffer is
    						   associated with */
    	atomic_t b_count;		/* 块引用计数 */
    };

        

其中,域b_data是指向内存中磁盘块缓冲区的指针;域b_size表示缓冲区的大小。
由于进程以页面为单位来访问缓冲区,所以结构中用域b_this_page把同一页面缓冲块组成了一个环形链表。
另外,系统根据磁盘数据块缓冲区的使用状态将它们分别组成了多个队列。
内存中的一个磁盘块缓冲区的队列示意图如下所示:
磁盘数据块缓冲区是一种全局资源,可以被所有的文件系统共享使用。
页缓冲区
为了方便进程对文件的使用,并在需要时能把以块为单位的磁盘块缓冲区(块的大小一般为512字节)映射到以页为单位(页的大小一般4k)的用户空间,VFS还建立了页缓冲区。
由于进程是通过VFS的i节点来访问文件的,因此,文件的页缓冲区也就被设置在inode结构中。
inode结构中有一个指向自身的i_data域的指针i_mapping,这个i_data域是一个address_space的结构。
而每一个页面的所有信息由struct page来描述,它有一个名称为mapping的域,这是一个指针,它指向一个struct address_space类型结构。
缓冲区的数据结构struct address_space的主要内容如下:


    struct address_space {
    	struct inode		*host;		    /* 属主的索引节点 */
    	struct radix_tree_root	page_tree;	        /* 全部页面的radix数 */
    	spinlock_t		tree_lock;	        /* and lock protecting it */
    	unsigned int		i_mmap_writable;        /* count VM_SHARED mappings */
    	struct prio_tree_root	i_mmap;		    /* tree of private and shared mappings */
    	struct list_head	i_mmap_nonlinear;        /*list VM_NONLINEAR mappings */
    	spinlock_t		i_mmap_lock;	    /* protect tree, count, list */
    	unsigned int		truncate_count;	    /* Cover race condition with truncate */
    	unsigned long		nrpages;	    /* 占用的物理边框总数 */
    	pgoff_t			writeback_index;        /* writeback starts here */
    	const struct address_space_operations *a_ops;	    /* 页缓冲区操作函数集指针 */
    	unsigned long		flags;		    /* error bits/gfp mask */
    	struct backing_dev_info *backing_dev_info;     /* 预读信息 */
    	spinlock_t		private_lock;	    /* for use by the address_space */
    	struct list_head	private_list;	    /* 页缓冲区链表 */
    	struct address_space	*assoc_mapping;	    /* 相关缓存 */
    } __attribute__((aligned(sizeof(long))));

        

页缓冲区与磁盘块缓冲区之间的关系如下图所示:
依照Linux的一贯风格,Linux将缓冲区操作函数封装在如下的address_space_operations结构中:


    struct address_space_operations {
    	int (*writepage)(struct page *page, struct writeback_control *wbc);
    	int (*readpage)(struct file *, struct page *);
    	void (*sync_page)(struct page *);
     
    	/* Write back some dirty pages from this mapping. */
    	int (*writepages)(struct address_space *, struct writeback_control *);
     
    	/* Set a page dirty.  Return true if this dirtied it */
    	int (*set_page_dirty)(struct page *page);
     
    	int (*readpages)(struct file *filp, struct address_space *mapping,
    			struct list_head *pages, unsigned nr_pages);
     
    	int (*write_begin)(struct file *, struct address_space *mapping,
    				loff_t pos, unsigned len, unsigned flags,
    				struct page **pagep, void **fsdata);
    	int (*write_end)(struct file *, struct address_space *mapping,
    				loff_t pos, unsigned len, unsigned copied,
    				struct page *page, void *fsdata);
     
    	/* Unfortunately this kludge is needed for FIBMAP. Don't use it */
    	sector_t (*bmap)(struct address_space *, sector_t);
    	void (*invalidatepage) (struct page *, unsigned long);
    	int (*releasepage) (struct page *, gfp_t);
    	ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,
    			loff_t offset, unsigned long nr_segs);
    	int (*get_xip_mem)(struct address_space *, pgoff_t, int,
    						void **, unsigned long *);
    	/* migrate the contents of a page to the specified target */
    	int (*migratepage) (struct address_space *,
    			struct page *, struct page *);
    	int (*launder_page) (struct page *);
    	int (*is_partially_uptodate) (struct page *, read_descriptor_t *,
    					unsigned long);
    };

        

通常,i_fop(inode的指向文件操作函数集的指针)并不直接与块设备联系,而是间接通过a_ops(address_space的页缓冲区操作函数集)读写文件。
文件的页缓冲就是i_fop与a_ops之间的缓冲,它是块设备缓冲之上的更高一级缓冲,直接用于具体文件的读写。
Linux的Proc文件系统
从原理上来讲,计算机中凡是能够提供信息和接受信息的设备或者主体,都可以被抽象成文件。
计算机应用程序经常需要了解Linux内核和正在运行进程的当前状态和信息,并且希望能对这些信息做简单的修改。
为此,Linux创建了一个专门提供上述信息的文件系统——Proc文件系统,该文件系统具有固定的安装点/proc,所以有时也把它叫做/proc文件系统。
Proc是一种特殊的文件系统。说它特殊,是因为它的文件并不像普通文件那样存放在外存储器中,它只是存在于内存中的一种文件。
同样,在外存储器中也不存在这个文件系统的目录和节点,它们也只存在于内存。当系统关闭之后,Proc文件系统包括其中所有的文件也就不复存在了。
实质上,Proc文件就是一个特殊的内存区,只不过系统把这个内存区看成文件,并且按照文件方式进行管理。
这个文件中存放的都是实时从操作系统的各个部分收集来的动态数据,进程通过访问这个文件不但可以方便地获得内核当前的运行状况和内部数据信息,并且还可以对其进行修改和配置。
下面是使用Proc文件系统的优点:
可以利用Proc文件系统的文件,开发一些专门用于获取内核数据的应用程序;
完成用户空间和内核空间的通信,能得到内核运行时的数据,安全且方便;
利用Proc文件可使进程直接对内核的参数进行配置的特点,可以在不重新编译内核的情况下优化系统配置。
————————————————
版权声明:本文为CSDN博主「Yngz_Miao」的编辑整理,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38410730/article/details/81368186

100次点赞 100次阅读