虚拟文件系统VFS(上)

Linux中断和中断处理程序

虚拟文件系统(VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中所有文件系统依赖VFS共存、协同工作,程序利用标准的Unix系统调用对不同的文件系统,甚至不同介质的文件系统进行读写操作。

1 通用文件系统接口

VFS使得用户可以直接使用Open()、read()、write()这样的系统调用而无须考虑具体文件系统和实际物理介质。
VFS将各种不同的文件系统抽象后采用统一的方式进行操作。同时与块I/O结合,提供抽象、接口以及交融,使得用户空间的程序调用统一的系统调用访问各种文件。

2 文件系统抽象层

之所以可以使用通用接口对所有类型的文件系统进行操作,是因为内核在底层文件系统接口上建立了一个抽象层,使得Linux能够支持各种文件系统。
为了支持多文件系统,VFS提供了一个通用文件系统模型,该模型概括了任何文件系统的常用功能集和行为。
VFS抽象层之所以能够衔接各种各样的文件系统,是因为它定义了所有文件系统都支持基本的、概念上的接口和数据结构。同时文件系统也将诸多行为在形式上与VFS的定义保持一致。实际文件系统的代码在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS层和内核的其余部分看来,所有文件系统都是相同的。
内核通过抽象层能够方便的支持各种各样的文件系统,实际的文件系统通过编程提供给VFS所希望的抽象接口和数据结构。

ret = write(fd, buf, len);

该系统调用将buf指针指向的长度为len的数据写入文件描述符fd对应的文件的当前位置,该系统调用首先被一个通用系统调用sys_write()处理,sys_write()找到fd所在的文件系统实际给出的操作,然后执行该操作,数据最后通过该操作写入存储介质。

3 Unix文件系统

Unix使用了四种与文件系统相关的抽象概念:文件、目录、索引节点、安装点。
从本质上将文件系统是特殊的数据分层存储结构,包含文件、目录和相关的控制信息,文件系统的通用操作包括创建、删除和安装等。Unix中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构上被称作命名空间,所有的已安装文件系统都作为根文件系统树的枝叶吹安在系统中。
文件通过目录进行组织,文件目录相当于文件夹,用来容纳相关文件,所以目录也包含其他目录,即子目录。所以目录层层嵌套,形成文件路径。文件路径中的每一部分都被称作目录条目,也称为目录项。在Unix中,目录属于普通文件,列出包含在目录中的所有文件。在VFS中同样把目录当做文件对待,对目录执行与文件相同的操作。目录项可以理解为文件路径,在读取到每个文件过后会为该文件建立目录项。
Unix文件系统将文件与文件信息加以区分,例如访问权限、文件大小、创建时间等属于文件信息。文件的相关信息被称为文件的元数据,存储在一个被称为索引节点(index node, inode)中的单独的数据结构中。
所有这些信息与文件系统控制信息相关,文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。
Unix文件系统在磁盘上的布局也是如此实现的,比如说在磁盘,文件信息安装inode格式存储在单独的块中,控制信息集中存储在磁盘的超级块中。Unix中文件的概念从物理上映射到存储介质上。Linux的VFS的设计目标就是要保证能与支持和实现了这些概念的文件系统协同工作。假如文件系统不支持这种风格,同样可以在Linux上工作,但必须经过封装提供符合概念的界面。这种转换需要在使用现场(on the fly)引入特殊处理,但是带来极大的开销。

4 VFS对象及其数据结构

VFS采用的是面向对象的设计思路,采用数据结构代表通用文件对象。VFS主要包括四个对象类型:

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

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

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

操作系统作为一个结构体指针实现,包含操作其父对象的函数指针。对于很多方法,如果VFS的通用函数无法满足,就必须使用文件系统的独有方法进行填充。

5 超级块对象

文件系统都必须实现超级块对象,用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区的文件系统超级块或者文件系统控制块。对于并非基于磁盘的文件系统,会在使用现场创建超级块并保存到内存。超级块对象由super_block表示,定义在<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;					/* 修改(脏)标志 */
	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;				/* inodes链表 */
	struct list_head	s_dirty;				/* 脏数据链表 */
	struct list_head	s_io;					/* 回写链表 */
	struct list_head	s_more_io;				/* 更多回写的链表 */
	struct hlist_head	s_anon;					/* 匿名目录项 */
	struct list_head	s_files;				/* 被分配文件链表 */
	/* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
	struct list_head	s_dentry_lru;			/* 未被使用目录项链表 */
	int			s_nr_dentry_unused;				/* 链表中目录项的数目 */

	struct block_device	*s_bdev;				/* 尚未同步标志 */
	struct mtd_info		*s_mtd;					/* 存储磁盘信息 */
	struct list_head	s_instances;			/* 该类型文件系统 */
	struct quota_info	s_dquot;				/* 限额相关选项 */

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

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

	void 			*s_fs_info;					/* 文件系统特殊信息 */
	fmode_t			s_mode;						/* 安装权限 */

	/*
	 * 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;							/* 子类型名称 */

	/*
	 * Saved mount options for lazy filesystems using
	 * generic_show_options()
	 */
	char *s_options;							/* 已存安装选项 */

	/*
	 * storage for asynchronous operations
	 */
	struct list_head s_async_list;
};

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

6 超级块操作

超级块结构体中最重要的域是s_op,指向超级块的操作函数表,超级块操作函数由super_operations结构体表示,定义在文件<linux/fs.h>中,其形式如下:

struct super_operations {
    /* 给定的超级块下创建和初始化一个新的索引节点对象 */
   	struct inode *(*alloc_inode)(struct super_block *sb);
    /* 用于释放给定的索引节点 */
	void (*destroy_inode)(struct inode *);
    /* VFS在索引节点脏(被修改)时会调用此函数,日志文件系统(如ext3和ext4)执行该函数进行日志更新 */
    void (*dirty_inode) (struct inode *);
    /* 用于将给定的索引节点写入从磁盘,wait参数指明写操作是否需要同步 */
	int (*write_inode) (struct inode *, int wait);
    /* 在最后一个指向索引节点的引用被释放后,VFS会调用此函数。VFS在简单地删除这个索引节点后,UNIX文件系统就不会定义该函数 */
	int (*drop_inode) (struct inode *);
    /* 从磁盘上删除给定的索引节点 */
    void (*delete_inode)(struct inode *);
    /* 在卸载文件系统时由VFS调用,用来释放超级块,调用者必须一直持有s_lock锁 */
	void (*put_super) (struct super_block *);
    /* 用给定的超级块更新磁盘上的超级块,VFS通过该函数对内存中的超级块与磁盘中的超级块进行同步,调用者必须一直持有s_lock锁 */
    void (*write_super) (struct super_block *);
    /* 使文件系统的数据元与磁盘上的文件系统同步,wait参数指定操作是否同步 */
	int (*sync_fs)(struct super_block *sb, int wait);
	int (*freeze_super) (struct super_block *);
	int (*freeze_fs) (struct super_block *);
	int (*unfreeze_fs) (struct super_block *);
    /* VFS通过调用该函数获取文件系统状态,指定文件系统相关的统计信息放置在kstatfs中 */
	int (*statfs) (struct dentry *, struct kstatfs *);
    /* 当指定新的安装选项重新安装文件系统时,VFS调用此函数,调用者必须一直持有s_lock锁 */
	int (*remount_fs) (struct super_block *, int *, char *);
    /* VFS调用该函数释放索引节点并清空包含相关数据的所有页面 */
    int (*clear_inodes) (struct inode *);
    /* VFS调用该函数中断安装操作 */
	void (*umount_begin) (struct super_block *);

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

该结构体中的每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的底层操作。

当文件系统需要对其超级块执行操作时,首先要在超级块对象中寻找需要的操作方法,当一个文件系统要写自己的超级块,需要调用:

sb->s_op->write_super(sb);

在这个调用中,sb是指向文件系统超级块的指针,沿着该指针进入超级块操作函数表s_op,并从表中取得希望得到的write_super()函数,该函数执行写入超级块的实际操作。需要注意的是,尽管write_siper()方法来自超级块,但在调用时,超级块毅然要作为参数进行传递,这是因为C语言缺少面向对象的支持、如果是在C++中,只需要如下调用:

sb.write_super()

由于在C语言中无法直接得到操作函数的父对象,所以必须将父对象以参数形式传递给操作函数。

s_op中的所有函数由VFS在进程上下文中调用,除了dirty_inode(),其他函数在必要时都可以阻塞。这其中的一些函数是可选的,在超级块操作表中,文件系统可以将不需要的函数指针设置为NULL。如果VFS发现操作函数指针是NULL,那么会调用通用函数执行相应操作,要么什么也不做,如何选择取决于具体操作。

7 索引节点对象

索引节点对象包含了内核在操作文件或目录时需要的全部信息,对于unix风格的文件系统来说,这些信息可以从磁盘索引节点直接读入,如果一个文件系统没有索引节点,那么不管这些信息在磁盘上是如何存放的,文件系统都必须从其中提取这些信息,没有索引节点的文件系统通常将文件的描述信息作为文件的一部分进行存放。这些文件系统与Unix风格的文件系统不同,没有将数据与控制信息分开存放。有些现代文件系统使用数据库存储文件的数据。但是无论采用哪种方式,索引节点必须在内存中创建以便文件系统使用。

索引节点对象由inode结构体表示,定义在文件<linux/fs.h>中:

struct inode {
	struct hlist_node	i_hash;				/* 散列表 */
	struct list_head	i_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;					/* 实际设备标识符 */
	loff_t			i_size;					/* 以字节为单位的文件大小 */
	struct timespec		i_atime;			/* 最后访问时间 */
	struct timespec		i_mtime;			/* 最后修改时间 */
	struct timespec		i_ctime;			/* 最后改变时间 */
	unsigned int		i_blkbits;			/* 以块为单位的块大小 */
	unsigned long		i_version;			/* 版本号 */
	unsigned long		i_blocks;			/* 文件的块数 */
	unsigned short          i_bytes;		/* 使用的字节数 */
	spinlock_t		i_lock;					/* 自旋锁 */
	struct semaphore	i_sem;				/* 索引节点信号量 */
	struct inode_operations	*i_op;			/* 索引节点操作表 */
	struct file_operations	*i_fop;			/* 缺省的索引节点操作 */
	struct super_block	*i_sb;				/* 相关的超级块 */
	struct file_lock	*i_flock;			/* 文件锁链表 */
	struct address_space	*i_mapping;		/* 相关的地址映射 */
	struct address_space	i_data;			/* 设备地址映射 */
	struct dquot		*i_dquot[MAXQUOTAS];/* 索引节点的磁盘限额 */
	/* These three should probably be a union */
	struct list_head	i_devices;			/* 块设备链表 */
	struct pipe_inode_info	*i_pipe;		/* 管道信息 */
	struct block_device	*i_bdev;			/* 块设备驱动 */
	struct cdev		*i_cdev;				/* 字符设备驱动 */

	unsigned long		i_dnotify_mask; 	/* 目录通知掩码 */
	struct dnotify_struct	*i_dnotify; 	/* 目录通知 */

	unsigned long		i_state;			/* 状态标志 */
	unsigned int		i_flags;			/* 文件系统标志 */

	atomic_t		i_writecount;			/* 写者计数 */
	void			*i_security;			/* 安全模块 */
};

一个索引节点代表文件系统中(索引节点仅当文件系统被访问时才会在内存中创建)的一个文件,也可以是设备或管道这样特殊的文件,因此索引节点结构体中有一些和特殊文件相关的项。

有时,某些文件系统并不能完整地包含索引节点结构体要求的所有信息,文件系统可以在实现中选择任意合适的办法来解决这个问题。

8 索引节点操作

索引节点的inode_operations项描述了VFS用以操作索引节点对象的所有方法,这些方法由文件系统实现。与超级块类似,对索引节点的操作调用方式如下:

i->i_op->truncate(i);

i 指向给定的索引节点,truncate()函数是由索引节点 i 所在的文件系统定义的。inode_operations结构体定义在文件<linux/fs.h>中。这些函数可能由VFS执行这些函数,也可能由具体的文件系统执行:

struct inode_operations {
	/* VFS通过系统调用create()和open()调用该函数,为dentry对象创建一个新的索引节点,创建时使用mode指定的初始模式 */
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
	/* 在特定目录中寻找索引节点,该索引节点对应于dentry中给出的文件名 */
    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
	/* 被系统调用link()调用,用来创建硬连接,硬连接名称由dentry参数指定,连接对象是目录dir中old_dentry目录项所代表的文件 */
    int (*link) (struct dentry *old_dentry,struct inode *dir,struct dentry *dentry);
	/* 被系统调用unlink()调用,从目录项dir中删除由目录项dentry指定的索引节点对象 */
    int (*unlink) (struct inode *dir,struct dentry *dentry);
    /* 被系统调用symlink()调用,创建符号连接。该连接名称由char *指定,连接对象是dentry *对象 */
	int (*symlink) (struct inode *,struct dentry *,const char *);
    /* 被系统调用mkdir()调用,创建一个新目录,创建时使用mode指定的初始模式 */
	int (*mkdir) (struct inode *dir,struct dentry *dentry,int mode);
    /* 被系统调用rmdir()调用,删除dir目录下的dentry代表的文件 */
	int (*rmdir) (struct inode *dir,struct dentry *dentry);
    /* 被系统调用mknod()调用,创建特殊文件(设备文件、命名管道或套接字)。要创建的文件放在dir目录中,其目录项为dentry,关联的设备为rdev,初始权限由mode指定 */
	int (*mknod) (struct inode *inode,struct dentry *dentry,int mode,dev_t rdev);
	/* VFS调用该目录移动文件。文件源路径在old_dir目录中吗,源文件由old_dentry指定,目标路径定在new_dir目录中,目录文件由new_dentry中 */
    int (*rename) (struct inode *old_dir, struct dentry *old_dentry,
			struct inode *new_dir, struct dentry *new_dentry);
    /* 被系统调用readlink()调用,拷贝数据到特定的缓冲区buffer中,拷贝的数据来自dentry指定的符号连接,拷贝大小最大可达buflen字节 */
	int (*readlink) (struct dentry *dentry, char __user *buffer,int buflen);
    /* VFS调用该函数,从一个符号连接查找它指向的索引文件,由dentry指向的连接被解析,其结果存放在nd指向的nameidata结构体中 */
	int (*follow_link) (struct dentry *dentry, struct nameidata *nd);
    /* VFS调用该函数,修改文件大小。在调用前,索引节点的i_size必须设置为预期的大小 */
	void (*truncate) (struct inode *);
    /* 用来检查给定的inode所代表的文件是否允许特定的访问模式。如果允许,返回-;否则返回负值的错误码 */
	int (*permission) (struct inode *, int, struct nameidata *);
    /* 被notify_change()调用,在修改索引节点后,通知发生了“改变事件” */
	int (*setattr) (struct dentry *, struct iattr *);
    /* 由VFS调用,通知索引节点需要从磁盘中更新 */
	int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
    /* 由VFS调用,给dentry指定的文件设置扩展属性。属性名为name,值为value */
	int (*setxattr) (struct dentry *dentry, const char *name, void *value, size_t size,int flags);
    /* 由VFS调用,向value中拷贝给定文件的扩展属性name对应的数值 */
	ssize_t (*getxattr) (struct dentry *dentry, const char *name, void *value, size_t size);
    /* 将特定文件的所有属性列表拷贝到一个缓冲列表中 */
	ssize_t (*listxattr) (struct dentry *, char *, size_t);
    /* 从给定文件中删除指定的属性 */
	int (*removexattr) (struct dentry *, const char *);
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值