linux VFS概述以及内核源代码分析
一、 概述
Linux能够支持各种不同的文件系统是通过VFS实现的,由于不同的物理文件系统具有不同的组织结构和不同的处理方式,为了能够处理各种不同的物理文件系统,操作系统必须把它们所具有的特性进行抽象,并建立一个面向各种物理文件系统的转换机制,通过这个转换机制,把各种不同物理文件系统转换为一个具有统一共性的虚拟文件系统。
VFS是一个软件层,用来处理与Unix标准文件系统相关的所有系统调用,是用户应用程序与文件系统实现之间的抽象层。它实际上向Linux 内核和系统中运行的进程提供了一个处理各种物理文件系统的公共接口,通过这个接口使得不同的物理文件系统在内核看来都是相同的。
VFS通用文件模型将文件以及其操作抽象为以下几种对象类型:超级块对象(superblock object)存放文件系统相关信息;索引节点对象(inode object)存放具体文件的一般信息;文件对象(file object)存放已打开的文件和进程之间交互的信息;目录项对象(dentry object)存放目录项与文件的链接信息。
二、 VFS相关的数据结构以及其分析
1.超级块对象
VFS超级块是各种具体文件系统在安装时建立的,并在这些文件系统卸载时自动删除,它只存在于内存中。VFS超级块在inculde/Linux/fs.h中定义,即数据结构super_block,该结构及其主要域的含义如下:
struct super_block {
struct list_head s_list; /*指向超级块链表的指针*/
/*
*包含该具体文件系统的块设备标识符。
*例如,对于 /dev/hda1,其设备标识符为 0x301
*/
kdev_t s_dev;
/*该具体文件系统中数据块的大小,以字节为单位*/
unsigned long s_blocksize;
/*块大小的值占用的位数,例如,如果块大小为1024字节,则该值为10*/
unsigned char s_blocksize_bits;
unsigned char s_dirt; /*修改标志*/
unsigned long long s_maxbytes; /*文件的最大长度*/
struct file_system_type *s_type;
/*指向某个特定的具体文件系统的用于超级块操作的函数集合的指针*/
struct super_operations *s_op;
struct dquot_operations *dq_op; /*指向磁盘限额方法的指针*/
unsigned long s_flags;
/*魔数,即该具体文件系统区别于其它文系统的一个标志*/
unsigned long s_magic;
struct dentry *s_root;
struct rw_semaphore s_umount;
struct semaphore s_lock;
int s_count;
atomic_t s_active;
struct list_head s_dirty; /*已修改索引节点的链表*/
struct list_head s_locked_inodes; /*涉及I/O的索引节点的链表*/
struct list_head s_files; /*分配给超级的文件对象的链表*/
struct block_device *s_bdev;
struct list_head s_instances;
struct quota_mount_options s_dquot; /*磁盘限额的选项*/
/*一个共用体,其成员是各种文件系统的fsname_sb_info数据结构*/
union {
struct minix_sb_info minix_sb;
struct ext2_sb_info ext2_sb;
struct ext3_sb_info ext3_sb;
struct hpfs_sb_info hpfs_sb;
struct ntfs_sb_info ntfs_sb;
struct msdos_sb_info msdos_sb;
struct isofs_sb_info isofs_sb;
struct nfs_sb_info nfs_sb;
struct sysv_sb_info sysv_sb;
struct affs_sb_info affs_sb;
struct ufs_sb_info ufs_sb;
struct efs_sb_info efs_sb;
struct shmem_sb_info shmem_sb;
struct romfs_sb_info romfs_sb;
struct smb_sb_info smbfs_sb;
struct hfs_sb_info hfs_sb;
struct adfs_sb_info adfs_sb;
struct qnx4_sb_info qnx4_sb;
struct reiserfs_sb_info reiserfs_sb;
struct bfs_sb_info bfs_sb;
struct udf_sb_info udf_sb;
struct ncp_sb_info ncpfs_sb;
struct usbdev_sb_info usbdevfs_sb;
struct jffs2_sb_info jffs2_sb;
struct cramfs_sb_info cramfs_sb;
void *generic_sbp;
} u;
struct semaphore s_vfs_rename_sem;
struct semaphore s_nfsd_free_path_sem;
};
所有超级块对象(每个已安装的文件系统都有一个超级块)以双向环形链表的形式链接在一起。链表中第一个元素和最后一个元素的地址分别存放在super_blocks变量的s_list域的 next 和 prev域中。
2.索引节点对象
文件系统处理文件所需要的所有信息都放在称为索引节点的数据结构中。VFS索引节点的数据结构inode在/includ/fs/fs.h中定义如下:
struct inode {
/**********描述索引节点高速缓存管理的域***********/
struct list_head i_hash; /*指向哈希链表的指针*/
struct list_head i_list; /*指向索引节点链表的指针*/
struct list_head i_dentry; /*指向目录项链表的指针*/
struct list_head i_dirty_buffers;
struct list_head i_dirty_data_buffers;
/**********描述文件信息的域****************/
unsigned long i_ino; /*索引节点号*/
atomic_t i_count; /*引用计数器*/
kdev_t i_dev; /*设备标识号 */
umode_t i_mode;
nlink_t i_nlink;
uid_t i_uid; /*文件拥有者标识号*/
gid_t i_gid; /*文件拥有者所在组的标识号*/
kdev_t i_rdev; /*实际设备标识号*/
loff_t i_size;
time_t i_atime; /*文件的最后访问时间*/
time_t i_mtime; /*文件的最后修改时间*/
time_t i_ctime; /*节点的修改时间*/
unsigned int i_blkbits; /*块的位数*/
unsigned long i_blksize; /*块大小*/
unsigned long i_blocks; /*该文件所占块数*/
unsigned long i_version; /*版本号*/
struct semaphore i_sem;
struct semaphore i_zombie; /*僵死索引节点的信号量*/
struct inode_operations *i_op; /*索引节点的操作*/
struct file_operations *i_fop; /*指向缺省的文件操作*/
struct super_block *i_sb; /*指向该文件系统超级块的指针 */
wait_queue_head_t i_wait; /*指向索引节点等待队列的指针*/
struct file_lock *i_flock; /*指向文件加锁链表的指针*/
/************用于分页机制的域***************/
struct address_space *i_mapping; /*把所有可交换的页面管理起来*/
struct address_space i_data;
struct dquot *i_dquot[MAXQUOTAS];
/*以下几个域应当是联合体*/
struct list_head i_devices; /*设备文件形成的链表*/
struct pipe_inode_info *i_pipe; /*指向管道文件*/
struct block_device *i_bdev; /*指向块设备文件的指针*/
struct char_device *i_cdev; /*指向字符设备文件的指针*/
unsigned long i_dnotify_mask; /*目录通知事件标志*/
struct dnotify_struct *i_dnotify;
unsigned long i_state; /*索引节点的状态标志*/
unsigned int i_flags; /*文件系统的安装标志*/
unsigned char i_sock; /*如果是套接字文件则为真*/
atomic_t i_writecount; /*写进程的引用计数*/
unsigned int i_attr_flags; /*文件创建标志*/
__u32 i_generation;
/*
*类似于超级块的一个共用体,其成员是各种具体
*文件系统的fsname_inode_info数据结构
*/
union {
struct minix_inode_info minix_i;
struct ext2_inode_info ext2_i;
struct ext3_inode_info ext3_i;
struct hpfs_inode_info hpfs_i;
struct ntfs_inode_info ntfs_i;
struct msdos_inode_info msdos_i;
struct umsdos_inode_info umsdos_i;
struct iso_inode_info isofs_i;
struct nfs_inode_info nfs_i;
struct sysv_inode_info sysv_i;
struct affs_inode_info affs_i;
struct ufs_inode_info ufs_i;
struct efs_inode_info efs_i;
struct romfs_inode_info romfs_i;
struct shmem_inode_info shmem_i;
struct coda_inode_info coda_i;
struct smb_inode_info smbfs_i;
struct hfs_inode_info hfs_i;
struct adfs_inode_info adfs_i;
struct qnx4_inode_info qnx4_i;
struct reiserfs_inode_info reiserfs_i;
struct bfs_inode_info bfs_i;
struct udf_inode_info udf_i;
struct ncp_inode_info ncpfs_i;
struct proc_inode_info proc_i;
struct socket socket_i;
struct usbdev_inode_info usbdev_i;
struct jffs2_inode_info jffs2_i;
void *generic_ip;
} u;
};
3.目录项对象
dentry 的定义在include/linux/dcache.h中:
struct dentry {
atomic_t d_count; /*目录项引用计数器*/
unsigned int d_flags; /*目录项标志*/
struct inode * d_inode; /*与文件名关联的索引节点*/
struct dentry * d_parent; /*父目录的目录项*/
struct list_head d_hash; /*目录项形成的哈希表*/
struct list_head d_lru; /*未使用的 LRU 链表*/
struct list_head d_child; /*父目录的子目录项所形成的链表*/
struct list_head d_subdirs; /*该目录项的子目录所形成的链表*/
struct list_head d_alias; /*索引节点别名的链表*/
int d_mounted; /*目录项的安装点*/
struct qstr d_name; /*目录项名(可快速查找) */
unsigned long d_time; /*由 d_revalidate函数使用*/
struct dentry_operations *d_op; /*目录项的函数集*/
struct super_block * d_sb; /*目录项树的根(即文件的超级块)*/
unsigned long d_vfs_flags;
void * d_fsdata; /*具体文件系统的数据*/
unsigned char d_iname[DNAME_INLINE_LEN]; /*短文件名*/
};
4.文件对象
file结构在include/linux/fs.h中定义如下:
struct file {
struct list_head f_list; /*所有打开的文件形成一个链表*/
struct dentry *f_dentry; /*指向相关目录项的指针*/
struct vfsmount *f_vfsmnt;/*指向VFS安装点的指针*/
struct file_operations *f_op; /*指向文件操作表的指针*/
atomic_t f_count; /*文件对象的引用计数器*/
unsigned int f_flags; /*打开文件时所指定的标志*/
mode_t f_mode; /*文件的打开模式*/
loff_t f_pos; /*文件的当前位置*/
/*预读标志、要预读的最多页面数、上次预读后的文
件指针、预读的字节数以及预读的页面数*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
int f_error;
unsigned long f_version;
void *private_data; /* tty驱动程序所需*/
/*用于直接访问缓冲区的描述符*/
struct kiobuf *f_iobuf;
long f_iobuf_lock;
};
三、 文件系统的mount操作
把一个文件系统(或设备)安装到一个目录点时要用到的主要数据结构为vfsmount,定义于include/linux/mount.h中:
struct vfsmount
{
struct list_head mnt_hash;
struct vfsmount *mnt_parent; /*所挂载的文件系统*/
struct dentry *mnt_mountpoint; /*挂载点目录*/
struct dentry *mnt_root; /*挂载文件树的根目录*/
struct super_block *mnt_sb; /*指向超级块的指针*/
struct list_head mnt_mounts; /*描述符父链表的头*/
struct list_head mnt_child; /*用于描述父链表的指针*/
atomic_t mnt_count;
int mnt_flags;
char *mnt_devname; /*设备名称*/
struct list_head mnt_list;
};
用户(一般是root ) 在挂载文件系统时,要指定三种信息:文件系统的名称、包含文件系统的物理块设备和文件系统在已有文件系统中的挂载点。
例如: # mount 2t msdos/dev/hdc/mnt/usr其中msdos 是要安装的文件系统类型, /dev/hdc 是文件系统所在的设备,/mnt/usr是安装点。
VFS 对上述命令的执行过程如下:
(1) 寻找对应的文件系统信息,VFS 通过filesystems在file- system- type 组成的链表中根据指定的文件系统名称搜索文件系统类型信息。
(2) 如果在上述链表中找到匹配的文件系统,则说明内核具有对该文件系统的内建支持。 否则,说明该文件系统可能由可装载模块支持,VFS 会请求内核装入相应原文件系统模块,此时,该文件系统在VFS 中注册并初始化。
(3) 不管是哪种情况,如果VFS 无法找到指定的文件系统,则返回错误。
(4) VFS 检验指定的物理块设备是否已经安装。 如果指定的块设备已被安装,则返回错误。
(5) VFS 查找作为新文件系统安装点目录的VFS索引节点,该VFS 索引节点可在索引节点高速缓存中,也有可能需要从安装点所在的块设备中读取。
(6) 如果该挂载点已经安装有其它文件系统则返回错误。 因为同一目录只能同时安装一个文件系统。
(7) VFS 安装代码为新的文件系统分配超块,并将安装信息传递给该文件系统的超块读取例程。
(8) 文件系统的超块读取例程将对应文件系统的信息映射到VFS 超块中。 如果在此过程中发生错误,则返回错误。
<!--[if !supportLists]--> 四、 <!--[endif]-->VFS的read操作
Linux中应用程序对read()的系统调用,将引起内核调用sys_read()服务例程,这完全和其它系统调用类似。在fs/目录下的read_write.c文件有sys_read()的定义,如下:
asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)
{
ssize_t ret;
struct file * file;
ret = -EBADF;
file = fget(fd);
if (file) {
if (file->f_mode & FMODE_READ) {
ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,
file, file->f_pos, count);
if (!ret) {
ssize_t (*read)(struct file *, char *, size_t, loff_t *);
ret = -EINVAL;
if (file->f_op && (read = file->f_op->read) != NULL)
ret = read(file, buf, count, &file->f_pos);
}
}
if (ret > 0)
dnotify_parent(file->f_dentry, DN_ACCESS);
fput(file);
}
return ret;
}
由以上代码可以看到,sys_read()定义了一个file结构(参见“二、4.文件对象”)的指针,然后调用了其f_op字段中的中指向read方法的指针。这样一来,应用程序对read的系统调用就被转化为相对间接的调用:file->f_op->read(…);
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
……
};
super_block对象中最后一个u联合体对象包含属于具体文件系统的超级块信息,例如,若超级块对象指的是ntfs文件系统,该字段就存放ntfs_sb_info数据结构,该结构包含磁盘分配位掩码和其它与VFS的通用文件模型无关的数据。inode对象中也有一个u联合体字段,用于存放属于具体文件系统的索引节点信息,如果索引节点指的是一个ntfs文件,该字段就存放一个名为ntfs_inode_info的数据结构。
每一个文件系统都有自己的文件操作(file_operations)集合,来执行read()、write()之类的操作。当内核将一个索引节点从磁盘装入内存,就会把指向这些文件操作的指针存放在file_operations结构中,而该结构的地址存放在索引节点对象的i_fop字段中。当进程打开这个文件时,VFS就用存放在索引节点中的这个地址初始化新文件对象的f_op字段,使得对文件操作的后续调用能够使用这些函数。如果需要,VFS也可以通过在f_op字段中存放一个新值而修改文件操作的集合。loff_t和ssize_t正是与具体文件系统相关的两个变量。