【Linux】Linux的虛擬文件系統

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文件可使進程直接對內核的參數進行配置的特點,可以在不重新編譯內核的情況下優化系統配置。

參考文章:深入理解linux系統下proc文件系統內容

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章